用于对两个字段进行排序的 Ruby 习惯用法

标签 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

我在 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 风格发表一些评论:

  1. 你几乎看不到 for在惯用的 Ruby 代码中。 each是在 Ruby 中进行几乎所有迭代的惯用方法,以及像 map 这样的“函数式”方法, reduceselect也很常见。从不for .

  2. Struct 的一大优势是您可以获得每个属性的访问器方法,因此您可以执行 tuple.word而不是 tuple[:word] .

  3. 调用没有参数的方法时不带括号:txt.split.map , 不是 txt.split().map

关于用于对两个字段进行排序的 Ruby 习惯用法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26513256/

相关文章:

ruby - 错误 - 迭代期间无法将新 key 添加到哈希中

ruby - 如何处理 Nokogiri 中的 404 not found 错误

JavaScript 不喜欢 hashes.to_json?

linux - sort -u 的意外结果

sorting - ElasticSearch排序不起作用

python - 在 Python 中对算法进行排序的最快方法

javascript - 如何在 rails 中初始化 Angularjs ng-model

ruby - 从 ruby​​ 中的字符串中删除空行

c - 如何在 Linux 上按名称对目录中的文件进行排序

c++ - 我的冒泡排序程序有什么错误?