Why isn't the value picked up in this Ruby inject call? - ruby

In the following code, if I don't add parentheses around the key, value parameters in "original.inject" (i.e., I do original.inject({}){ |result, key, value| ), I get a nil error as in the code comments below, as if the value is not being passed correctly. Why is this? What exactly is going on here? (I'm running ruby-2.1.1)
def hash_test(original,options={},&block)
original.inject({}){ |result, (key, value)|
value = value + 2
block.call(result, key, value)
result
}
end
h={:a=>3, :b=>4}
r = hash_test(h) { |result, key, value| result[key]=value }
puts r #=> {:a=>5, :b=>6}
#if no parentheses around (key, value) in original.inject, you get a:
# hash_transformer.rb:5:in `block in hash_test': undefined method `+' for nil:NilClass (NoMethodError)
# from hash_transformer.rb:4:in `each'
# from hash_transformer.rb:4:in `inject'
# from hash_transformer.rb:4:in `hash_test'
# from hash_transformer.rb:15:in `<main>'

When inject is called on a Hash inject yields the key value pairs as an array to the supplied block, example as below:
{:a=>3, :b=>4}.inject({}) do |x,y|
p y
x[y[0]] = y[1]
x
end
#>>[:a, 3]
#>>[:b, 4]
#=> {:a=>3, :b=>4}
y yielded the first time is [:a, 3], and second time is [:b, 4], so you need to destructure the array by supplying the parenthesis around the arguments:
(a,b) = [:a, 3]
a #=> :a
b #=> 3

This shows what's going on.
With deconstruction of hash element
{:a=>3, :b=>4}.inject({}) do |x,(y,z)|
puts "x = #{x}"
puts "y = #{y}"
puts "z = #{z}"
puts
x
end
x = {}
y = a
z = 3
x = {}
y = b
z = 4
Without deconstruction of hash element
{:a=>3, :b=>4}.inject({}) do |x,y,z|
puts "x = #{x}"
puts "y = #{y}"
puts "z.nil? = #{z.nil?}"
puts
x
end
x = {}
y = [:a, 3]
z.nil? = true
x = {}
y = [:b, 4]
z.nil? = true

Take a look at this post :
http://apidock.com/ruby/Enumerable/inject
It said inject is defined to take pairs parameter.
If you want to use three parameters, like (inject do |c,key,val|), then you should use parentheses to combine the (key,val)

Related

Ruby map! does not change contained variable

