Why is only one value in my hash being changed? - ruby
I'm making a simple RPG as a learning project, and am having an issue with part of the character creator.
This code should determine what skill string is assigned to player[:caste][:skill] and player[:sub][:skill], then increase each respective skill's value in player[:skills] by 2. This code should work regardless of what string is assigned to player[:caste][:skill] and player[:sub][:skill], as long as it is equal to player[:skills].to_s.
Currently, it is only applying the change to player[:skills][:endurance] but not player[:skills][:athletics].
player = {
caste: {skill: "athletics"},
sub: {skill: "endurance"},
skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 0, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0},
}
player[:skills] = player[:skills].map do |skill, mod|
[skill, (mod += 2 if skill.to_s == player[:caste][:skill])]
[skill, (mod += 2 if skill.to_s == player[:sub][:skill])]
end.to_h
In other words, my code is returning the following player[:skills] hash:
skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 2, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0}
but I want it to return:
skills: {acrobatics: 0, athletics: 2, engineering: 0, endurance: 2, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0}
Please let me know if there is a simpler way to do this. I've also tried the following:
player[:skills] = player[:skills].map do |skill, mod|
[skill, (mod += 2 if skill.to_s == (player[:caste][:skill] || player[:sub][:skill]))]
end.to_h
which only affects the skill found in player[:caste][:skill].
When I run your code I get this as the result.
{:acrobatics=>nil, :athletics=>nil, :engineering=>nil, :endurance=>2, :heal=>nil, :history=>nil, :influence=>nil, :insight=>nil, :magicka=>nil, :perception=>nil, :riding=>nil, :stealth=>nil, :streetwise=>nil, :thievery=>nil}
That's because map returns last statement executed. In addition you actually only set a value for skill when it's matches the sub skill otherwise, it is set to nil.
So whats happening in your code is that each iteration is returning the following which is the result of the last statement in the block passed into map.
[:acrobatics, nil]
[:athletics, nil]
[:engineering, nil]
[:endurance, 2]
[:heal, nil]
[:history, nil]
[:influence, nil]
[:insight, nil]
[:magicka, nil]
[:perception, nil]
[:riding, nil]
[:stealth, nil]
[:streetwise, nil]
[:thievery, nil]
The final result being an array that looks like this.
[[:acrobatics, nil], [:athletics, nil], [:engineering, nil], [:endurance, 2], [:heal, nil], [:history, nil], [:influence, nil], [:insight, nil], [:magicka, nil], [:perception, nil], [:riding, nil], [:stealth, nil], [:streetwise, nil], [:thievery, nil]]
Which is finally mapped to a new hash
{:acrobatics=>nil, :athletics=>nil, :engineering=>nil, :endurance=>2, :heal=>nil, :history=>nil, :influence=>nil, :insight=>nil, :magicka=>nil, :perception=>nil, :riding=>nil, :stealth=>nil, :streetwise=>nil, :thievery=>nil}
The reason you get all those nil's is because in your statements the result of the case were the if statement is not true is nil.
For example:
[skill (mod += 2 if skill.to_s == player[:caste][:skill])]
will return [the_skill, nil] for the cases were skill.to_s == player[:caste][:skill] is not true
To see what's happening try this in irb.
x = 0
=> 0
x += 1 if false
=> nil
x += 1 if true
=> 1
You could get past that using something like this.
[skill, skill.to_s == player[:caste][:skill] ? mod + 2 : mod ]
or using the above example:
x = 0
=> 0
x = false ? x + 1 : x
=> 0
x = true ? x + 1 : x
=> 1
The following modified version of your code should work.
player[:skills] = player[:skills].map do |skill, mod|
[skill, skill.to_s == player[:caste][:skill] || skill.to_s == player[:sub][:skill] ? mod + 2 : mod ]
end.to_h
However, here is a slightly more verbose, but hopefully much easier to follow way to accomplish what you want to do and allows for added modifications in the future with out the code getting too confusing.
player = {
caste: {skill: "athletics"},
sub: {skill: "endurance"},
skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 0, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0},
}
player_caste_skill = player[:caste][:skill]
player_sub_skill = player[:sub][:skill]
current_skills = player[:skills]
updated_skills = {}
current_skills.each_pair do |skill, prev_value|
new_value = prev_value
case skill.to_s
when player_caste_skill, player_sub_skill
new_value = prev_value + 2
when "some_other_skill"
new_value = prev_value + 3
end
updated_skills[skill] = new_value
end
puts current_skills
puts updated_skills
I'd set a default value (Hash#default) to the player[:skill] hash, just to avoid errors in case of missing key (it adds the key!!), allowing to add also a new key without the need to initialise to 0 each skill.
player[:skills].default = 0
Then scan the keys you need to increment in just one liner:
[:caste, :sub].each { |key| player.dig(key, :skill).to_sym.then { |skill| player[:skills][skill] += 2 } }
Thanks to the initialisation, your player can also be
player = {
caste: {skill: "athletics"},
sub: {skill: "endurance"},
skills: {}
}
Returning a result like:
player #=> {:caste=>{:skill=>"athletics"}, :sub=>{:skill=>"endurance"}, :skills=>{:athletics=>2, :endurance=>2}}
Where:
player[:skills][:whatever] #=> 0
I would iterate through defined skills rather than through skill values.
player.
map { |_, h| h[:skill] }.
compact.
map(&:to_sym).
each { |skill| player[:skills][skill] += 2 }
Now player is updated accordingly, as you might check by exaimning player with p player or like.
Change the code to be something like this:
player = {
caste: {skill: "athletics"},
sub: {skill: "endurance"},
skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 0, heal: 0,
history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0,
stealth: 0, streetwise: 0, thievery: 0},
}
player[:skills] = player[:skills].map do |skill, mod|
[skill, (mod += 2 if [player[:caste][:skill], player[:sub][:skill]].include?
(skill.to_s))]
end.to_h
The reason your code didn't work because map return the last line as a result for the current iteration, So in the athletics case the last line which is
[skill, (mod += 2 if skill.to_s == player[:sub][:skill])]
will be false which will be nil that's why only endurance case works.
hope it helps.
Related
Prepare the Bunnies Escape - Foobar
I've been at this for a while and for the life of me I cannot figure out why I cannot pass test cases 4 and 5. My code is below, including my own custom test cases that all execute and pass in under 5ms. Basically I added a third dimension to each node's position that represents whether a wall has already been traversed or not. When analyzing each current node's neighbor, if it's a wall and the current node has a zero for its third coordinate, then moving to the wall and to a 1 on the third coordinate becomes an option. On paper, it works great. In my own IDE, it works great. I'm starting to wonder if there's something in here that's Python 3 and not working correctly in foobar or something. I'd appreciate any help. class Node(): def __init__(self, position): self.position = position self.gCost = 1 self.hCost = 0 self.fCost = 0 def __eq__(self, other): return self.position == other.position def solution(map): startNode = Node((0, 0, 0)) endNode = Node((len(map[0]) - 1, len(map) - 1, 0)) openList = [startNode] closedList = [] while openList: currentNode = openList[0] currentIndex = 0 for i in range(len(openList)): if openList[i].fCost < currentNode.fCost: currentNode = openList[i] currentIndex = i openList.pop(currentIndex) closedList.append(currentNode) if currentNode.position[0] == endNode.position[0] and currentNode.position[1] == endNode.position[1]: return currentNode.gCost for offset in [(1, 0), (-1, 0), (0, 1), (0, -1)]: neighborPosition = (currentNode.position[0] + offset[0], currentNode.position[1] + offset[1], currentNode.position[2]) if neighborPosition[0] < 0 or neighborPosition[0] >= len(map[0]) or neighborPosition[1] < 0 or neighborPosition[1] >= len(map): continue if map[neighborPosition[0]][neighborPosition[1]] == 1: if currentNode.position[2] == 1: continue neighborPosition = (neighborPosition[0], neighborPosition[1], 1) neighbor = Node(neighborPosition) if neighbor in closedList: continue if neighbor in openList: openNodeIndex = openList.index(neighbor) if openList[openNodeIndex].gCost < currentNode.gCost + 1: continue openList.pop(openNodeIndex) openList.append(neighbor) else: openList.append(neighbor) neighbor.gCost = currentNode.gCost + 1 neighbor.hCost = endNode.position[0] - neighbor.position[0] + endNode.position[1] - neighbor.position[1] neighbor.fCost = neighbor.gCost + neighbor.hCost return -1 import time start = time.time() map1 = [[0, 0, 0, 1], [1, 0, 0, 1], [1, 0, 0, 0], [0, 0, 0, 0]] sol1 = solution(map1) print("Result: ", sol1, "Expected: ", 7) map2 = [[0,1,0,0,0], [0,1,0,1,0], [0,1,0,1,0], [0,1,0,1,0], [0,0,0,1,0]] sol2 = solution(map2) print("Result: ", sol2, "Expected: ", 9) map3 = [[0,0,0,0,0,0,1,0,0,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,1,0,1,0], [0,0,0,0,1,0,0,0,1,0]] sol3 = solution(map3) print("Result: ", sol3, "Expected: ", 19) map4 = [[0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 1], [0, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0]] sol4 = solution(map4) print("Result: ", sol4, "Expected: ", 11) map5 = [[0, 1, 1, 0], [0, 0, 0, 1], [1, 1, 0, 0], [1, 1, 1, 0]] sol5 = solution(map5) print("Result: ", sol5, "Expected: ", 7) map6 = [[0,1,0], [0,1,0], [0,1,0]] sol6 = solution(map6) print("Result: ", sol6, "Expected: ", 5) map7 = [[0,1,1,0,0,0,0,1,0,1,0,0,0,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,0,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,1,1,0,1,1,0,1,0,1,0,1,1,0,1,1,0,1,1,0], [0,0,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,1,1,0]] sol7 = solution(map7) print("Result: ", sol7, "Expected: ", 123) map8 = [[0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0],[0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0],[0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0],[0,1,0,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1],[0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0],[1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1],[0,1,0,1,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0],[0,1,0,1,0,1,1,1,0,1,1,0,1,1,1,1,1,1,0,1],[0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0],[0,1,0,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1],[0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0,0,0,0,0],[0,1,0,1,0,1,0,1,0,0,0,1,1,1,0,1,1,1,1,1],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0],[1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1,1],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0],[0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,0,0,1,0,0],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,1,1,1],[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,0,1,1,0,0]] sol8 = solution(map8) print("Result: ", sol8, "Expected: ", 89) end = time.time() print("Time: ", end - start) Edit: Quick update - converted the closedList to a set and now it solves my test cases in under 1ms, still fails Google's test cases 4 and 5 though.
So I figured it out. The line if map[neighborPosition[0]][neighborPosition[1]] == 1: had the x and y coordinates backwards. It should have been if map[neighborPosition[1]][neighborPosition[0]] == 1: In cases where the map was not square it was going out of bounds. Just needed to add a test case that wasn't square and figured it out pretty quick from there.
How can you efficiently flip a large range of indices's values from 1 to 0 or vice versa
You're given an N sized array arr. Suppose there's a contiguous interval arr[a....b] where you want to flip all the 1s to 0s and vice versa. Now suppose that there are a large (millions or billions) of these intervals (they could have different starting and end points) that you need to process. Is there an efficient algorithm to get this done? Note that a and b are inclusive. N can be any finite size essentially. The purpose of the question was just to practice algorithms. Consider arr = [0,0,0,0,0,0,0] Consider that we want to flips the following inclusive intervals [1,3], [0,4] After process [1,3], we have arr = [0,1,1,1,0,0,0] and after processing [0,4], we have arr = [1,0,0,0,1,0,0], which is the final array.
The obvious efficient way to do that is to not do that. Instead first collect at what indices the flipping changes, and then do one pass to apply the collected flipping information. Python implementation of a naive solution, the efficient solution, and testing: def naive(arr, intervals): for a, b in intervals: for i in range(a, b+1): arr[i] ^= 1 def efficient(arr, intervals): flips = [0] * len(arr) for a, b in intervals: flips[a] ^= 1 flips[b+1] ^= 1 xor = 0 for i, flip in enumerate(flips): xor ^= flip arr[i] ^= xor def test(): import random n = 30 arr = random.choices([0, 1], k=n) intervals = [] while len(intervals) < 100: a = random.randrange(n-1) b = random.randrange(n-1) if a <= b: intervals.append((a, b)) print(f'{arr = }') expect = arr * 1 naive(expect, intervals) print(f'{expect = }') result = arr * 1 efficient(result, intervals) print(f'{result = }') print(f'{(result == expect) = }') test() Demo output: arr = [1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0] expect = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0] result = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0] (result == expect) = True
Cast to Int Array and use bitwise not if you are using C or C++. But this is an SIMD task so its parallelizable if you wish.
ruby array sum of elements with structure conversion
I have { 3=>[ {63=>[5, 0, 1, 0]}, {64=>[0, 0, 0, 0]}, {65=>[0, 1, 2, 2]} ], 1=>[ {31=>[2, 0, 0, 0]}, {32=>[0, 0, 3, 0]} ] } I need to convert into { 3 => [5,1,3,2], 1 => [2,0,3,0] }
h= { 3=>[ {63=>[5, 0, 1, 0]}, {64=>[0, 0, 0, 0]}, {65=>[0, 1, 2, 2]} ], 1=>[ {31=>[2, 0, 0, 0]}, {32=>[0, 0, 3, 0]} ] } p h.map{ |k, v| { k=> v.map(&:values).flatten(1).transpose.map{ |r| r.reduce(:+) } } } # => [{3=>[5, 1, 3, 2]}, {1=>[2, 0, 3, 0]}]
It's nothing difficult, you just need a little attention. a = { 3=>[ {63=>[5, 0, 1, 0]}, {64=>[0, 0, 0, 0]}, {65=>[0, 1, 2, 2]} ], 1=>[ {31=>[2, 0, 0, 0]}, {32=>[0, 0, 3, 0]} ] } b = a.each_with_object({}) do |(k, v), memo| res = [] v.each do |h| h.each do |_, v2| v2.each_with_index do |el, idx| res[idx] ||= 0 res[idx] += el end end end memo[k] = res end b # => {3=>[5, 1, 3, 2], 1=>[2, 0, 3, 0]}
Here's some readable variable names and a basic explanation. a = { 3=>[ {63=>[5, 0, 1, 0]}, {64=>[0, 0, 0, 0]}, {65=>[0, 1, 2, 2]} ], 1=>[ {31=>[2, 0, 0, 0]}, {32=>[0, 0, 3, 0]} ] } b = a.each_with_object({}) do |(key, sub_hashes), result| # Get the subarray for each nested hash (Ignore keys on the nested hashes) # Also flattening while mapping to get appropriate array of arrays value = sub_hashes.flat_map(&:values). # Transpose each row into a column # e.g. [[5,0,1,0], [0,0,0,0], [0,1,2,2]] becomes [[5,0,0], [0,0,1], [1,0,2], [0,0,2]] transpose. # Sum each column # e.g. [1,0,2] = 1 + 0 + 2 = 3 map { |column| column.reduce(0, :+) } # Update results set (Could also get rid of intermediate variable 'value' if you wish) result[key] = value end puts b # => {3=>[5, 1, 3, 2], 1=>[2, 0, 3, 0]} puts b == {3 => [5,1,3,2], 1=>[2,0,3,0]} Edit: Now using flat_map!
Can I create an array in Ruby with default values?
Perl is pretty nice about default values: : jmglov#laurana; perl -e '#foo; printf "%d\n", $foo[123]' 0 : jmglov#laurana; perl -e '%foo; printf "%d\n", $foo{bar}' 0 Ruby can do the same, at least for hashes: >> foo = Hash.new(0) => {} >> foo[:bar] => 0 But the same seemingly does not work for arrays: >> foo = Array.new(0) => [] >> foo[123] => nil >> foo[124] = 0 => 0 >> foo[456] = 0 => 0 >> foo[455,456] => [nil, 0] Is it possible to supply a default value for arrays, so when they are auto-extended, they're filled with 0 instead of nil? Of course I can work around this, but at a cost to expressiveness: >> foo[457,458] = 890, 321 => [890, 321] >> foo[456] += 789 NoMethodError: You have a nil object when you didn't expect it! You might have expected an instance of Array. The error occurred while evaluating nil.+ >> foo.inject(0) {|sum, i| sum += (i || 0) } => 1211 >> foo.inject(:+) NoMethodError: You have a nil object when you didn't expect it! You might have expected an instance of Array. The error occurred while evaluating nil.+ Update 1: One of my colleagues pointed out that I can use #compact to solve the #inject issue, and #to_i to solve the standard element-at-index issue: >> foo.include? nil => true >> foo.compact.inject(:+) => 1211 >> foo[456,457] => [0, 890, 321] >> foo[455..457] => [nil, 0, 890] >> foo[455..457].map(&:to_i) => [0, 0, 890] Update 2: Thanks to Andrew Grimm for a solution to the += issue: >> foo = [] => [] >> def foo.[](i) >> fetch(i) {0} >> end => nil >> foo[4] => 0 >> foo => [] >> foo[4] += 123 => 123 >> foo => [nil, nil, nil, nil, 123] Update 3: this is starting to look like whack-a-mole! >> foo => [nil, nil, nil, nil, 123] >> foo[-2..-1] TypeError: can't convert Range into Integer But we can deal with that: >> def foo.[](index) >> if index.is_a? Range >> index.map {|i| self[i] } >> else ?> fetch(index) { 0 } # default to 0 if no element at index; will not cause auto-extension of array >> end >> end => nil >> foo => [nil, nil, nil, nil, 123] >> foo[-2..-1] => [nil, 123] I now have to admit (sheepishly) that I'll subclass Array to avoid cluttering my code: class MyClass class ArrayWithDefault < Array def [](index) if index.is_a? Range index.map {|i| self[i] } else fetch(index) { 0 } # default to 0 if no element at index; will not cause auto-extension of array end end end end Thanks for all the creative solutions. TIMTOWTDI indeed!
Not auto extended, but initialized to the specified length with a default value: >> Array.new(123, 0) => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Given that Ruby returns nil for a non-existing element (as opposed to index-out-of-bounds type error), you could just use an "or": a = [1,2,3] puts a[5] # => nil puts a[5] || "a default" # => a default You could take the monkey patch approach, but you probably would not want to do this in anything larger than a 1-file script: a = [1,2,3] def a.[](index) self.at(index) || "a default" end puts a[5] # => "a default"
The easiest way would be: new_array = Array.new(size, default_value) For example: new_array = Array.new(5,"foo")
Another approach would be overriding the Array#[] method and return the default value if there is no item class Array def [](index) self.at(index) ? self.at(index) : 0 end end and arr = [1,2,3] puts arr[0] # print 1 puts arr[5] # print 0
I'll put Johans elegant solution out there: foo.compact.inject(:+)
If you're dealing with integers you can call to_i: foo = [] foo[100] #=> nil foo[100].to_i #=> 0 foo[100] = 3 foo[100] #=> 3 UPD Oh, I didn't read all topic :) so you can use this: foo.inject{|a,b| a.to_i + b.to_i } which, actually, not the smartest one
I think an array is the wrong abstraction if you want to auto extend the array. Add another level of abstraction. Edit (from our discussion): The important thing is that the code to achieve your goal is located in the right place (single responsibility principle), and that place is not your "client code", hence the need for a new class. Extending the existing Array class (through inheritance/mixin) is probably better than encapsulating the wanted behaviour in an entierly new class.
How to modify Hash to enable access of element 'hash[:a][:b]' by shorter 'hash[:a,:b]' in Ruby?
I would be happy to access any element of multi-dimensional hash-array by a shorter expression h = {a: {b: 'c'}} # default way p h[:a][:b] # => "c" # a nicer way p h[:a,:b] # => "c" # nice assignment h[:a,:b] = 1 p h # => {:a=>{:b=>1}} I realize that in this way one eliminates the possibility to have a hash key being an array. {[:a,:b] => "c"} Since it is quite rare situation, I would prefer to reduce number of [] in my expressions. How can one achieve this? Update Ok, I wasn't clear. The problem is that I have tried to make custom [] and []= methods myself, but failed. Could you show me how such functionality can be implemented? Multi-dimensional arrays If you are looking for something similar for arrays, have a look on narray gem http://narray.rubyforge.org/ >> a = NArray.int(5,5) => NArrayint5,5: [ [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ] ] >> a[1,2] => 0 >> a[1,2]=1 => 1 >> a => NArrayint5,5: [ [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0 ] ] >> a[1,0..4]=1 => 1 >> a => NArrayint5,5: [ [ 0, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ], [ 0, 1, 0, 0, 0 ] ]
I have fixed my code, so now it works class AutoHash < Hash def initialize *args super #update, #update_index = args[0][:update], args[0][:update_key] unless args.empty? end def [] key,*args if args.count > 0 self[key][*args] else if self.has_key? key super key else AutoHash.new :update => self, :update_key => key end end end def []= *args v = args.pop k = args.shift if args.count > 0 self[k][*args]= v else #update[#update_index] = self if #update and #update_index super k,v end end end Examples a = AutoHash.new a[:a][:b][:c] = 123 a[:a,:b,:c] = 321 p a # => {:a=>{:b=>{:c=>321}}} If such definition is too confusing, then one could name the method differently (e.g. #path instead of redifining []) h[:a][:b][:c] = 123 p h.path(:a,:b,:c) # => 123 h.path(:a,:b,:c)= 321 p h #=> {:a=>{:b=>{:c=>321}}} mypath = [:a,:b,:c] p h.path(mypath) #=> 321
If you really want something like this then Ruby allows you to implement custom versions of [] and []= on classes of your choice including the Hash class provided by the language. Use with care if modifying base classes