Multiple variable assignment with each_with_index - ruby

I have this code snippet, a bucket in this case is just an array within a larger array:
def Dict.get_slot(aDict, key, default=nil)
# Returns the index, key, and value of a slot found in a bucket.
bucket = Dict.get_bucket(aDict, key)
bucket.each_with_index do |kv, i|
k, v = kv
if key == k
return i, k, v
end
end
return -1, key, default
end
The two variables called k and v are set to the contens of kv. But how can this work, when kv only contains one value at a time?
I wrote this into another file:
bucket = ['key', 'value']
key = 'key'
bucket.each_with_index do |kv, i|
k, v = kv
if key == k
puts k, v, i
end
end
And then the v variable was empty:
key
0
My question is, why does multiple assignment work in the first example, but not in the second?

bucket is a dictionary, simply put: a list of pairs of values, not just a list of values. Consider:
bucket.each do |kv|
# kv is an array: [some_k, some_v]
k, v = kv
# k is some_k
# v is some_v
end
bucket.each_with_index do |kv, i|
# kv is again an array: [some_k, some_v]
k, v = kv
# k is some_k
# v is some_v
end
As a side note, this kind of "pattern matching" can also be used in nested form for the block parameters directly:
bucket.each_with_index do |(k, v), i|
# k is some_k
# v is some_v
end

When you're calling bucket.each_with_index it first acts on 'key', then 'value'
You could try nested arrays, so in this example each member of the array is an array with two items.
irb(main):012:0> [['a','b'],['c','d']].each_with_index{|x,i|puts "#{i}: #{x}"}
0: ["a", "b"]
1: ["c", "d"]
You can then identify these by index
irb(main):019:0> [['a','b'],['c','d']].each_with_index{|x,i|puts "#{i}: #{x[0]} - #{x[1]}"}
0: a - b
1: c - d
Or set these as values with the syntax you used:
irb(main):020:0> [['a','b'],['c','d']].each_with_index{|x,i| a,b = x ; puts "#{i}: #{a} - #{b}"}
0: a - b
1: c - d
Without the one liners:
bucket = [
['a','b'],
['c','d']
]
bucket.each_with_index do |x, index|
k, v = x
puts index
puts "#{k} = #{v}"
end

Related

Detect if nested array contains similar elements

I have a method that gets an array of arrays and detects if any sub array is occurs more than one time, regardless of its order:
def has_similar_content?(array)
array.each.with_index do |prop1, index1|
array.each.with_index do |prop2, index2|
next if index1 == index2
return true if prop1.sort == prop2.sort
end
end
false
end
> has_similar_content?([%w[white xl], %w[red xl]])
=> false
> has_similar_content?([%w[blue xl], %w[xl blue cotton]])
=> false
> has_similar_content?([%w[blue xl], %w[xl blue]])
=> true
> has_similar_content?([%w[xl green], %w[red xl], %w[green xl]])
=> true
My problem is the runtime of this method, it has a quadratic complexity and needs an additional sort of the arrays to detect if the elements are the same.
Is there a more efficient way to do this?
I have assumed the question is as stated in my comment on the question.
Code
def disregarding_order_any_dups?(arr)
arr.map do |a|
a.each_with_object(Hash.new(0)) do |k,h|
h[k] += 1
end
end.uniq.size < arr.size
end
Examples
disregarding_order_any_dups? [%w[white xl], %w[red xl]]
#=> false
disregarding_order_any_dups? [%w[blue xl],
%w[xl blue cotton]]
#=> false
disregarding_order_any_dups? [%w[blue xl], %w[xl blue]]
#=> true
disregarding_order_any_dups? [%w[xl green], %w[red xl],
%w[green xl]]
#=> true
disregarding_order_any_dups? [[1,2,3,2], [3,1,3,2],
[2,3,1,2]]
#=> true
Complexity
If n = arr.size and m = arr.map(&:size).max, the computational complexity is O(n*m). The single statement within map's block could be replaced with a.sort, but that would increase the computational complexity to O(n*m*log(m)).
Explanation
For the last example the steps are as follows.
arr = [[1,2,3,2], [3,1,3,2], [2,3,1,2]]
b = arr.map do |a|
a.each_with_object(Hash.new(0)) do |k,h|
h[k] += 1
end
end
#=> [{1=>1, 2=>2, 3=>1}, {3=>2, 1=>1, 2=>1},
# {2=>2, 3=>1, 1=>1}]
c = b.uniq
#=> [{1=>1, 2=>2, 3=>1}, {3=>2, 1=>1, 2=>1}]
d = c.size
#=> 2
e = arr.size
#=> 3
d < e
#=> true
The expression
h = Hash.new(0)
creates a counting hash. Ruby expands h[k] += 1 to
h[k] = h[k] + 1
The hash instance methods are :[]= on the left, :[] on the right. If h does not have a key k, h[k] on the right is replaced with h's default value, which has been defined to equal zero, resulting in:
h[k] = 0 + 1
If h has a key k, h[k] on the right, the value of k, is not replaced with h's default value. See the version of Hash::new which takes an argument equal to the hash's default value.
this way is simpler:
array.
group_by(&:sort).
transform_values(&:length).
values.any? { |count| count > 1 }
This is still quadratic but it is faster :
def has_similar_content?(array)
# sort subarray only once. O( n * m * log(m) )
sorted_array= array.map(&:sort)
# if you can change the input array, this prevent object allocation :
# array.map!(&:sort!)
# compare each pair only once O( n * n/2 )
nb_elements= sorted_array.size
0.upto(nb_elements - 1).each do |i|
(i + 1).upto(nb_elements - 1).each do |j|
return true if sorted_array[i] == sorted_array[j]
end
end
return false
end

