用于在两个字段上排序的Ruby习语

use*_*148 2 ruby sorting

我需要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)

我想知道是否有更好的方法(更少的代码)来实现这种稳定的排序.

Jor*_*ing 5

我想你是在思考这个问题.

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风格进行了一些评论:

  1. 你几乎从未for在惯用的Ruby代码中看到过.each是在Ruby中进行几乎所有迭代的惯用方法,以及类似的"功能"方法map,reduce并且select也很常见.从不for.

  2. Struct的一个很大的优点是你可以获得每个属性的访问器方法,所以你可以tuple.word代替tuple[:word].

  3. 没有参数的方法在没有括号的情况下调用:txt.split.map不是txt.split().map