Working with Transpose functions result in error - arrays

consider the following array
arr = [["Locator", "Test1", "string1","string2","string3","string4"],
["$LogicalName", "Create Individual Contact","value1","value2"]]
Desired result:
[Test1=>{"string1"=>"value1","string2"=>"value2","string3"=>"","string4"=>""}]
When I do transpose, it gives me the error by saying second element of the array is not the length of the first element in the array,
Uncaught exception: element size differs (2 should be 4)
so is there any to add empty string in the place where there is no element and can perform the transpose and then create the hash as I have given above? The array may consist of many elements with different length but according to the size of the first element in the array, every other inner array has to change by inserting empty string and then I can do the transpose. Is there any way?

It sounds like you might want Enumerable#zip:
headers, *data_rows = input_data
headers.zip(*data_rows)
# => [["Locator", "$LogicalName"], ["Test1", "Create Individual Contact"],
# ["string1", "value1"], ["string2", "value2"], ["string3", nil], ["string4", nil]]

If you wish to transpose an array of arrays, each element of the array must be the same size. Here you would need to do something like the following.
arr = [["Locator", "Test1", "string1","string2","string3","string4"],
["$LogicalName", "Create Individual Contact","value1","value2"]]
keys, vals = arr
#=> [["Locator", "Test1", "string1", "string2", "string3", "string4"],
# ["$LogicalName", "Create Individual Contact", "value1", "value2"]]
idx = keys.index("Test1") + 1
#=> 2
{ "Test1" => [keys[idx..-1],
vals[idx..-1].
concat(['']*(keys.size - vals.size))].
transpose.
to_h }
#=> {"Test1"=>{"string1"=>"value1", "string2"=>"value2", "string3"=>"", "string4"=>""}}
It is not strictly necessary to define the variables keys and vals, but that avoids the need to create those arrays multiple times. It reads better as well, in my opinion.
The steps are as follows. Note keys.size #=> 6 and vals.size #=> 4.
a = vals[idx..-1]
#=> vals[2..-1]
#=> ["value1", "value2"]
b = [""]*(keys.size - vals.size)
#=> [""]*(4 - 2)
#=> ["", ""]
c = a.concat(b)
#=> ["value1", "value2", "", ""]
d = keys[idx..-1]
#=> ["string1", "string2", "string3", "string4"]
e = [d, c].transpose
#=> [["string1", "value1"], ["string2", "value2"], ["string3", ""], ["string4", ""]]
f = e.to_h
#=> {"string1"=>"value1", "string2"=>"value2", "string3"=>"", "string4"=>""}
f = e.to_h
#=> { "Test1" => f }

Find the longest Element in your Array and make sure every other element has the same length - loop and add maxLength - element(i).length amount of "" elements.

Related

I need some clarification on how this 2D array can be created in Ruby?

I'm currently learning Ruby through App Academy Open, and came across a problem that I solved differently than the course solution. I could use some clarification on how the course solution works.
We have to define a function "zip" that takes any number of arrays as arguments (but all arrays the same length). The function should return a 2D array where each subarray contains the elements at the same index from each argument.
zip(['a','b','c'],[1,2,3])
should return:
[['a',1],['b',2],['c',3]]
Here is my solution:
def zip(*arrs)
main_arr = Array.new(arrs[0].length) {Array.new}
arrs.each do |array|
array.each_with_index do |ele, ele_idx|
main_arr[ele_idx] << ele
end
end
main_arr
end
And here is the course solution:
def zip(*arrays)
length = arrays.first.length
(0...length).map do |i|
arrays.map { |array| array[i] }
end
end
Can someone explain how the 2D array is being built within the mapped range above? I'm a bit confused as a beginner and could use some clarification.
EDIT:
Thank you very much iGian. Explanation really helped.
Running the code below should be self explanatory, see the comments:
def zip_steps(*arrays)
# get the size of the array
length = arrays.first.length
# mapping the range up to use as indexing
p (0...length).map { |i| i } #=> [0, 1, 2]
# map the arrays
p arrays.map { |array| array } #=> [["a", "b", "c"], [1, 2, 3]]
# map the arrays returning a specific element at index 1, for example
p arrays.map { |array| array[1] } #=> ["b", 2]
# put arrays mapping inside the range mapping
# where instead of returning the element 1
# it returns the element i
(0...length).map do |i|
arrays.map { |array| array[i] }
end
end
ary1 = ['a','b','c']
ary2 = [1,2,3]
p zip_steps(ary1, ary2) #=> [["a", 1], ["b", 2], ["c", 3]]

