【问题标题】:How to sort hash in Ruby如何在Ruby中对哈希进行排序
【发布时间】:2018-04-19 22:32:28
【问题描述】:

我有以下哈希:

scores = {
  "Charlie" => 0
  "Delta" => 5 
  "Beta" => 2
  "Alpha" => 0
}

列出的数字是整数,团队表示为字符串。

如何按分数对列表进行排序,如果有平局,按字母顺序列出,然后输出?

我想象预期的输出如下所示:

1. Delta, 5 pts
2. Beta, 2 pts
3. Alpha, 0 pts
4. Charlie, 0 pts

我对哈希进行了排序,但如果出现平局,我不确定如何按字母顺序排序。我使用的代码如下:

scores = Hash[ scores.sort_by { |team_name, scores_array| scores_array.sum.to_s } ]

【问题讨论】:

  • 我发现该链接非常有用,更不用说它并没有真正解释如果有平局时如何按第二个键排序。查看我的编辑。
  • 您的示例数据中没有平局。
  • 我编辑了我的问题以显示一个领带的例子@Stefan

标签: ruby


【解决方案1】:

在 Ruby 中,Arrays are lexicographically ordered。您可以利用该事实来按多个键进行排序:只需创建一个要排序的键数组:

scores.sort_by {|team, score| [-score, team] }.to_h
#=> {'Delta' => 5, 'Beta' => 2, 'Alpha' => 0, 'Charlie' => 0}

【讨论】:

  • “数组是按字典顺序排列的” – 你的意思是Strings?
  • 不,我的意思是Arrays,否则我的解决方案将不起作用。这实际上记录在Array#<=>'s RDocsthe RubySpec 中。 Strings 也是如此,尽管 badly documentedspecified
  • 这很聪明!
  • @Schwern:这实际上是一个标准的 Ruby 习语,我很惊讶之前没有提到它。
【解决方案2】:

将哈希排序为键/值对的排序数组的一般模式如下所示。

sorted_array_of_tuples = hash.sort { |a,b| ... }

Hash 具有Enumerable mixin,这意味着它可以使用sort。用于 Hash sort 比较键和值的元组(两个元素数组),因此我们可以轻松地制作比较键和值的块。

通常你会使用排序后的数组。

sorted = hash.sort { ... }
sorted.each { |t|
    puts "#{t[0]}: #{t[1]}"
}

但您可以使用to_h 将其转回哈希。由于 Ruby 中的 Hash 会记住其键的插入顺序,因此从 sorted Array 创建的新生成的 Hash 将保留其排序。

sorted_hash = hash.sort { |a,b| ... }.to_h

...但是如果将更多键添加到sorted_hash,它们将不会被排序,它们只会在最后。因此,您可能希望冻结已排序的哈希以防止修改破坏排序。

sorted_hash.freeze

至于排序块,在其他语言中,“按此比较,如果相等,则按此比较”的习惯用法如下所示:

sorted_scores = scores.sort { |a,b|
    # Compare values or compare keys
    a[1] <=> b[1]    || a[0] <=> b[0]
}

这利用了&lt;=&gt; 在它们相等时返回0。在其他语言中,0false,因此您可以使用逻辑运算符来创建主要、次要和三次比较的完整链。

但在 Ruby 中,0 不是 false。只有falsefalse。通常这是一件好事,但这意味着我们需要更加具体。

sorted_scores = scores.sort { |a,b|
  # Compare values
  cmp = a[1] <=> b[1]
  # Compare keys if the values were equal
  cmp = a[0] <=> b[0] if cmp == 0
  # Return the comparison
  cmp
}

【讨论】:

    【解决方案3】:

    您可以使用 进行比较,并在您首先按值(分数)比较的块中进行比较,如果匹配,然后按键(名称)比较。

    scores = {
      "Alpha" => 0,
      "Beta" => 2,
      "Charlie" => 0,
      "Delta" => 5,
    }
    
    # convert to an array of arrays
    # => [[Alpha, 0], [Beta, 2], [Charlie, 0], [Delta, 5]]
    # then sort by value and, if needed, by key
    scores = scores.to_a.sort! { |a,b|
      cmp = a[1] <=> b[1]             # sort by value (index = 1)
      cmp = a[0] <=> b[0] if cmp == 0 # sort by key (index = 0) if values matched
      cmp
    }.to_h # convert back to a hash
    
    puts scores
    

    或者,如果您想将比较代码提取到一个方法中以便重用/清晰,您可以让它调用该方法。

    # Compare entries from the scores hash.
    # Each entry has a name (key) and score (value) (e.g. ["Alpha", 0].
    # First compare by scores, then by names (if scores match).
    def compare_score_entries(a, b)
      cmp = a[1] <=> b[1]
      cmp = a[0] <=> b[0] if cmp == 0
      return cmp
    end
    
    scores = scores.sort(&method(:compare_score_entries)).to_h
    

    【讨论】:

    • 很好——我在用 JavaScript 思考。我把它改成了==
    【解决方案4】:

    你可以这样排序

    scores = {
      "Charlie" => 0,
      "Delta" => 5,
      "Beta" => 2,
      "Alpha" => 0
    }
    puts scores
      .map{|name, score| [-score, name]}
      .sort
      .zip((0...scores.size).to_a)
      .map{|(score, name), i| "#{i + 1}. #{name}, #{-score} pts"}
    

    注意减分。整数反向排序的技巧。

    【讨论】:

      【解决方案5】:

      你可以试试这个

      scores = {
        'Charlie' => 0,
        'Delta' => 5,
        'Beta' => 2,
        'Alpha' => 0
      }
      
      scores_sorted = scores.sort_by { |_key, value| -value }
      
      scores_sorted.each.with_index(1) do |value, index|
       puts "#{index}. #{value[0]}, #{value[1]} pts"
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-07-25
        • 1970-01-01
        • 1970-01-01
        • 2017-03-21
        • 1970-01-01
        • 2011-02-02
        相关资源
        最近更新 更多