Ruby哈希顺序,uniq方法

时间:2014-12-16 22:51:36

标签: ruby

变量fav_food可以是"pie""cake""cookie",它们将由用户输入。但是,我希望我的food_qty哈希将fav_food列为第一个密钥。

因此,我出来了

food_order = ([fav_food] + food_qty.keys).uniq

但是,有更好的方法吗?

food_qty = {"pie" => 0, "cake" => 0, "cookie" => 0}
# make the fav_food listed first in the hash
food_order = ([fav_food] + food_qty.keys).uniq

2 个答案:

答案 0 :(得分:4)

为什么要将特定的键/值对放在哈希中?哈希不需要订购,因为您可以随时直接访问任何元素而无需任何额外费用。

如果您需要检索订单中的元素,然后获取密钥并对该列表进行排序,然后迭代该列表,或使用values_at

foo = {
  'z' => 1,
  'a' => 2
}

foo_keys = foo.keys.sort # => ["a", "z"]

foo_keys.map{ |k| foo[k] } # => [2, 1]
foo.values_at(*foo_keys) # => [2, 1]

哈希记得他们的插入顺序,但你不应该依赖它;如果您稍后插入某些内容并且其他语言不支持,则对哈希进行排序并不会有所帮助。相反,您可以根据需要对键进行排序,并使用该列表来检索值。

如果你想强制一个键成为第一个,那么首先检索它的值,然后考虑这个:

foo = {
  'z' => 1,
  'a' => 2,
  'w' => 3,
}

foo_keys = foo.keys # => ["z", "a", "w"]
foo_keys.unshift(foo_keys.delete('w')) # => ["w", "z", "a"]

foo_keys.map{ |k| foo[k] } # => [3, 1, 2]
foo.values_at(*foo_keys) # => [3, 1, 2]

如果您想要一个排序的键列表,其中一个键被强制到一个位置:

foo_keys = foo.keys.sort # => ["a", "w", "z"]
foo_keys.unshift(foo_keys.delete('w')) # => ["w", "a", "z"]

foo_keys.map{ |k| foo[k] } # => [3, 2, 1]
foo.values_at(*foo_keys) # => [3, 2, 1]

  

RE你的第一段:虽然命令哈希,特别是因为这是一个常见的要求,哈希在Ruby中填充了很多角色。即使其他语言不支持这种行为,依赖于Ruby中的哈希也没有任何损害。

没有排序,就像排序一样,而是记住他们的插入顺序。来自the documentation

  

哈希按照插入相应键的顺序枚举其值。

这很容易测试/证明:

foo = {z:0, a:-1} # => {:z=>0, :a=>-1}
foo.to_a # => [[:z, 0], [:a, -1]]
foo[:b] = 3
foo.merge!({w:2})
foo # => {:z=>0, :a=>-1, :b=>3, :w=>2}
foo.to_a # => [[:z, 0], [:a, -1], [:b, 3], [:w, 2]]
foo.keys # => [:z, :a, :b, :w]
foo.values # => [0, -1, 3, 2]

如果订购了散列,{@ 1}}将以某种方式进行整理,即使在添加其他键/值对之后也是如此。相反,它仍然在其插入顺序中。基于键的有序散列会将foo.to_a移动为第一个元素,就像基于值的有序散列一样。

如果哈希是有序的,并且,如果它很重要,我们可以通过某种方式告诉哈希它的排序是什么,升序或降序,或者基于键或值具有某种特殊顺序。相反,我们没有这些东西,只有从Enumerable继承的a:-1sort方法,两者都将哈希转换为数组并对其进行排序并返回数组,因为数组可以从订单中受益。

也许你正在考虑拥有SortedMap的Java,并提供这些功能:

  

进一步提供其键的总排序的Map。地图是根据其键的自然顺序排序的,或者是通常在排序地图创建时提供的比较器。迭代有序映射的集合视图(由entrySet,keySet和values方法返回)时会反映此顺序。提供了一些额外的操作来利用订购。

同样,因为Ruby的Hash没有排序超出其插入顺序,所以我们没有这些功能。

答案 1 :(得分:2)

您可以使用Hash#merge

food_qty = { "pie" => 0, "cake" => 0, "cookie" => 0 }
fav_food = "cookie"

{ fav_food => nil }.merge(food_qty)
# => { "cookie" => 0, "pie" => 0, "cake" => 0 }

这是有效的,因为Hash#merge首先复制原始哈希,然后,对于已存在的密钥(如"cookie"),更新值 - 这将保留现有密钥的顺序。如果不清楚,上述内容相当于:

{ "cookie" => nil }.merge("pie" => 0, "cake" => 0, "cookie" => 0)

编辑:以下是我的原始答案,在我意识到我也偶然发现了上面的“真实”答案之前。

我并不是真的提倡这个(应该采用Tin Man的建议),但是如果你使用Ruby 2.0+,我会提出以下愚蠢的Ruby技巧:

food_qty = { :pie => 0, :cake => 0, :cookie => 0}
fav_food = :cookie

{ fav_food => nil, **food_qty }
# => { :cookie => 0, :pie => 0, :cake => 0 }

这是有效的,因为Ruby 2.0添加了“双splat”或“关键字splat”运算符,作为数组中splat的类似物:

arr = [ 1, 2, *[3, 4] ] # => [ 1, 2, 3, 4 ]
hsh = { a: 1, b: 2, **{ c: 3 } } # => { :a => 1, :b => 2, :c => 3 }

...但它似乎做了反向合并(一个ActiveSupport的Hash#reverse_merge),将“外部”哈希合并到“内部”。

{ a: 1, b: 2, **{ a: 3 } }        # => { :a => 1, :b => 2 }
# ...is equivalent to:
{ a: 3 }.merge( { a: 1, b: 2 } )  # => { :a => 1, :b => 2 }

实现双splat以支持Ruby 2.0中的keyword arguments,这可能是它只有在“内部”Hash的键都是符号时才有效的原因。

就像我说的,我不建议实际做,但我觉得它很有趣。