Ruby converting Array of Arrays into Array of Hashes

Please I need a help with this.
In Ruby If I have this array of arrays
array = [["a: 1", "b:2"],["a: 3", "b:4"]]
How can I obtain this array of hashes in ruby
aoh = [{:a => "1", :b => "2"},{:a => "3", :b => "4"}]
Note that, like pointed out in the comments, this is most likely an XY-problem and instead of transforming the array the better option is to build the starting array in a better manner.
Nevertheless, you can do this in the following manner:
aoh = array.map { |array| array.to_h { |string| string.split(':').map(&:strip) } }
# => [{"a"=>"1", "b"=>"2"}, {"a"=>"3", "b"=>"4"}]
The above will give you string keys which is the safer option to go with. You can convert them to symbols, but they should only be used for trusted identifiers. When the data comes from an user or external source I would go for the above.
Converting to symbols can be done by adding the following line:
# note that this line will mutate the aoh contents
aoh.each { |hash| hash.transform_keys!(&:to_sym) }
#=> [{:a=>"1", :b=>"2"}, {:a=>"3", :b=>"4"}]
array = [["a: 1", "b:2"], ["a: 3", "b:4"]]
array.map do |a|
Hash[
*a.flat_map { |s| s.split(/: */) }.
map { |s| s.match?(/\A\d+\z/) ? s : s.to_sym }
]
end
#=> [{:a=>"1", :b=>"2"}, {:a=>"3", :b=>"4"}]
The regular expression /: */ reads, "match a colon followed by zero or more (*) spaces". /\A\d+\z/ reads, "match the beginning of the string (\A) followed by one or more (+) digits (\d), followed by the end of the string (\z).
The steps are as follows. The first is for the element arr[0] to be passed to the block, the block variable a assigned its value and the block calculation performed.
a = array[0]
#=> ["a: 1", "b:2"]
b = a.flat_map { |s| s.split(/: */) }
#=> ["a", "1", "b", "2"]
c = b.map { |s| s.match?(/\A\d+\z/) ? s : s.to_sym }
#=> [:a, "1", :b, "2"]
d = Hash[*c]
#=> {:a=>"1", :b=>"2"}
We see the array ["a: 1", "b:2"] is mapped to {:a=>"1", :b=>"2"}. Next the element arr[1] is passed to the block, the block variable a is assigned its value and the block calculation is performed.
a = array[1]
#=> ["a: 3", "b:4"]
b = a.flat_map { |s| s.split(/: */) }
#=> ["a", "3", "b", "4"]
c = b.map { |s| s.match?(/\d/) ? s : s.to_sym }
#=> [:a, "3", :b, "4"]
d = Hash[*c]
#=> {:a=>"3", :b=>"4"}
The splat operator (*) causes Hash[*c] to be evaluated as:
Hash[:a, "3", :b, "4"]
See Hash::[].
Loop through your items, loop through its items, create a new array:
array.map do |items|
items.map do |item|
k,v = item.split(":", 2)
{ k.to_sym => v }
}
}
Note that we're using map instead of each which will return an array.

Merge hashes of arrays based on similar positions with Ruby

