【问题标题】:Ruby hash order, uniq methodRuby哈希顺序,uniq方法
【发布时间】:2015-02-15 09:39:07
【问题描述】:

变量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

【问题讨论】:

  • 所以如果fav_food"cookie",那么哈希应该是{"cookie"=>0, "pie"=>0, "cake"=>0}?

标签: ruby


【解决方案1】:

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

如果您需要按顺序检索元素,然后获取键并对列表进行排序,然后迭代该列表,或者使用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]

如果对哈希进行排序,foo.to_a 会以某种方式进行整理,即使在添加了额外的键/值对之后也是如此。相反,它保留在其插入顺序中。基于键的有序散列会将a:-1 移动到第一个元素,就像基于值的有序散列一样。

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

也许您正在考虑 Java,它具有 SortedMap,并提供了这些功能:

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

同样,由于 Ruby 的 Hash 不会超出其插入顺序进行排序,因此我们没有这些功能。

【讨论】:

  • RE 你的第一段:散列 are 有序,特别是因为这是一个常见的要求,并且散列在 Ruby 中占据了如此多的角色。即使其他语言不支持这种行为,在 Ruby 中对哈希进行排序也没有什么坏处。
  • 哈希只记住它们的插入顺序,但这与任何形式的排序规则都没有相似之处。请参阅添加的示例。
【解决方案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)

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

我并不真正提倡这一点(应该采纳铁皮人的建议),但如果您使用的是 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 添加了“double splat”或“keyword splat”运算符,类似于 Array 中的 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 }

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

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

【讨论】:

  • 如果您使用 Ruby 版本 2.1.0 到 2.1.2,请小心这个愚蠢的 Ruby 技巧。有一个错误,您双重喷射的哈希将被破坏性修改:cf。 stackoverflow.com/questions/23282342/…
  • @Jordan,我错过了您的编辑并发布了相同的merge 解决方案,随后我将其删除。我建议你在你的回答中把它放在前面和中心;当然,这是最好的方法。或者,将您的 merge 解决方案设为单独的答案。
  • 我建议做一个小改动:{ fav_food => "Bananas! But, yes, they have no bananas, they have no bananas today!"}.merge(food_qty)。它同样有效,而且更有趣。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多