Can I create an array in Ruby with default values? - ruby

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.

Related

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.

Why is only one value in my hash being changed?

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.

Nested if else inside .each iteration

I'm wondering if this makes sense or if the syntax is wrong and basically if this is acceptable. I wanted to nest an if/else condition within my iteration of the array.
def change_numbers(first_array, second_array)
second_array.each do |index|
if first_array[index] == 0
first_array[index] = 1
else
first_array[index] = 0
end
end
end
The array is a simple (binary) array and will only consist of 0s and 1s and I want to use the second array's elements as the indices of the first array that I am going to change.
Example:
first_array = [0, 0, 0, 0, 1, 1, 1, 1, 1]
second_array = [3, 5, 7]
Result:
first_array = [0, 0, 0, 1, 1, 0, 1, 0, 1]
If you don't want to use an if/else you can do:
second_array.each do |index|
first_array[index] = (first_array[index] + 1) % 2
end
def change_numbers(first_array, second_array)
second_array.each { |index| first_array[index] = 1 - first_array[index] }
end
A bit-wise XOR:
ar = [0, 0, 0, 0, 1, 1, 1, 1, 1]
indices = [3, 5, 7]
indices.each{|i| ar[i] ^= 1 }
You can try this -
def change_numbers(first_array, second_array)
second_array.each do |index|
first_array[index] = ((first_array[index] == 0) ? 1 : 0)
end
end

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!

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

Resources