I have the following two hashes with arrays as values.
a = {
"Us" => [["1", ["1", "2"]], ["2", ["1"]]],
"Pa" => [["1", ["1", "3", "5"]], ["4", ["7"]]]
}
b = {
"Us" => [["1", ["F", "O"]], ["2", ["N"]]],
"Pa" => [["1", ["S", "D", "H"]], ["4", ["K"]]]
}
I'm trying to merge the hashes to get a final has like this:
c = {
"Us" => [["1", ["1|F", "2|O"]], ["2", ["1|N"]]],
"Pa" => [["1", ["1|S", "3|D", "5|H"]], ["4", ["7|K"]]]
}
I found the following code with merge, and tried to apply it to my issue, but I got an error:
a.merge(b) {|key, a_val, b_val| a_val.merge b_val }
# >> NoMethodError: undefined method `merge' for [["1", ["1", "2"]], ["2", ["1"]]]:Array
I even got an error with a + b:
a + b
# >> NoMethodError: undefined method `+' for #<Hash:0x0000060078e460>
<<<< UPDATE >>>>
Thanks both Cary and tadman. Outside the original question I show the input file I have and the output I´m tryng to obtain. I show in order for you get an idea why
I generated 2 hashes in that way. In the output I create blocks where fathers are unique values of column 1, below children (unique values in column 2 related with col 1).
Column 3 are subchildren that belong to a value in col2 and column 4 are text contents related with col3.
Probably hash "c" is easier to generate from the beginning.
This is my input file
Main,Stage1,Stage2,Description
Us,1,1,F
Us,1,2,O
Us,2,1,N
Pa,1,1,S
Pa,1,3,D
Pa,1,5,H
Pa,4,7,K
This is the output I almost get.
Main..Stage1..Stage2..Description
Us
......1
..............1.......F
..............2.......O
......2
..............1.......N
Pa
......1
..............1.......S
..............3.......D
..............5.......H
......4
..............7.......K
Then I was able to create this code, but like tadman says, I need to reorder the way I get this to make easier the things, since
I use 4 hashes. After I create hash "a" and "b" I was stuck, since I needed a unique hash to iterate and be able to print in the output structure shown above.
My code before post the question
X = Hash.new{|hsh,key| hsh[key] = [] }
Y = Hash.new{|hsh,key| hsh[key] = [] }
a = Hash.new{|hsh,key| hsh[key] = [] }
b = Hash.new{|hsh,key| hsh[key] = [] }
File.foreach('file.txt').with_index do
|line, line_num|
if line_num > 0
r = line.split(",")
X[r[0] + "°" + r[1]].push r[2]
Y[r[0] + "°" + r[1]].push r[3].strip
end
end
X.each{ |k,v|
lbs = k.split("°")
a[lbs[0]].push [ lbs[1], v] #Here I generate hash a
}
Y.each{ |k,v|
lbs = k.split("°")
b[lbs[0]].push [ lbs[1], v] #Here I generate hash b
}
What you have here is going to require a bit of work to solve because of all the complicated nesting. This would be a lot easier if you did some work to reorder how that data is stored.
Yet you can do this:
a={"Us"=>[["1", ["1", "2"]], ["2", ["1"]]], "Pa"=>[["1", ["1", "3", "5"]], ["4", ["7"]]]}
b={"Us"=>[["1", ["F", "O"]], ["2", ["N"]]], "Pa"=>[["1", ["S", "D", "H"]], ["4", ["K"]]]}
c = a.keys.map do |k|
ah = a[k].to_h
bh = b[k].to_h
[
k,
ah.keys.map do |ka|
[
ka,
ah[ka].zip(bh[ka]).map do |pair|
pair.join('|')
end
]
end
]
end.to_h
# => {"Us"=>[["1", ["1|F", "2|O"]], ["2", ["1|N"]]], "Pa"=>[["1", ["1|S", "3|D", "5|H"]], ["4", ["7|K"]]]}
The key here is rigorous use of map to transform each layer and zip to "zipper" two arrays together into pairs that can then be combined with join into the desired string target. Cast back to a Hash with to_h at the end and you get what you want.
There's an intermediate conversion for each subset to a hash to handle out-of-order situations where one might specify the apparent "keys" in a different sequence.
What you'll want to do is wrap this up in a method with a descriptive name:
def hash_compactor(a,b)
# ... (code) ...
end
That'll help keep it modular. Normally I try and create solutions that handle N arguments by defining it as:
def hash_compactor(*input)
# ...
end
Where input is then an array of various sets in the form you've given. The resulting code is surprisingly a lot more complicated.
Note this makes a lot of assumptions about the input being perfectly matched and will explode if that's not the case.
I suggest you first convert the values of one of the hashes to hashes, as I will explain. Suppose we create a new b.
newbie = b.transform_values(&:to_h)
#=> {"Us"=>{"1"=>["F", "O"], "2"=>["N"]},
# "Pa"=>{"1"=>["S", "D", "H"], "4"=>["K"]}}
We can now use a and newbie to produce the desired return value.
a.each_with_object({}) do |(k,v),h|
h[k] = v.map do |first, arr|
[first, arr.zip(newbie[k][first]).map { |pair| pair.join('|') }]
end
end
#=> {"Us"=>[["1", ["1|F", "2|O"]], ["2", ["1|N"]]],
# "Pa"=>[["1", ["1|S", "3|D", "5|H"]], ["4", ["7|K"]]]}
If a can be mutated it's slightly easier.
a.each do |k,v|
v.map! do |first, arr|
[first, arr.zip(newbie[k][first]).map { |pair| pair.join('|') }]
end
end
The method Hash#trasform_values made its debut in Ruby v2.4. To support older versions, one compute newbie as follows.
newbie = b.each_with_object({}) {|(k,v),h| h[k] = v.to_h }
In this solution we'll keep the original structure.
I've followed your first try but instead of:
a.merge(b) {|key, a_val, b_val| a_val.merge b_val }
Think about use a new custom merge function like:
c = a.merge(b) {|key, a_val, b_val| myMergeArray(a_val, b_val) }
Then the new merge function is a simple recursive one:
def myMergeArray(a,b,sep = '|')
c = a
c.each_with_index { |e, i|
if c[i].is_a? Array
c[i] = myMergeArray(c[i], b[i], sep)
else
c[i] = c[i] + sep + b[i] if c[i] != b[i]
end
}
return c
end
I've assumed that in case of equal elements, just save one, so e.g. "Y" and "Y" yield just "Y" instead of "Y|Y"
Cheers!