When is a block or object that is passed to Hash.new created or run?

I'm going through ruby koans and I am having a little trouble understanding when this code will be run:
hash = Hash.new {|hash, key| hash[key] = [] }
If there are no values in the hash, when does the new array get assigned to a given key in the Hash? Does it happen the first time a hash value is accessed without first assigning it? Please help me understand when exactly default values are created for any given hash key.
For the benefit of those new to Ruby, I have discussed alternative approaches to the problem, including the one that is the substance of this question.
The task
Suppose you are given an array
arr = [[:dog, "fido"], [:car, "audi"], [:cat, "lucy"], [:dog, "diva"], [:cat, "bo"]]
and wish to to create the hash
{ :dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"] }
First solution
h = {}
arr.each do |k,v|
h[k] = [] unless h.key?(k)
h[k] << v
end
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
This is quite straightforward.
Second solution
More Ruby-like is to write:
h = {}
arr.each { |k,v| (h[k] ||= []) << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
When Ruby sees (h[k] ||= []) << v the first thing she does is expand it to
(h[k] = h[k] || []) << v
If h does not have a key k, h[k] #=> nil, so the expression becomes
(h[k] = nil || []) << v
which becomes
(h[k] = []) << v
so
h[k] #=> [v]
Note that h[k] on the left of equality uses the method Hash#[]=, whereas h[k] on the right employs Hash#[].
This solution requires that none of the hash values equal nil.
Third solution
A third approach is to give the hash a default value. If a hash h does not have a key k, h[k] returns the default value. There are two types of default values.
Passing the default value as an argument to Hash::new
If an empty array is passed as an argument to Hash::new, that value becomes the default value:
a = []
a.object_id
#=> 70339916855860
g = Hash.new(a)
#=> {}
g[k] returns [] when h does not have a key k. (The hash is not altered, however.) This construct has important uses, but it is inappropriate here. To see why, suppose we write
x = g[:cat] << "bo"
#=> ["bo"]
y = g[:dog] << "diva"
#=> ["bo", "diva"]
x #=> ["bo", "diva"]
This is because the values of :cat and :dog are both set equal to the same object, an empty array. We can see this by examining object_ids:
x.object_id
#=> 70339916855860
y.object_id
#=> 70339916855860
Giving Hash::new a block which returns the default value
The second form of default value is to perform a block calculation. If we define the hash with a block:
h = Hash.new { |h,k| h[key] = [] }
then if h does not have a key k, h[k] will be set equal to the value returned by the block, in this case an empty array. Note that the block variable h is the newly-created empty hash. This allows us to write
h = Hash.new { |h,k| h[k] = [] }
arr.each { |k,v| h[k] << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
As the first element passed to the block is arr.first, the block variables are assigned values by evaluating
k, v = arr.first
#=> [:dog, "fido"]
k #=> :dog
v #=> "fido"
The block calculation is therefore
h[k] << v
#=> h[:dog] << "fido"
but since h does not (yet) have a key :dog, the block is triggered, setting h[k] equal to [] and then that empty array is appended with "fido", so that
h #=> { :dog=>["fido"] }
Similarly, after the next two elements of arr are passed to the block we have
h #=> { :dog=>["fido"], :car=>["audi"], :cat=>["lucy"] }
When the next (fourth) element of arr is passed to the block, we evaluate
h[:dog] << "diva"
but now h does have a key, so the default does not apply and we end up with
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy"]}
The last element of arr is processed similarly.
Note that, when using Hash::new with a block, we could write something like this:
h = Hash.new { launch_missiles("any time now") }
in which case h[k] would be set equal to the return value of launch_missiles. In other words, anything can be done in the block.
Even more Ruby-like
Lastly, the more Ruby-like way of writing
h = Hash.new { |h,k| h[k] = [] }
arr.each { |k,v| h[k] << v }
h #=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
is to use Enumerable#each_with_object:
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |k,v| h[k] << v }
#=> {:dog=>["fido", "diva"], :car=>["audi"], :cat=>["lucy", "bo"]}
which eliminates two lines of code.
Which is best?
Personally, I am indifferent to the second and third solutions. Both are used in practice.
The block is called when you add a new key to the hash. In that specific case:
hash["d"] #calls the block and store [] as a value of "d" key
hash["d"] #should print []
For more information, visit: https://docs.ruby-lang.org/en/2.0.0/Hash.html
If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block's responsibility to store the value in the hash if required.
Makes life easier
This is syntactic sugar for those times that you have a hash whose values are all arrays and you don't want to check each time to see if the hash key is already there and the empty array is already initialized before adding new elements. It allows this:
hash[:new_key] << new_element
instead of this:
hash[:new_key] = [] unless hash[:new_key]
hash[:new_key] << new_element
Solves an older problem
It's also an alternative to the simpler way of specifying a default value for hashes, which looks like this:
hash = Hash.new([])
The problem with this approach is that the same array object is used as the default for all keys. So
hash = Hash.new([])
hash[:a] << 1
hash[:b] << 2
will return [1, 2] for either hash[:a] or hash[:b], or even hash[:foo] for that matter. Which is not usually the desired/expected behavior.

How to find the next element given a certain element in array

I wrote a method to get the next element after a given element inside an array. If I provide the method with c, I want it to return e; if e, I want it to return a, etc.:
array = %w[a f c e]
def find_element_after(element, array)
index = array.find_index(element) + 1
array.at(index)
end
array.find_element_after("c", array)
If I pass in the last element I will get nil. But I want to return the first element instead.
I can solve this with if and else. But I want to know if Ruby has better way?
You could modify your method, taking array size into account, like this:
array = %w[a f c e]
def find_element_after(element, array)
index = array.find_index(element) + 1
array.at(index % array.size) # divide calculated index modulo array size
end
find_element_after('e', array)
# => "a"
If you want to make your method proof to passing argument that isn't member of array, you could do:
def find_element_after(element, array)
index = array.find_index(element)
array.at((index + 1) % array.size) if index
end
find_element_after('u', array)
# => nil
or:
def find_element_after(element, array)
return nil unless array.include?(element)
index = array.find_index(element)
array.at(index % array.size)
end
as you see, there's many possible solutions. Feel free to experiment.
If you pass in the last element, it actually works. The index gets evaluated to the last index, and retrieving the element at lastindex + 1 from the array returns nil.
The problem is when you provide an element that is not present in the array. It's this scenario that will result in the index being nil and then throwing the NoMethodError when you call + 1 on it.
To fix this case, define your method like this:
def find_element_after(element, array)
index = array.find_index(element)
array.at(index + 1) if index
end
Here's a demo showing how it works now (run online):
array = %w[a f c e]
def find_element_after(element, array)
index = array.find_index(element)
array.at(index + 1) if index
end
p find_element_after("c", array) # element in the middle - prints "e"
p find_element_after("e", array) # last element - prints "nil"
p find_element_after("z", array) # element not present in the array - prints "nil" (no error)
You can use each_cons to iterate the array using pairs:
def find_element_after(element, array)
cons = array.each_cons(2).find { |i1, i2| i1 == element }
cons.nil? ? array.first : cons.last
end
find_element_after('c', array)
# => "e"
find_element_after('e', array)
# => "a"
Here are some other ways to do that.
#1 Use the method Array#rotate
def nxt(arr, e)
arr.rotate(arr.index(e)+1).first
end
arr = %w[a f c e]
nxt(arr, 'a') #=> "f"
nxt(arr, 'f') #=> "c"
nxt(arr, 'c') #=> "e"
nxt(arr, 'e') #=> "a"
I think this reads well, but it has the disadvantage of creating a temporary array the size of arr.
#2 Use Array#rotate to construct a hash
h = Hash[arr.zip(arr.rotate(1))]
#=> {"a"=>"f", "f"=>"c", "c"=>"e", "e"=>"a"}
h['a'] #=> "f"
h['f'] #=> "c"
h['c'] #=> "e"
h['e'] #=> "a"
#3 Use Array#cycle to create an enumerator
enum = arr.cycle
def nxt(enum, v)
until enum.next == v do
end
enum.next
end
nxt(enum, 'a') #=> "f"
nxt(enum, 'f') #=> "c"
nxt(enum, 'c') #=> "e"
nxt(enum, 'e') #=> "a"
The latter two approaches should be relatively efficient if you had several values to map.
If need a cycle so to get the first element if the last passed
def find_after(element, array)
idx = array.index(element)
array[idx - array.length + 1]
end

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 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