如何按键名字母顺序对Ruby哈希进行排序

36

我正在尝试按字母顺序对哈希进行排序,但似乎没有找到不创建自己的排序类就能完成的方法。我找到了下面的代码可以按值对整数进行排序,我试图对其进行修改,但没有成功。

temp["ninjas"]=36
temp["pirates"]=12
temp["cheese"]=222
temp.sort_by { |key, val| key }

我的目标是按键对哈希进行排序,然后输出值。我将不得不多次使用不同的哈希顺序但相同的值来完成此操作。


1
代码似乎已经按照您的要求执行。您能否编辑问题并包含您期望的输出? - Don Cruickshank
我做出了这样的假设,即您希望输出另一个哈希。如果是这样的话,在问题中看到这一点会很好(那么问题和答案将匹配)。 - Neil Slater
1
你想要什么作为输出?哈希表并不真正排序(自Ruby 1.9+起,它们按插入顺序排序)。你具体想做什么? - Dave Newton
5个回答

57
假设您希望输出为哈希,可以按排序顺序迭代键,那么您已经接近成功了。`Hash#sort_by` 返回一个由数组组成的数组,其中内部数组都包含两个元素。

Ruby的 `Hash` 有一个构造函数可以使用此输出。

请尝试以下内容:

temp = Hash[ temp.sort_by { |key, val| key } ]

更简洁地说

temp = temp.sort_by { |key| key }.to_h
如果您的哈希表中包含混合键类型,这将无法正常工作(例如Ruby不会自动区分StringSymbol),您将收到类似于comparison of Symbol with String failed (ArgumentError)的错误消息。如果是这样,您可以将上述内容修改为:
temp = Hash[ temp.sort_by { |key, val| key.to_s } ] 

解决这个问题的一个方法是使用 to_s 方法将键转换为字符串。但需要注意的是,键仍将保留其原始类型,这可能会导致后续代码中的假设出现问题。此外,大多数内置类都支持 .to_s 方法,因此您可能会从中获得不想要的结果(例如数字键的意外排序顺序或其他意外的类型)。

此外,您可以像这样将键转换为 Strings

temp = Hash[ temp.map { |key, val| [key.to_s, val] }.sort ] 

然而,采用这种方法会丢失有关原始密钥类型的信息,从而无法可靠地引用回原始数据。


我尝试了你的代码,但是出现了以下错误:在'sort_by'中,符号与字符串的比较失败(ArgumentError)。 - Rilcon42
你的哈希表有混合键类型,无论你如何排序都会出现问题。你可以将其改为 temp = Hash[ temp.sort_by { |key, val| key.to_s } ] 来解决这个问题,但要小心,这会将符号键更改为字符串...如果你真的想按排序顺序处理数据,你需要决定一个数据类型作为你的键,并坚持使用它。 - Neil Slater
你介意更新你的答案并注明如何解决问题,以便未来可能不会阅读评论的用户吗?谢谢! - Rilcon42
1
最后一部分不是真的。sort_by 不会改变键,它只是使用块中的值进行排序。传递给 Hash[] 构造函数的参数具有与原始哈希相同的键。 - Kazim Zaidi
@KazimZaidi:谢谢。那个错误已经存在很长时间了……我会重新表述一下,但仍然有一个问题,即按字符串值排序,然后保留原始值的含义是什么。例如,如果键是整数,则哈希表不一定以有用的方式排序。 - Neil Slater

12
sorted_by_key = Hash[original_hash.sort]

通过按键的字母顺序将original_hash的键/值插入,将创建一个新的哈希表。Ruby 2.x哈希表会记住它们的插入顺序,因此如果你枚举它或输出它,这个新的哈希表将按键排序后显示。

如果您以非字母顺序插入更多的元素,则当然无法保证这一点。

此外,这假设原始哈希表键都是可排序/可比较的。


5
“original_hash.sort.to_h”的翻译是将原始哈希表按键排序并返回一个新的哈希表。 - Kevin Hutchinson

