我需要 Ruby 习惯用法来对两个字段进行排序。在 Python 中,如果您对双元素元组列表进行排序,它会根据第一个元素进行排序,如果两个元素相等,则会根据第二个元素进行排序。
一个示例是来自 http://www.pythonlearn.com/html-008/cfbook011.html 的以下 Python 排序代码(单词从最长到最短排序并考虑第二个元素以打破平局)
txt = 'but soft what light in yonder window breaks'
words = txt.split()
t = list()
for word in words:
t.append((len(word), word))
t.sort(reverse=True)
res = list()
for length, word in t:
res.append(word)
print res
我在 Ruby 中想到的是以下使用结构的代码
txt = 'but soft what light in yonder window breaks'
words = txt.split()
t = []
tuple = Struct.new(:len, :word)
for word in words
tpl = tuple.new
tpl.len = word.length
tpl.word = word
t << tpl
end
t = t.sort {|a, b| a[:len] == b[:len] ?
b[:word] <=> a[:word] : b[:len] <=> a[:len]
}
res = []
for x in t
res << x.word
end
puts res
我想知道是否有更好的方法(更少的代码)来实现这种稳定的排序。
最佳答案
我认为你想多了。
txt = 'but soft what light in yonder window breaks'
lengths_words = txt.split.map {|word| [ word.size, word ] }
# => [ [ 3, "but" ], [ 4, "soft" ], [ 4, "what" ], [ 5, "light" ], ... ]
sorted = lengths_words.sort
# => [ [ 2, "in" ], [ 3, "but" ], [ 4, "soft" ], [ 4, "what" ], ... ]
如果你真的想使用Struct,你可以:
tuple = Struct.new(:length, :word)
tuples = txt.split.map {|word| tuple.new(word.size, word) }
# => [ #<struct length=3, word="but">, #<struct length=4, word="soft">, ... ]
sorted = tuples.sort_by {|tuple| [ tuple.length, tuple.word ] }
# => [ #<struct length=2, word="in">, #<struct length=3, word="but">, ... ]
这等价于:
sorted = tuples.sort {|tuple, other| tuple.length == other.length ?
tuple.word <=> other.word : tuple.length <=> other.length }
(注意这次是sort
,不是sort_by
。)
...但是由于我们使用的是 Struct,我们可以通过定义我们自己的比较运算符 (<=>
) 来使它变得更好,即 sort
将调用(同样适用于任何 Ruby 类):
tuple = Struct.new(:length, :word) do
def <=>(other)
[ length, word ] <=> [ other.length, other.word ]
end
end
tuples = txt.split.map {|word| tuple.new(word.size, word) }
tuples.sort
# => [ #<struct length=2, word="in">, #<struct length=3, word="but">, ... ]
还有其他选项可以进行更复杂的排序。如果您想首先获得最长的单词,例如:
lengths_words = txt.split.map {|word| [ word.size, word ] }
sorted = lengths_words.sort_by {|length, word| [ -length, word ] }
# => [ [ 6, "breaks" ], [ 6, "window" ], [ 6, "yonder" ], [ 5, "light" ], ... ]
或者:
tuple = Struct.new(:length, :word) do
def <=>(other)
[ -length, word ] <=> [ -other.length, other.word ]
end
end
txt.split.map {|word| tuple.new(word.size, word) }.sort
# => [ #<struct length=6, word="breaks">, #<struct length=6, word="window">, #<struct length=6, word="yonder">, ... ]
如您所见,我非常依赖 Ruby 的内置功能来根据数组的内容对数组进行排序,但如果您愿意,您也可以“自己动手”,这可能对很多很多项目表现更好.这是一个相当于你的 t.sort {|a, b| a[:len] == b[:len] ? ... }
的比较方法代码(加上奖金 to_s
方法):
tuple = Struct.new(:length, :word) do
def <=>(other)
return word <=> other.word if length == other.length
length <=> other.length
end
def to_s
"#{word} (#{length})"
end
end
sorted = txt.split.map {|word| tuple.new(word.size, word) }.sort
puts sorted.join(", ")
# => in (2), but (3), soft (4), what (4), light (5), breaks (6), window (6), yonder (6)
最后,对您的 Ruby 风格发表一些评论:
你几乎看不到
for
在惯用的 Ruby 代码中。each
是在 Ruby 中进行几乎所有迭代的惯用方法,以及像map
这样的“函数式”方法,reduce
和select
也很常见。从不for
.Struct 的一大优势是您可以获得每个属性的访问器方法,因此您可以执行
tuple.word
而不是tuple[:word]
.调用没有参数的方法时不带括号:
txt.split.map
, 不是txt.split().map
关于用于对两个字段进行排序的 Ruby 习惯用法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26513256/