Default Hash values
Back in the final example of #16 Enumerable#each_with_object, we could see Hash.new(0) used to create a Hash with a default value of 0. Let's explore it a bit further.
Default values are returned when accessing keys that don't yet exist in the Hash:
counters = Hash.new(0)
counters["apples"] += 1
counters["bananas"] += 1
counters["apples"] += 1
puts counters
# => {"apples"=>2, "bananas"=>1}
const counters = {};
counters["apples"] = (counters["apples"] || 0) + 1;
counters["bananas"] = (counters["bananas"] || 0) + 1;
counters["apples"] = (counters["apples"] || 0) + 1;
console.log(counters);
// => { apples: 2, bananas: 1 }
If you want to use a mutable object (like an Array or another Hash) as the default value, you must use a block to ensure you generate a new object for each key:
pokemon_abilities = Hash.new { |hash, key| hash[key] = [] }
pokemon_abilities["pikachu"] << "static"
pokemon_abilities["pikachu"] << "lightning-rod"
pokemon_abilities["bulbasaur"] << "overgrow"
pokemon_abilities["bulbasaur"] << "chlorophyll"
puts pokemon_abilities
# => {
# "pikachu"=>["static", "lightning-rod"],
# "bulbasaur"=>["overgrow", "chlorophyll"],
# }
const pokemonAbilities = {};
function addAbility(pokemon, ability) {
if (!pokemonAbilities[pokemon]) {
pokemonAbilities[pokemon] = [];
}
pokemonAbilities[pokemon].push(ability);
}
addAbility("pikachu", "static");
addAbility("pikachu", "lightning-rod");
addAbility("bulbasaur", "overgrow");
addAbility("bulbasaur", "chlorophyll");
console.log(pokemonAbilities);
// => {
// pikachu: ["static", "lightning-rod"],
// bulbasaur: ["overgrow", "chlorophyll"]
// }
I couldn't think of any cleaner approach in JavaScript than to create a dedicated method
A word of caution: if you fall into the trap of passing a mutable object as a default value without using a block, you'll end up with all keys sharing the same object instance:
hash = Hash.new([])
hash["a"] << 1
hash["b"] << 2
puts hash
# => {}
puts hash["asdf"]
# => [1, 2]
😱
So long as you stay clear of this, using default values in Hashes is a powerful way to simplify your code!
History
Simple default values for Hashes were present in Ruby at least as early as version 1.6. The ability to use a block to generate default values was added in Ruby 1.8.
It seems likely that the Smalltalk Dictionary API at:ifAbsentPut: inspired this feature in Ruby, as it provides similar functionality for setting and retrieving a default value based on a block.