6
现在的Ruby哈希表能够记住其插入顺序,但早期的Ruby版本(小于v1.9)不支持。但是,不必对哈希表进行排序,因为基本上哈希表是一种随机访问结构。这意味着可以随时访问所有元素,无论一个元素是第一个还是最后一个,都可以同样地访问它。
这与数组不同,数组就像是一个顺序/文本文件、链表或队列,您必须通过迭代来顺序访问它,此时元素的顺序很重要。
因此,使用哈希表,获取键,对键进行排序,然后遍历键列表或使用values_at立即检索所有值。例如:
hash = {
    'z' => 9,
    'a' => 1
}

sorted_keys = hash.keys.sort # => ["a", "z"]
sorted_keys.each do |k|
  puts hash[k]
end
# >> 1
# >> 9

hash.values_at(*sorted_keys) # => [1, 9]

有些编程语言甚至不能对哈希表进行排序,仅通过已排序的键列表访问它是提取元素按顺序的唯一方式,因此最好不要养成依赖键值对顺序的习惯,而是依赖键。


3
你说散列表排序“没有优势”的陈述只在查找情况下才是正确的。如果你想了解散列表的相关信息(例如,创建散列表内容的md5),一致的顺序是相关和必要的。 - MissingHandle
由于问题特别涉及查找,因此您的评论与此无关。是的,对整个对象创建MD5有时会有帮助,但由于大多数情况下人们都是检索值,所以排序哈希不会有帮助。 - the Tin Man
2
只是想澄清你所说的话,“不要试图对哈希进行排序,因为没有任何优势”,以便那些需要开阔思路学习的新手能够理解。谢谢! - MissingHandle
@theTinMan,当解决同一个问题的方法往往有很多种时,我们很欣赏您的见解。不过,您的评论并没有涵盖我的当前用例:如何迭代按值排序的哈希表(我知道,这不是 OP 的问题,但我仍然很感兴趣)?我使用哈希表来计算对象出现的次数,通过将这些对象用作键并在值中递增计数器。最后,我想迭代按值排序的哈希表。我最终使用了 Hash#sort_by,然后是普通的 Hash#each。不确定是否有更“Ruby”的方法来处理这种情况...您有什么建议吗? - pjvleeuwen
@PaulvanLeeuwen Hash#invert 是按值排序的起点。将结果分配给一个新变量。添加 keyssort,你就快成功了。https://dev59.com/5mgu5IYBdhLWcg3w6bGo - the Tin Man
@PaulvanLeeuwen 还可以参考 https://stackoverflow.com/q/16103164/128421 - the Tin Man

3
除了 Neil Slater的回答,他使用的是Hash#sort_by方法(当在块中输出可比较值时,这种方法很简洁)...
irb(main):001:0> h = { a: 0, b: 5, c: 3, d: 2, e: 3, f:1 }
=> {:a=>0, :b=>5, :c=>3, :d=>2, :e=>3, :f=>1}
irb(main):002:0> h.sort_by { |pair| pair[1] }.to_h
=> {:a=>0, :f=>1, :d=>2, :c=>3, :e=>3, :b=>5}

...或反向变体...

irb(main):003:0> h.sort_by { |pair| pair[1] }.reverse.to_h
=> {:b=>5, :e=>3, :c=>3, :d=>2, :f=>1, :a=>0}

除此之外,还有一种选项是使用 Array#sort 方法,它允许您定义自己的比较规则(例如,按值升序排序,但对于相等的值,则按键降序排序):

irb(main):004:0> h.to_a.sort { |one, other| (one[1] == other[1]) ? other[0] <=> one[0] : one[1] <=> other[1] }.to_h
=> {:a=>0, :f=>1, :d=>2, :e=>3, :c=>3, :b=>5}

这最后一种选择不太简洁,但可以更灵活(例如自定义逻辑来处理混合类型)。


0

您可以创建一个新的空哈希表来保存排序后的哈希数据。遍历返回的数组并将数据加载到新哈希表中以保存排序后的哈希数据。

temp = {}
temp["ninjas"]=36
temp["pirates"]=12
temp["cheese"]=222 
temp = temp.sort_by { |key, val| key }

temp_sorted = {}
temp.each { |sub_arr| temp_sorted[sub_arr[0]] = sub_arr[1] } 
temp = temp_sorted

现在temp等于{"cheese"=>222, "ninjas"=>36, "pirates"=>12}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接