Suppose I have an object x, and an array y=[x] which contains x. If I manipulate x, then y does not change:
x = 1 # => 1
y = [x] # => [1]
x = x+1 # => 2
x # => 2
y # => [1]
and if I change y, then x does not change.
x = 1 # => 1
y = [x] # => [1]
y.map!{|a| a+1} # => [2]
y # => [2]
x # => 1
Is there a way to have them change in parallel? It feels like when I map! over an array the underlying values should change.
first of all
x = x + 1
will create a new variable with an old name x
x = 1
y = [x]
x.object_id
# 3
y[0].object_id
# 3
x = x + 1
x.object_id
# 5
y[0].object_id
# 3
Second, numbers are immutable objects in Ruby (and Strings, for example, are mutable). So you just can't do what you want using number objects in Ruby. What you can do is slightly more obscure. You can create your own mutable object (container for a number) and use a reference to this object in your array.
class MutableNumber
attr_accessor :n
def initialize(n)
#n = n
end
end
x = MutableNumber.new(1)
y = [x]
y[0].n
#=> 1
x.n += 1
y[0].n
#=> 2
You can go little bit further and to add more magic here to mimic numbers
class MutableNumber
attr_accessor :n
def initialize(n)
#n = n
end
def method_missing(m, *args, &blk)
#n = #n.send(m, *args, &blk)
self
end
end
x = MutableNumber.new(1)
y = [x]
y[0].n
#=> 1
x += 1
y[0].n
#=> 2
But I would not encourage you to do any of this.
Just stick to the idea that numbers are immutable. And overall you should be careful with mutability.
map! is mutable for the array, not for its elements. It means that the array gets new values in place (i.e. it's assigned to original array). Elements inside the array are not mutated, but replaced by new elements.
If you want to change old values, you can iterate using each and call mutating methods on each element. You can see this with array of strings:
a = "a"; b = "b"; aa = [a, b]
#=> ["a", "b"]
aa.map!{|e| e.capitalize }
#=> ["A", "B"]
[a, b]
#=> ["a", "b"]
a = "a"; b = "b"; aa = [a, b]
#=> ["a", "b"]
aa.each{|e| e.capitalize! }
#=> ["A", "B"]
[a, b]
#=> ["A", "B"]
Note that it won't work for immutable objects and numbers are immutable, as #fl00r explained in his answer.

How to pass a method to a Hash#map

The code:
a = [1, 2, 3]
h = {a: 1}
def f args
p args
end
h.map(&method(:f))
a.map(&method(:f))
h.map do |k,v|
p [k,v]
end
The output:
[:a, 1]
1
2
3
[:a, 1]
Why can't I define f for a hash as follows?
def f k, v
p [k, v]
end
You are correct that the reason stems from the one of the two main differences between proc's and lambda's. I'll trying explaining it in a slightly different way than you did.
Consider:
a = [:a, 1]
h = {a: 1}
def f(k,v)
p [k, v]
end
a.each(&method(:f))
#-> in `f': wrong number of arguments (1 for 2) (ArgumentError)
h.each(&method(:f))
#-> in `f': wrong number of arguments (1 for 2) (ArgumentError)
where I use #-> to show what is printed and #=> to show what is returned. You used map, but each is more appropriate here, and makes the same point.
In both cases elements of the receiver are being passed to the block1:
&method(:f)
which is (more-or-less, as I will explain) equivalent to:
{ |k,v| p [k,v] }
The block is complaining (for both the array and hash) that it is expecting two arguments but receiving only one, and that is not acceptable. "Hmmm", the reader is thinking, "why doesn't it disambiguate in the normal way?"
Let's try using the block directly:
a.map { |k,v| p [k,v] }
#-> [:a, nil]
# [1, nil]
h.map { |k,v| p [k,v] }
#-> [:a, 1]
This works as expected, but does not return what we wanted for the array.
The first element of a (:a) is passed into the block and the block variables are assigned:
k,v = :a
#=> :a
k #=> :a
v #=> nil
and
p [k,v]
#-> :a
#-> nil
Next, 1 is passed to the block and [1,nil] is printed.
Let's try one more thing, using a proc created with Proc::new:
fp = Proc.new { |k,v| p [k, v] }
#=> #<Proc:0x007ffd6a0a8b00#(irb):34>
fp.lambda?
#=> false
a.each { |e| fp.call(e) }
#-> [:a, nil]
#-> [:a, 1]
h.each { |e| fp[e] }
#-> [:a, 1]
(Here I've used one of three aliases for Proc#call.) We see that calling the proc has the same result as using a block. The proc expects two arguments and but receives only one, but, unlike the lambda, does not complain2.
This tells us that we need to make small changes to a and f:
a = [[:a, 1]]
h = {a: 1}
def f(*(k,v))
p [k, v]
end
a.each(&method(:f))
#-> [:a, 1]
h.each(&method(:f))
#-> [:a, 1]
Incidentally, I think you may have fooled yourself with the variable name args:
def f args
p args
end
as the method has a single argument regardless of what you call it. :-)
1 The block is created by & calling Method#to_proc on the method f and then converting the proc (actually a lambda) to a block.
2 From the docs for Proc: "For procs created using lambda or ->() an error is generated if the wrong number of parameters are passed to a Proc with multiple parameters. For procs created using Proc.new or Kernel.proc, extra parameters are silently discarded."
As it appears, it must be some sort of implicit destructuring (or non-strict arguments handling), which works for procs, but doesn't for lambdas:
irb(main):007:0> Proc.new { |k,v| p [k,v] }.call([1,2])
[1, 2]
=> [1, 2]
irb(main):009:0> lambda { |k,v| p [k,v] }.call([1,2])
ArgumentError: wrong number of arguments (1 for 2)
from (irb):9:in `block in irb_binding'
from (irb):9:in `call'
from (irb):9
from /home/yuri/.rubies/ruby-2.1.5/bin/irb:11:in `<main>'
But one can make it work:
irb(main):010:0> lambda { |(k,v)| p [k,v] }.call([1,2])
[1, 2]
=> [1, 2]
And therefore:
def f ((k, v))
p [k, v]
end
So Hash#map always passes one argument.
UPD
This implicit destructuring also happens in block arguments.
names = ["Arthur", "Ford", "Trillian"]
ids = [42, 43, 44]
id_names = ids.zip(names) #=> [[42, "Arthur"], [43, "Ford"], [44, "Trillian"]]
id_names.each do |id, name|
puts "user #{id} is #{name}"
end
http://globaldev.co.uk/2013/09/ruby-tips-part-2/
UPD Don't take me wrong. I'm not suggesting writing such code (def f ((k, v))). In the question I was asking for explanation, not for the solution.

Ruby: How to access each element of an array inside a hash where hash key is the array I want to access

I have:
Array1 = [x, y, z]
Array2 = [m, n]
a = b
hash1 = {Array1 => val1,
Array2 => val2,
a => c
}
How to iterate inside each element of Array1, Array2 inside the hash1?
hash1.each do |t|
t[0] #gives me Array1 as a string. I need [x,y,z]
end
It don't give you a string. It give you the correct array.
{
[1,2] => 'a'
}.each{|t| puts t[0].class}
# prints array
{
[1,2] => 'a'
}.each{|t| puts t[0][0]}
# prints 1
Note that you are doing each on a hash. You can deconstruct the key-value pair giving two variables to the block, like this:
{a:1, b:2}.each { |k,v| p k; p v }
#prints :a
#prints 1
#prints :b
#prints 2
Something like this
hash1.keys.each do |k|
if k.is_a?(Array)
k.each do |v|
.. Do something here ..
end
end
end
Just replace the Do something here with the code you want, and v will be the value in the array

Ruby - mapping an array to hashmap

I have an array, and a function that returns a value given a value. Ultimately I want to create a hashmap that has the values of the array as key value, and the result of f(key_value) as the value. Is there a clean, simple way, like similar to each/map of Array, of doing this using block?
So something that is equivalent to
hsh = {}
[1,2,3,4].each do |x|
hsh[x] = f(x)
end
but looks more similar to this, in that it's simple and one line?
results = array.map { | x | f(x) }
Note that since Ruby 2.1.0 you can also use Array#to_h, like this:
[1,2,3,4].map{ |x| [x, f(x)] }.to_h
Ruby 2.6.0 enables passing a block to the to_h-method. This enables an even shorter syntax for creating a hash from an array:
[1, 2, 3, 4].to_h { |x| [x, f(x)] }
You could also define the function as the hash's default value:
hash = Hash.new {|hash, key| hash[key] = f(key) }
Then when you lookup a value, the hash will calculate and store it on the fly.
hash[10]
hash.inspect #=> { 10 => whatever_the_result_is }
You need each_with_object.
def f x
x * 2
end
t = [1, 2, 3, 4].each_with_object({}) do |x, memo|
memo[x] = f(x)
end
t # => {1=>2, 2=>4, 3=>6, 4=>8}
Another one:
t2 = [1, 2, 3, 4].map{|x| [x, f(x)]}
Hash[t2] # => {1=>2, 2=>4, 3=>6, 4=>8}
Check out the Hash::[] method.
Hash[ [1,2,3,4].collect { |x| [x, f(x)] } ]
Using Facets' mash (method to convert enumerable to hashes):
[1, 2, 3, 4].mash { |x| [x, f(x)] }
From Ruby 2.1:
[1, 2, 3, 4].map { |x| [x, f(x)] }.to_h
Also, Rails method index_with would be helpful:
a = ['a', 'bsdf', 'wqqwc']
a.index_with(&:size)
=> {"a"=>1, "bsdf"=>4, "wqqwc"=>5}
You're looking for reduce()|inject() method:
elem = [1,2,3,4]
h = elem.reduce({}) do |res, x|
res[x] = x**2
res
end
puts h
The argument passed to reduce({}) is the initial value of an intermediate object that is passed to the block as res variable. In each iteration we're adding new pair key: value to the res Hash and returing the Hash to be used in next iteration.
The method above precomputes a very practical hash of squared values:
{1=>1, 2=>4, 3=>9, 4=>16}

Ruby multidimensional array

Maybe it's just my lack of abilities to find stuff here that is the problem, but I can't find anything about how to create multidimensional arrays in Ruby.
Could someone please give me an example on how to do it?
Strictly speaking it is not possible to create multi dimensional arrays in Ruby. But it is possible to put an array in another array, which is almost the same as a multi dimensional array.
This is how you could create a 2D array in Ruby:
a = [[1,2,3], [4,5,6], [7,8,9]]
As stated in the comments, you could also use NArray which is a Ruby numerical array library:
require 'narray'
b = NArray[ [1,2,3], [4,5,6], [7,8,9] ]
Use a[i][j] to access the elements of the array. Basically a[i] returns the 'sub array' stored on position i of a and thus a[i][j] returns element number j from the array that is stored on position i.
you can pass a block to Array.new
Array.new(n) {Array.new(n,default_value)}
the value that returns the block will be the value of each index of the first array,
so..
Array.new(2) {Array.new(2,5)} #=> [[5,5],[5,5]]
and you can access this array using array[x][y]
also for second Array instantiation, you can pass a block as default value too. so
Array.new(2) { Array.new(3) { |index| index ** 2} } #=> [[0, 1, 4], [0, 1, 4]]
Just a clarification:
arr = Array.new(2) {Array.new(2,5)} #=> [[5,5],[5,5]]
is not at all the same as:
arr = Array.new(2, Array.new(2, 5))
in the later case, try:
arr[0][0] = 99
and this is what you got:
[[99,5], [99,5]]
There are two ways to initialize multi array (size of 2).
All the another answers show examples with a default value.
Declare each of sub-array (you can do it in a runtime):
multi = []
multi[0] = []
multi[1] = []
or declare size of a parent array when initializing:
multi = Array.new(2) { Array.new }
Usage example:
multi[0][0] = 'a'
multi[0][1] = 'b'
multi[1][0] = 'c'
multi[1][1] = 'd'
p multi # [["a", "b"], ["c", "d"]]
p multi[1][0] # "c"
So you can wrap the first way and use it like this:
#multi = []
def multi(x, y, value)
#multi[x] ||= []
#multi[x][y] = value
end
multi(0, 0, 'a')
multi(0, 1, 'b')
multi(1, 0, 'c')
multi(1, 1, 'd')
p #multi # [["a", "b"], ["c", "d"]]
p #multi[1][0] # "c"
The method given above don't works.
n = 10
arr = Array.new(n, Array.new(n, Array.new(n,0.0)))
arr[0][1][2] += 1
puts arr[0][2][2]
is equivalent to
n = 10
a = Array.new(n,0.0)
b = Array.new(n,a)
arr = Array.new(n, b)
arr[0][1][2] += 1
puts arr[0][2][2]
and will print 1.0, not 0.0, because we are modifiyng array a and printing the element of array a.
Actually this is much quicker than the block method given above:
arr = Array.new(n, Array.new(n, Array.new(n,0.0)))
arr[0][1][2] += 1
I had to reproduce PHP-style multidimensional array in Ruby recently. Here is what I did:
# Produce PHP-style multidimensional array.
#
# Example
#
# arr = Marray.new
#
# arr[1][2][3] = "foo"
# => "foo"
#
# arr[1][2][3]
# => "foo"
class Marray < Array
def [](i)
super.nil? ? self[i] = Marray.new : super
end
end
Perhaps you can simulate your multidimensional Array with a Hash. The Hash-key can by any Ruby object, so you could also take an array.
Example:
marray = {}
p marray[[1,2]] #-> nil
marray[[1,2]] = :a
p marray[[1,2]] #-> :a
Based on this idea you could define a new class.
Just a quick scenario:
=begin rdoc
Define a multidimensional array.
The keys must be Fixnum.
The following features from Array are not supported:
* negative keys (Like Array[-1])
* No methods <<, each, ...
=end
class MArray
INFINITY = Float::INFINITY
=begin rdoc
=end
def initialize(dimensions=2, *limits)
#dimensions = dimensions
raise ArgumentError if limits.size > dimensions
#limits = []
0.upto(#dimensions-1){|i|
#limits << (limits[i] || INFINITY)
}
#content = {}
end
attr_reader :dimensions
attr_reader :limits
=begin rdoc
=end
def checkkeys(keys)
raise ArgumentError, "Additional key values for %i-dimensional Array" % #dimensions if keys.size > #dimensions
raise ArgumentError, "Missing key values for %i-dimensional Array" % #dimensions if keys.size != #dimensions
raise ArgumentError, "No keys given" if keys.size == 0
keys.each_with_index{|key,i|
raise ArgumentError, "Exceeded limit for %i dimension" % (i+1) if key > #limits[i]
raise ArgumentError, "Only positive numbers allowed" if key < 1
}
end
def[]=(*keys)
data = keys.pop
checkkeys(keys)
#content[keys] = data
end
def[](*keys)
checkkeys(keys)
#content[keys]
end
end
This can be used as:
arr = MArray.new()
arr[1,1] = 3
arr[2,2] = 3
If you need a predefined matrix 2x2 you can use it as:
arr = MArray.new(2,2,2)
arr[1,1] = 3
arr[2,2] = 3
#~ arr[3,2] = 3 #Exceeded limit for 1 dimension (ArgumentError)
I could imagine how to handle commands like << or each in a two-dimensional array, but not in multidimensional ones.
It might help to remember that the array is an object in ruby, and objects are not (by default) created simply by naming them or naming a the object reference. Here is a routine for creating a 3 dimension array and dumping it to the screen for verification:
def Create3DimensionArray(x, y, z, default)
n = 0 # verification code only
ar = Array.new(x)
for i in 0...x
ar[i] = Array.new(y)
for j in 0...y
ar[i][j] = Array.new(z, default)
for k in 0...z # verification code only
ar[i][j][k] = n # verification code only
n += 1 # verification code only
end # verification code only
end
end
return ar
end
# Create sample and verify
ar = Create3DimensionArray(3, 7, 10, 0)
for x in ar
puts "||"
for y in x
puts "|"
for z in y
printf "%d ", z
end
end
end
Here is an implementation of a 3D array class in ruby, in this case the default value is 0
class Array3
def initialize
#store = [[[]]]
end
def [](a,b,c)
if #store[a]==nil ||
#store[a][b]==nil ||
#store[a][b][c]==nil
return 0
else
return #store[a][b][c]
end
end
def []=(a,b,c,x)
#store[a] = [[]] if #store[a]==nil
#store[a][b] = [] if #store[a][b]==nil
#store[a][b][c] = x
end
end
array = Array3.new
array[1,2,3] = 4
puts array[1,2,3] # => 4
puts array[1,1,1] # => 0

Resources