我需要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
Run Code Online (Sandbox Code Playgroud)
我在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
Run Code Online (Sandbox Code Playgroud)
我想知道是否有更好的方法(更少的代码)来实现这种稳定的排序.
我想你是在思考这个问题.
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" ], ... ]
Run Code Online (Sandbox Code Playgroud)
如果您真的想使用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">, ... ]
Run Code Online (Sandbox Code Playgroud)
这相当于:
sorted = tuples.sort {|tuple, other| tuple.length == other.length ?
tuple.word <=> other.word : tuple.length <=> other.length }
Run Code Online (Sandbox Code Playgroud)
(注意,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">, ... ]
Run Code Online (Sandbox Code Playgroud)
还有其他选项可用于更复杂的排序.如果你想先获得最长的单词,例如:
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" ], ... ]
Run Code Online (Sandbox Code Playgroud)
要么:
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">, ... ]
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我非常依赖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)
Run Code Online (Sandbox Code Playgroud)
最后,对您的Ruby风格进行了一些评论:
你几乎从未for在惯用的Ruby代码中看到过.each是在Ruby中进行几乎所有迭代的惯用方法,以及类似的"功能"方法map,reduce并且select也很常见.从不for.
Struct的一个很大的优点是你可以获得每个属性的访问器方法,所以你可以tuple.word代替tuple[:word].
没有参数的方法在没有括号的情况下调用:txt.split.map不是txt.split().map