Pp! on a hash with default value?

Following is a example:

h2 = Hash(String, Array(Time::Span)).new([] of Time::Span)

# h2["foo"] = h2["foo"].dup unless h2.has_key?(key)

h2["foo"] << 1.second

pp h2 # => {}

pp h2.keys # => []

pp h2["foo"] # => [00:00:01]

When pp on h2, there is no key “foo” exists, but, when visit it use h2["foo"], i get the correct result, what i expected is, the key “foo” exists after the code h2["foo"] << 1.second is run.

The workaround is, uncomment the line h2["foo"] = h2["foo"].dup, but it’s wired to add a code like this, my current usage need run h2["foo"] << 1.second even on a empty hash no error, did I do something wrong?

This has been explained in the document.
API doc
As an alternative, you can use h2["foo"] += [1.second]

3 Likes

Duplicate of How to append to an Array that is a default Value of a Hash?.

2 Likes

I do a benchmark on all known working solution, @Blacksmoke16 solution is the best on runtime and memory usage

code
require "benchmark"

def meth1
  h2 = Hash(Int32, Array(Time::Span)).new([] of Time::Span)
  (1..1000000).each do |e|
    h2[e] = h2[e].dup unless h2.has_key?(e)
    h2[e] << e.seconds
  end
end

def meth2
  h2 = Hash(Int32, Array(Time::Span)).new([] of Time::Span)
  (1..1000000).each do |e|
    h2[e] += [e.seconds]
  end
end

def meth3
  h2 = Hash(Int32, Array(Time::Span)).new() { |h, k| h[k] = [] of Time::Span }
  (1..1000000).each do |e|
    h2[e] << e.seconds
  end
end

Benchmark.ips do |x|
  x.report("meth1") { meth1 }
  x.report("meth2") { meth2 }
  x.report("meth3") { meth3 }
end

puts Benchmark.memory { meth1 }
puts Benchmark.memory { meth2 }
puts Benchmark.memory { meth3 }
 ╰──➤ $ cr run --release 1.cr
meth1   7.31  (136.75ms) (±17.06%)  139MB/op   1.41× slower
meth2   8.41  (118.96ms) (±11.45%)  170MB/op   1.23× slower
meth3  10.32  ( 96.89ms) (± 9.33%)  139MB/op        fastest
146073600
178070928
146068608