【问题标题】:How to preserve alphabetical order of keys when sorting a hash by the value按值对哈希进行排序时如何保留键的字母顺序
【发布时间】:2015-06-06 18:52:40
【问题描述】:

这里是初学者。我的第一个问题。对我放轻松。

给定以下哈希:

pets_ages = {"Eric" => 6, "Harry" => 3, "Georgie" => 12, "Bogart" => 4, "Poly" => 4,
        "Annie" => 1, "Dot" => 3}

并运行以下方法:

pets_ages.sort {|x, y| x[1] <=> y[1]}.to_h

返回以下内容:

{
      "Annie" => 1,
        "Dot" => 3,
      "Harry" => 3,
       "Poly" => 4,
     "Bogart" => 4,
       "Eric" => 6,
    "Georgie" => 12
}

您会注意到哈希按值很好地排序,正如预期的那样。我想改变的是键的顺序,以便在打平的情况下它们保持字母顺序。请注意,“Dot”和“Harry”在这方面是正确的,但由于某种原因,“Poly”和“Bogart”不是。我的理论是,在平局的情况下,它会自动按长度对键进行排序,而不是按字母顺序排序。我该如何改变呢?

【问题讨论】:

  • 我会小心地期望键使用哈希以给定的顺序排列。除非您确定使用 ActiveSupport::OrderedHash 或其他确保密钥排序的库。散列应该是基于键的查找。不应该有订单。如果您需要订单,请将其保留为数组。
  • pets_ages.sort_by { |k, v| [v, k] }pets_ages.sort_by(&amp;:reverse) 应该可以工作
  • @PhilipHallstrom 在 Ruby 中,哈希从 1.9+ 开始明确排序。
  • 很高兴知道这一点。谢谢!

标签: ruby sorting hash key-value alphabetical


【解决方案1】:

在许多语言中,Hashes/Dicts 没有排序,因为它们是如何在幕后实现的。 Ruby 1.9+ 足以保证订购。

您可以一次性完成 - Ruby 允许您按任意标准进行排序。

# Given
pets_ages = {"Eric" => 6, "Harry" => 3, "Georgie" => 12, "Bogart" => 4, "Poly" => 4, "Annie" => 1, "Dot" => 3}

# Sort pets by the critera of "If names are equal, sort by name, else, sort by age"
pets_ages.sort {|(n1, a1), (n2, a2)| a1 == a2 ? n1 <=> n2 : a1 <=> a2 }.to_h

# => {"Annie"=>1, "Dot"=>3, "Harry"=>3, "Bogart"=>4, "Poly"=>4, "Eric"=>6, "Georgie"=>12}

Hash#sort 将返回一个由[k, v] 对组成的数组,但是这些k, v 对可以按您想要的任何标准在一次传递中进行排序。一旦我们得到排序的对,我们将其转换回带有Array#to_h(Ruby 2.1+)的哈希,或者您可以在早期版本中使用Hash[sorted_result],正如 Beartech 指出的那样。

您可以在排序块中变得任意复杂;如果您熟悉 Javascript 排序,那么 Ruby 在这里的工作原理实际上是一样的。 &lt;=&gt; 方法根据对象之间的比较方式返回 -1、0 或 1。 #sort 只期望其中一个返回值,它告诉它两个给定值如何相互关联。如果您不想使用,甚至根本不必使用 &lt;=&gt; - 这样的东西相当于更紧凑的形式:

pets_ages.sort do |a, b|
  if a[1] == b[1]
    if a[0] > b[0]
      1
    elsif a[0] < b[0]
      -1
    else
      0
    end
  else
    if a[1] > b[1]
      1
    elsif a[1] < b[1]
      -1
    end
  end
end

如您所见,只要您始终返回集合中的某些内容 (-1 0 1),您的排序函数就可以为所欲为,因此您可以随意组合它们。然而,这种冗长的形式在 Ruby 中几乎不需要,因为超级方便的 运算符!

不过,正如 Stefan 所指出的,您在这里有一个很大的捷径:Array# 对compare each entry between the compared arrays 来说已经足够好了。这意味着我们可以这样做:

pets_ages.sort {|a, b| a.reverse <=> b.reverse }.to_h

这需要每个 [k, v] 对,将其反转为 [v, k],并使用 Array# 进行比较。由于您需要对比较的每个 [k, v] 对执行相同的操作,因此您可以使用 #sort_by 进一步缩短它

pets_ages.sort_by {|k, v| [v, k] }.to_h

它的作用是对每个哈希条目,将键和值传递给块,块的返回结果用于将这个 [k, v] 对与其他条目进行比较。由于将 [v, k] 与另一个 [v, k] 对进行比较会给我们想要的结果,因此我们只返回一个由 [v, k] 组成的数组,该数组通过 sort_by 收集和排序原始的 [k, v] 对.

【讨论】:

  • 你可以把最后一个写成pets_ages.sort_by(&amp;:reverse).to_h
【解决方案2】:

正如 Philip 所指出的,哈希并不是为了保持顺序,尽管我认为在最新的 Ruby 中它们可能会这样。但是,假设他们没有。这是一个基于数组的解决方案,然后可以重新散列:

编辑这里是单行:

 new_pets_ages = Hash[pets_ages.sort.sort_by {|a| a[1]}]

上一个答案:

pets_ages = {"Eric" => 6, "Harry" => 3, "Georgie" => 12, "Bogart" => 4, "Poly" => 4,
    "Annie" => 1, "Dot" => 3}

arr = pets_ages.sort

# [["Annie", 1], ["Bogart", 4], ["Dot", 3], ["Eric", 6], ["Georgie", 12],
#    ["Harry", 3], ["Poly", 4]]

new_arr = arr.sort_by {|a| a[1]}

#[["Annie", 1], ["Dot", 3], ["Harry", 3], ["Bogart", 4], ["Poly", 4], ["Eric", 6],
#    ["Georgie", 12]]

最后得到一个哈希值:

h = Hash[new_arr]

#{"Annie"=>1, "Dot"=>3, "Harry"=>3, "Bogart"=>4, "Poly"=>4, "Eric"=>6, 
#   "Georgie"=>12}

因此,当我们对哈希进行排序时,它会为我们提供一个数组数组,其中的项目按原始键排序。然后我们按每个数组的第二个值对数组数组进行排序,因为它是惰性排序,所以它只在需要时移动它们。然后我们可以将其发送回散列。我确信有一种技巧可以在一行中进行两次排序,但这看起来很简单。

【讨论】:

    【解决方案3】:

    正如您已经知道您使用过的几种用于排序的 ruby​​ 方法。因此,我不会详细解释它,而是为您保持非常简单的一条线。这是你的答案:

    pets_ages.sort.sort_by{|pets| pets[1]}.to_h
    

    谢谢

    【讨论】:

      猜你喜欢
      • 2015-02-07
      • 2011-12-09
      • 2013-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-20
      • 2014-04-13
      相关资源
      最近更新 更多