Ruby array += vs push

I have an array of arrays and want to append elements to the sub-arrays. += does what I want, but I'd like to understand why push does not.
Behavior I expect (and works with +=):
b = Array.new(3,[])
b[0] += ["apple"]
b[1] += ["orange"]
b[2] += ["frog"]
b => [["apple"], ["orange"], ["frog"]]
With push I get the pushed element appended to EACH sub-array (why?):
a = Array.new(3,[])
a[0].push("apple")
a[1].push("orange")
a[2].push("frog")
a => [["apple", "orange", "frog"], ["apple", "orange", "frog"], ["apple", "orange", "frog"]]
Any help on this much appreciated.
The issue here is b = Array.new(3, []) uses the same object as the base value for all the array cells:
b = Array.new(3, [])
b[0].object_id #=> 28424380
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380
So when you use b[0].push, it adds the item to "each" sub-array because they are all, in fact, the same array.
So why does b[0] += ["value"] work? Well, looking at the ruby docs:
ary + other_ary → new_ary
Concatenation — Returns a new array built by concatenating the two arrays together to produce a third array.
[ 1, 2, 3 ] + [ 4, 5 ] #=> [ 1, 2, 3, 4, 5 ]
a = [ "a", "b", "c" ]
c = a + [ "d", "e", "f" ]
c #=> [ "a", "b", "c", "d", "e", "f" ]
a #=> [ "a", "b", "c" ]
Note that
x += y
is the same as
x = x + y
This means that it produces a new array. As a consequence, repeated use of += on arrays can be quite inefficient.
So when you use +=, it replaces the array entirely, meaning the array in b[0] is no longer the same as b[1] or b[2].
As you can see:
b = Array.new(3, [])
b[0].push("test")
b #=> [["test"], ["test"], ["test"]]
b[0].object_id #=> 28424380
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380
b[0] += ["foo"]
b #=> [["test", "foo"], ["test"], ["test"]]
b[0].object_id #=> 38275912
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380
If you're wondering how to ensure each array is unique when initializing an array of arrays, you can do so like this:
b = Array.new(3) { [] }
This different syntax lets you pass a block of code which gets run for each cell to calculate its original value. Since the block is run for each cell, a separate array is created each time.
It's because in the second code section, you're selecting the sub-array and pushing to it, if you want an array of array's you need to push the array to the main array.
a = Array.new(3,[])
a.push(["apple"])
a.push(["orange"])
a.push(["frog"])
to get the same result as the first one.
EDIT: I forgot to mention, because you initialize the array with blank array's as elements, you will have three empty elements in front of the pushed elements,

