我正在研究如何在Ruby中使用特殊的排序机制。我最终在Ruby中重写了this neat JavaScript solution:
class SpecialStr
include Comparable
attr_accessor :str
def initialize (str)
@str = str
end
def <=> (other)
self_num, self_string = @str.split(' ')
other_num, other_string = other.str.split(' ')
self_num > other_num ? 1 : other_num > self_num ? -1 :
self_string > other_string ? -1 : 1
end
end
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr_object = []
arr.each { |str| arr_object << SpecialStr.new(str) }
arr_object.sort! { |x, y| y <=> x }
output_arr = []
arr_object.each { |obj| output_arr << obj.str}
puts output_arr
这具有所需的输出(数字递减,然后是字符串递增):
8540 xxxxxx
38 xxxx
20 axxx
20 bx
2 m
2 xxx
2 z
但代码似乎不必要地复杂化了。 (Ruby应该比JS更简洁!)所以我问自己(现在我问你),为什么我不能这样做呢?
def <=> (other)
self_num, self_string = self.split(' ')
other_num, other_string = other.split(' ')
self_num > other_num ? 1 : other_num > self_num ? -1 :
self_string > other_string ? -1 : 1
end
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr.sort! { |x, y| y <=> x }
puts arr
此输出错误,基于sort
,好像我没有重新定义<=>
:
8540 xxxxxx
38 xxxx
20 bx
20 axxx
2 z
2 xxx
2 m
此处的代码较短,但不起作用。它使用Ruby <=>
模块中内置的Comparable
版本,而不是我试图覆盖它。为什么我不能覆盖它?方法只能在类或模块内部重写吗? 是在Ruby中编写第一个脚本的方法更短吗? (对不起,如果这是一个菜鸟问题,我是初学者。)
答案 0 :(得分:3)
最简单的方法是将字符串拆分为数字和单词,然后按减号数字排序(以减少数字)和单词:
arr = ['2 xxx', '20 axxx', '2 m', '38 xxxx', '20 bx', '8540 xxxxxx', '2 z']
arr.sort_by! do |number_word|
number, word = number_word.split
[ -number.to_i, word ]
end
puts arr
# =>
# 8540 xxxxxx
# 38 xxxx
# 20 axxx
# 20 bx
# 2 m
# 2 xxx
# 2 z
排序数组时,第一个元素(-number
)具有优先级。如果两个第一个元素相同,则排序使用第二个元素(word
)。
答案 1 :(得分:1)
写作时
arr.sort! { |x, y| y <=> x }
相当于
arr.sort! { |x, y| y.<=>(x) }
即。它正在调用y
(太空飞船)运营商的<=>
版本。由于y
只是String
,因此会执行字符串的默认比较。
为了更简洁地编写代码,您可以在传递给sort!
的块中编写自定义比较逻辑:
arr.sort! do |x, y|
x_num, x_string = x.split(' ')
y_num, y_string = y.split(' ')
y_num > x_num ? 1 : x_num > y_num ? -1 :
y_string > x_string ? -1 : 1
end
或者,将其作为独立方法编写:
def my_compare(x, y)
x_num, x_string = x.split(' ')
y_num, y_string = y.split(' ')
y_num > x_num ? 1 : x_num > y_num ? -1 :
y_string > x_string ? -1 : 1
end
并从sort!
:
arr.sort! { |x, y| my_compare(x, y) }
有些事情可能有助于澄清:
在Ruby中,没有自由浮动的方法(即没有附加到类或模块的方法)。当您在任何类或模块之外编写def ...
时,该方法将作为实例方法添加到Object
。 严格来说,有unbound methods,但即使这些也需要在被调用之前与对象相关联。
接下来要记住的是<=>
的默认实现来自:它位于Kernel
模块,它包含在类Object
中。
所以当你在课堂外写def <=>(other)
时,你会覆盖Object
的方法:
[1] pry(main)> method(:<=>).owner
=> Kernel
[2] pry(main)> def <=>(other)
[2] pry(main)* puts "overridden!"
[2] pry(main)* end
=> :<=>
[3] pry(main)> method(:<=>).owner
=> Object
但是,String
类会覆盖<=>
本身。为了将字符串与另一个对象String
进行比较,将优先使用Object
,中的实现,即使您已覆盖{{1}中的方法}。
但是,如果您的类没有自己的Object
(或者在类层次结构中它与<=>
之间的重写实现),那么您在Object
上的重写方法确实会被使用:
Object
[6] pry(main)> class Something; end
=> nil
[7] pry(main)> s1 = Something.new
=> #<Something:0x007fddb4431ba8>
[8] pry(main)> s2 = Something.new
=> #<Something:0x007fddb4469760>
[9] pry(main)> s1 <=> s2
overridden!
=> nil
第一个代码段使用pry
方法来获取方法,然后使用method
找出类层次结构中定义方法的位置。
另一个例子:
owner
所以,如果我们有一只狗:
class Animal
def eat
puts "Munch!"
end
end
class Dog < Animal
def bark
puts "yap!"
end
end
我们可以找出其方法的来源:
buddy = Dog.new
所以在原始示例中,我们可以看到[10] pry(main)> buddy.method(:eat).owner
=> Animal
[11] pry(main)> buddy.method(:bark).owner
=> Dog
开始引用<=>
模块中的方法,但是当我们执行Kernel
时...这直接添加了一个方法def <=>
现在覆盖了所包含的方法。
第二个例子展示了当没有自己的Object
实现的最小类时会发生什么。 <=>
可以向我们展示直接在类上实现的实例方法。空instance_methods(false)
类没有任何:)
Something
所以它将使用继承的[14] pry(main)> Something.instance_methods(false)
=> []
方法。
答案 2 :(得分:1)
你的问题是:
y <=> x
只是一种花哨的(人性化的)写作方式:
y.<=>(x)
因此<=>
运算符不是函数调用,它是运算符左侧的方法调用。该方法调用不会使用您的def <=>
,因为您的比较方法未在您要排序的数组中的对象上定义,您已创建了<=>
其他一些课程的方法。
在JavaScript中,你会这样说:
a.sort(function(a, b) { ... })
或在更现代的时代:
a.sort((a, b) => ...)
所以你将sort
函数交给使用比较器,你没有在任何地方定义比较器运算符,只是一个带有两个参数并返回所需值的函数。
在Ruby中,您通常使用块作为&#34;回调&#34;:
arr.sort! do |a, b|
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
a_num > b_num ? 1 : b_num > a_num ? -1 : a_string > b_string ? -1 : 1
end
在我们继续之前,你的比较器逻辑有问题,因为Enumerable#sort
的块应该是
返回-1,0或+1,具体取决于
a
和b
之间的比较。
并且您的块不处理0
(相等)情况。此外,您的_num
仍然是字符串,因此他们不会比较相同的数字。第一个问题可以通过使用Array#<=>
(逐个元素地比较数组)来解决,然后可以通过简单的to_i
调用来解决第二个问题:
arr.sort! do |a, b|
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
您可以更进一步切换到sort_by!
:
arr.sort_by! do |e|
i, s = e.split(' ')
[i.to_i, s]
end
如果你想在多个地方使用块的逻辑,你可以使用lambda更接近JavaScript版本:
cmp = ->(a, b) do
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
arr1.sort!(&cmp)
arr2.sort!(&cmp)
natural = ->(e) do
i, s = e.split(' ')
[i.to_i, s]
end
arr1.sort_by!(&natural)
arr2.sort_by!(&natural)
或单独的方法:
def cmp(a, b)
a_num, a_string = a.split(' ')
b_num, b_string = b.split(' ')
[a_num.to_i, a_string] <=> [b_num.to_i, b_string]
end
def some_other_method
arr1.sort!(&method(:cmp))
arr2.sort!(&method(:cmp))
end
def natural(e)
i, s = e.split(' ')
[i.to_i, s]
end
def some_other_other_method
arr1.sort_by!(&method(:natural))
arr2.sort_by!(&method(:natural))
end
如果您真的想将self_number
和other_number
值作为字符串进行比较,请忽略to_i
次调用并进一步简化blocks / lambdas:
arr.sort! { |a, b| a.split(' ') <=> b.split(' ') }
arr.sort_by! { |e| e.split(' ') }