【问题标题】:Is order of a Ruby hash literal guaranteed?Ruby 哈希文字的顺序是否得到保证?
【发布时间】:2015-10-03 19:14:00
【问题描述】:

Ruby,从 v1.9 开始,在遍历哈希时支持确定性顺序;首先添加的条目将首先返回。

这是否适用于文字,即{ a: 1, b: 2 } 总是在 b 之前产生 a?

我对 Ruby 2.1 (MRI) 做了一个快速实验,它实际上是一致的,但是该语言在多大程度上保证了这一点适用于所有 Ruby 实现?

【问题讨论】:

  • 包括我在内的读者都在问自己:“哈希键的顺序与它们是什么类型的对象有什么关系?”。
  • h = { a: 1, b: 2 }h = { }; h[:a] = 1; h[:b] = 2 相同,所以是的。找到一个说明这是另一回事的规范。
  • @muistooshort 这是你在没有引用任何证据的情况下做出的假设。
  • 去找一个 Ruby 规范,我会指出相关部分。
  • @muistooshort:您可以通过猴子修补Hash 并将其[]= 方法替换为记录其执行的方法来轻松测试您的假设,您将看到这两种形式绝对是不等效。

标签: ruby loops sorting hash literals


【解决方案1】:

来自documentation

哈希按照对应键的顺序枚举它们的值 已插入。

【讨论】:

  • 是的,正如问题所提到的,但我指的是 literal 表示法。
  • 文字符号没有什么不同/特别之处。它将它们(按提供的顺序)添加到哈希中(因此将以相同的顺序枚举),请参阅github.com/ruby/ruby/blob/trunk/hash.c#L550-L633
  • 这是一个 Ruby 实现。我不知道对此有任何测试。无论如何都赞成代码参考。
  • 为什么 literal 符号会有所不同?它不像文字符号创建不同类型的散列,它仍然是散列。
  • Ruby 解析器必须解析文字。虽然最明显的方法是从上到下,但可能有许多与优化相关的原因导致它可以以不同的顺序进行。此外,Ruby 1.9 的顺序规则适用于 Ruby 代码;它不一定适用于 Ruby 的内部可能对哈希执行的操作(即,即使 Ruby 从上到下插入,也不意味着保留顺序)。
【解决方案2】:

有几个位置可以指定,即一些被认为是“Ruby 语言规范”的东西:

ISO 规范没有提到任何关于Hash 排序的内容:它的编写方式是所有现有的 Ruby 实现都自动符合它,而无需更改,即它被编写为描述性的 的当前 Ruby 实现,而不是 规范的。在编写规范时,这些实现包括 MRI、YARV、Rubinius、JRuby、IronRuby、MagLev、MacRuby、XRuby、Ruby.NET、Cardinal、tinyrb、RubyGoLightly、SmallRuby、BlueRuby 等。特别感兴趣的是 MRI(only 实现 1.8)和 YARV(only 实现 1.9(当时)),这意味着规范只能指定以下行为1.8 和 1.9 通用,Hash 排序不是。

RubySpec 项目被其开发人员放弃,因为 ruby​​ 核心开发人员和 YARV 开发人员从未认识到它。但是,它确实(隐含)specify that Hash literals are ordered left-to-right

new_hash(1 => 2, 4 => 8, 2 => 4).keys.should == [1, 4, 2]

这是Hash#keys 的规范,但是,其他规范测试Hash#valuesHash#keys 具有相同的顺序,Hash#each_valueHash#each_key 具有相同的顺序,并且Hash#each_pair 和@ 987654340@也有同样的顺序。

我在the YARV testsuite 中找不到任何指定保留顺序的内容。事实上,我在那个测试套件中根本找不到任何关于排序的信息,恰恰相反:测试会根据排序花费很长时间来避免

Flanagan/matz 的书在 9.5.3.6 Hash 迭代器部分中隐含地指定了 Hash 文字顺序。首先,它使用与文档大致相同的公式:

然而,在 Ruby 1.9 中,哈希元素按其插入顺序进行迭代,[…]

然后它继续:

[…],即以下示例中显示的顺序:

在这些例子中,它实际上使用了文字:

h = { :a=>1, :b=>2, :c=>3 }

# The each() iterator iterates [key,value] pairs
h.each {|pair| print pair }    # Prints "[:a, 1][:b, 2][:c, 3]"

# It also works with two block arguments
h.each do |key, value|                
  print "#{key}:#{value} "     # Prints "a:1 b:2 c:3" 
end

# Iterate over keys or values or both
h.each_key {|k| print k }      # Prints "abc"
h.each_value {|v| print v }    # Prints "123"
h.each_pair {|k,v| print k,v } # Prints "a1b2c3". Like each

his comment@mu is too short 中提到

h = { a: 1, b: 2 }h = { }; h[:a] = 1; h[:b] = 2 相同

another comment那个

没有任何意义

很遗憾,事实并非如此:

module HashASETWithLogging
  def []=(key, value)
    puts "[]= was called with [#{key.inspect}] = #{value.inspect}"
    super
  end
end

class Hash
  prepend HashASETWithLogging
end

h = { a: 1, b: 2 }
# prints nothing

h = { }; h[:a] = 1; h[:b] = 2
# []= was called with [:a] = 1
# []= was called with [:b] = 2

因此,取决于您如何解释书中的那一行以及您如何判断该书的“规范”,是的,文字的顺序有保证的。

【讨论】:

  • 感谢您的详细回答。
  • 如果您坚持挑选 nits,它们在功能上是等效的。哈希文字很可能在 MRI 中用 C 语言处理,以避免一遍又一遍地调用 []= 的开销;就订购而言,它们是等效的,这就是最重要的。考虑到哈希是有序的,对哈希文字的其他处理没有任何意义。
猜你喜欢
  • 2021-01-27
  • 2014-09-29
  • 2011-01-04
  • 1970-01-01
  • 2015-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多