Pushing Hash onto Array: last Hash overwriting previous array elements

I have the following Ruby script:
arr = ['bob', 'jack', 'smith']
array_of_hashes = Array.new
hash = Hash.new
arr.each do |item|
hash.clear
hash[:name] = item
array_of_hashes << hash
end
puts array_of_hashes
This returns an array of hashes, whose :name keys are all from the last element.
[
[0] {
:name => "smith"
},
[1] {
:name => "smith"
},
[2] {
:name => "smith"
}
]
I would expect it to return the following, but I'm stuck trying to figure out why the last Hash element is overwriting all previous array elements:
[
[0] {
:name => "bob"
},
[1] {
:name => "jack"
},
[2] {
:name => "smith"
}
]
EDIT: Thank you all for your answers. It's nice to have some different methods laid out of accomplishing the same thing. I've tested each solution and each one is great. I ended up using the one which was most similar to my original code; but my use case is a simple, local script - it's not being used in an industrial-sized app - in which case, I would probably choose a different solution.
Look at your code carefully, you'll see that you've only created a single instance of Hash. Your iterator keeps blowing away earlier efforts with .clear, setting the element to the current iterate, and adding the reference to that one Hash object to different array locations in array_of_hashes. But at the end of the day, they all point to the same Hash object, and that Hash object contains only the last thing you put into it.
Take note of (and understand) the answer by #pjs and the comment by #tadman as they explain why you are getting the results you are getting.
I would actually instead do something like:
names = ['bob', 'jack', 'smith']
# combine the results into an array of Hashes
array_of_hashes =
names.collect do |item|
{ name: item }
end
# output the results
array_of_hashes # [{name: "bob"}, {name: "jack"},{name:"smith"}]
Some notes:
The Array#collect method will return an array, so you don't need to manually add to an array you've initialized your self.
You can just return a new Hash for each item which will be returned in the collect block
You should try something like
arr = ['bob', 'jack', 'smith']
array_of_hashes = Array.new
arr.each { |item|
hash = Hash.new
hash[:name] = item
array_of_hashes << hash
}
puts array_of_hashes
with a new instance being pushed into the array for every new item.
I would write
[:name].product(arr).map { |pair| Hash[[pair]] }
#=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}]
or (for Ruby v2.0+)
[:name].product(arr).map { |pair| [pair].to_h }
The steps:
a = [:name].product(arr)
#=> [[:name, "bob"], [:name, "jack"], [:name, "smith"]]
enum = a.map
# => #<Enumerator: [[:name, "bob"], [:name, "jack"], [:name, "smith"]]:map>
Note that by sending the method Enumerator#each to enum we obtain the desired result:
enum.each { |pair| [pair].to_h }
#=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}]
(Enumerator#each will invoke Array#each because a.class #=> Array.)
Enumerator#each sets the block variable to the first element of enum:
pair = enum.next
#=> [:name, "bob"]
and the block calculation is performed:
[pair].to_h
#=> [[:name, "bob"]].to_h
#=> {:name=>"bob"}
Next, the second element of enum, [:name, "jack"], is mapped to:
pair = enum.next
#=> [:name, "jack"]
[pair].to_h
#=> [[:name, "jack"]].to_h
#=> {:name=>"jack"}
Lastly,
pair = enum.next
#=> [:name, "smith"]
[pair].to_h
#=> [[:name, "smith"]].to_h
#=> {:name=>"smith"}
You can try this too
>>> names = ['bob', 'jack', 'smith']
>>> names.inject([]){|s,p| s << {name: p}}
=> [{:name=>"bob"}, {:name=>"jack"}, {:name=>"smith"}]

Resources