Ruby:多维哈希中来自Hash.keys的意外结果

时间:2015-09-30 17:31:04

标签: ruby

我有以下小序列,这对我没有意义:

irb(main):001:0> h = {}
=> {}
irb(main):002:0> h.default = {}
=> {}
irb(main):003:0> h["foo"]["bar"] = 6
=> 6
irb(main):004:0> h.length
=> 0
irb(main):005:0> h.keys
=> []
irb(main):006:0> h["foo"]
=> {"bar"=>6}

第5步是如何返回一个空的键列表,第4步表示h的长度是0,但我可以在步骤6中看到"foo"是一个有效密钥并具有关联值。我希望keys返回["foo"]length返回1

我误解了什么?注意这是Ruby 1.9.3p0

另请注意,此操作正常:

irb(main):001:0> h = {}
=> {}
irb(main):002:0> h["foo"] = {}
=> {}
irb(main):003:0> h["foo"]["bar"] = 6
=> 6
irb(main):004:0> h.length
=> 1
irb(main):005:0> h.keys
=> ["foo"]
irb(main):006:0> h["foo"]
=> {"bar"=>6}

唯一的区别是使用Hash.default来设置默认值并跳过h["foo"]的显式初始化。这是一个错误吗?

3 个答案:

答案 0 :(得分:2)

*Main> freeVars (Var "e") ["e"] *Main> freeVars (Lam "e" (Var "x")) ["x"] *Main> freeVars (Lam "e" (Var "e")) [] *Main> freeVars (App (Lam "e" (Var "x")) (Nat 3)) ["x"] *Main> freeVars (App (Lam "e" (Var "e")) (Nat 3)) [] 表示在缺少密钥时返回的默认值为$(function () { $('#container-speed').highcharts(Highcharts.merge({}, { "colors": [ "#4FB04F" ], "yAxis": { "min": 0, "max": 200500500, "tickPositions": [ 0, 150500500, 200500500 ], "labels": { "y": 24, "style": { "color": "#0e4d5c" } }, "lineWidth": 0, "minorTickInterval": null, "tickWidth": 0, "title": { "enabled": false } }, "series": [ { "name": "Speed", "data": [ 150500500 ] } ], "chart": { "type": "solidgauge", "height": 190 }, "title": { "text": "" }, "pane": { "center": [ "50%", "125%" ], "size": "240%", "startAngle": -70, "endAngle": 70, "background": { "backgroundColor": "#0e4d5c", "borderWidth": 0, "innerRadius": "81%", "outerRadius": "92%", "shape": "arc" } }, "tooltip": { "enabled": false }, "plotOptions": { "solidgauge": { "innerRadius": "88%", "radius": "85%", "dataLabels": { "enabled": false } } }, "credits": { "enabled": false } })); }); 。此外,将返回此特定哈希实例。

h.default = {}行查找键{},当它无法找到时,它会获取默认值h["foo"]["bar"] = 6并在其中插入键foo }和值{}。在此之后哈希仍然是空的。

这就是您看到这些结果的原因。

您也可以使用default_proc来设置密钥。

答案 1 :(得分:1)

您只需设置默认值,如果未找到任何值,将返回该值。这并没有改变没有为h["foo"]分配值的事实。 {"bar"=>6}将是未找到的任何密钥的值。

h = {}
h.default = {}      # => {}
h["foo"]["bar"] = 6 # => 6
h["foo"]            # => {"bar"=>6}
h["baz"]            # => {"bar"=>6}

如果你想要一个返回的哈希并将缺失键的值设置为空哈希,你必须这样做:

h = Hash.new { |hash, key| hash[key] = {} }
h["foo"]["bar"] = 6  # => 6
h                    # => {:foo=>{:bar=>6}}

答案 2 :(得分:0)

默想:

h={} #=> {}
h['foo'] #=> nil

由于['foo']不存在,尝试访问它会导致结果为零。这意味着尝试访问'foo'的子哈希将失败:

h['foo']['bar']

NoMethodError: undefined method `[]' for nil:NilClass

这相当于使用:

nil['bar']

NoMethodError: undefined method `[]' for nil:NilClass

我们可以通过定义h['foo']是什么来解决这个问题:

h['foo'] = {} #=> {}

它是定义的,所以当我们请求它的值时,我们得到一个空哈希:

h['foo'] #=> {}

现在h['foo']['bar']将返回预期的内容,为零:

h['foo']['bar'] # => nil

此时,h['foo']['bar']的作业将有意义:

h['foo']['bar'] = 6 

看着h表明我们有哈希哈希:

h # => {"foo"=>{"bar"=>6}}

使用h.default = {}尝试解决h['foo']返回nil的问题:

h = {}
h.default = {}
h # => {}
h['foo'] # => {}

而且,是的,它确实如此,但它也引入了一个主要问题。我们希望哈希值能够一致地返回密钥的值,但是h.default = {}已经设置了问题。通常我们希望哈希行为如下:

h = {}
h # => {}
h['foo'] = {}
h['bar'] = {}
h # => {"foo"=>{}, "bar"=>{}}
h['foo']['baz'] = 1
h['bar']['baz'] = 2
h # => {"foo"=>{"baz"=>1}, "bar"=>{"baz"=>2}}
h['foo'].object_id # => 70179045516300
h['bar'].object_id # => 70179045516280
h['foo']['baz'].object_id # => 3
h['bar']['baz'].object_id # => 5

使用h.default = {}打破了这一点。我们知道存在h['foo'],我们希望h['bar']不存在或指向内存中不同的键/值对,但它不会:

h = {}
h.default = {}
h # => {}
h['foo']['baz'] = 1
h['bar']['baz'] = 2
h # => {}
h['foo'].object_id # => 70142994238340
h['bar'].object_id # => 70142994238340
h['foo']['baz'] # => 2
h['bar']['baz'] # => 2
h['foo']['baz'].object_id # => 5
h['bar']['baz'].object_id # => 5

真的不希望这种情况发生,因为它是完全陌生和意想不到的行为,这将使你的代码以奇怪和奇妙的方式打破,再加上让你讨厌维护/调试你的其他人代码。

整个问题是h.default = {}的使用,因为它并不意味着“总是使用新的哈希”,这意味着“总是使用这个哈希”。

相反,像这样的东西可以解决问题:

h = Hash.new{ |hash,key| hash[key] = Hash.new(&hash.default_proc) }

h['foo']['baz'] = 1
h['bar']['baz'] = 2
h # => {"foo"=>{"baz"=>1}, "bar"=>{"baz"=>2}}
h['foo'].object_id # => 70109399098340
h['bar'].object_id # => 70109399098320
h['foo']['baz'] # => 1
h['bar']['baz'] # => 2
h['foo']['baz'].object_id # => 3
h['bar']['baz'].object_id # => 5

此时,散列应该按预期运行,没有人会想要追捕你。这个解决方案有一些问题,但它是一个更好的构建块。有关详细信息,请参阅“Dynamically creating a multi-dimensional hash in Ruby”。