Combine array values based on first element of array using ruby - ruby

Let's say I have the following code:
arr = [["a",1],["a",2],["b",1],["b",2]]
I want to merge the array that should like
arr1 = [["a",1,2],["b",1,2]]
Thanks in advance

Using Enumerable#group_by:
arr = [["a",1],["a",2],["b",1],["b",2]]
arr.group_by(&:first).map{ |key, value|
[key, *value.map(&:last)]
}
# => [["a", 1, 2], ["b", 1, 2]]

Related

Inverting a hash value (that's an array) into new individual keys

I have the following:
lumpy_hash = { 1 => ["A", "B"] }
then if I invoke Hash#invert on this hash, I'd like to get:
lumpy_hash = {"A" => 1, "B" => 1}
I don't get that from using Hash#invert. Any ideas on doing this? I'm not sure if I should try Hash#map or Hash#invert.
There are many ways to do this. Here is one:
Hash[lumpy_hash.map { |k,v| v.product([k]) }.first]
#=> {"A"=>1, "B"=>1}
I don't think the method Hash#invert is useful here.
The steps:
enum = lumpy_hash.map
#=> #<Enumerator: {1=>["A", "B"]}:map>
k,v = enum.next
#=> [1, ["A", "B"]]
k #=> 1
v #=> ["A", "B"]
a = v.product([k])
#=> ["A", "B"].product([1])
#=> [["A", 1], ["B", 1]]
Hash[a]
#=> {"A"=>1, "B"=>1}
Here's another way that makes use of a hash's default value. This one is rather interesting:
key,value = lumpy_hash.to_a.first
#=> [1, ["A","B"]]
Hash.new { |h,k| h[k]=key }.tap { |h| h.values_at(*value) }
#=> {"A"=>1,"B"=>1}
Object#tap passes an empty hash to its block, assigning it to the block variable h. The block returns h after adding three key-value pairs, each having a value equal to the hash's default value. It adds the pairs merely by computing the values of keys the hash doesn't have!
Here's another, more pedestrian, method:
lumpy_hash.flat_map{|k,vs| vs.map{|v| {v => k}}}.reduce(&:merge)
=> {"A"=>1, "B"=>1}

Ruby array of two dimensional arrays, search/lookup?

This may be very simple, but I don't know all of Ruby's array functions.
If I have a given array like:
values = [["a", 1], ["b", 3], ["c", 7], ... etc ]
I would like two functions:
A function that, when I give it "b", gives me 3.
The other way around, a function that when I give it 3, gives me "b".
There must be an easy way?
Hash[values]["b"] # => 3
Hash[values.map(&:reverse)][3] # => "b"
My first question is: Does this have to be an array? Hash is designed for this and has key / value lookup built-in.
You can create a Hash from an array by doing:
hash = Hash[values]
Then use hash["a"] # => 1
For the reverse, do: hash.key(1) # => "a"
The first is easy to achieve, by converting your array to a Hash with:
value_hash = Hash[values]
And access this with:
value_hash['b'] # => 3
For the other way around I would first like to know if you are sure that is is a unique request? So are both 'a','b','c',... and 1,3,7... etc. unique?
hash = array.to_h => Converts your array to a hash
hash[key] = value => Get the value associated with the key
hash.invert[key] = value => This method inverts your hash and you can select values
Yeah a hash is the answer, if you don't have duplicate keys of course. Otherwise you can use Array#assoc#rassoc which searches an array of arrays matching the first and last elements respectively:
ary = [["A", 1], ["B", 2], ["C", 3], ["D", 4], ["E", 5], ["F", 6], ["G", 6]]
ary.assoc('A') => ["A", 1]
ary.rassoc('3') => ["C", 3]
Note: these methods return the first matching array, not all of them.
See more at http://www.ruby-doc.org/core-2.1.2/Array.html
I see no point in creating a hash to locate a single value. Why not the simple, direct approach?
values = [["a", 1], ["b", 3], ["c", 7]]
values.find { |l,n| l=='b' }.last #=> 3
values.find { |l,n| n==3 }.first #=> "b"
Of course, neither of these deal with multiple values.

How can I push keys to an unsorted array?

I'm trying to create an array of the keys of an ordered hash. I want them to be listed in the same order in both the array and the hash. I have this hash.
h = { "a" => 3, "b" => 1, "c" = 4, "d" = 2 }
What I want is this array.
arr = ["b", "d", "a", "c"]
I have
h.sort_by { |k, v| v}
h.keys
but that returns the keys in alphabetical order. What can I do to keep them in the order of the sorted hash?
h.sort_by{|k,v| v} will give you [["b", 1], ["d", 2], ["a", 3], ["c", 4]], then use .map to get the key.
h.sort_by{|k,v| v}.map &:first
h = { "a" => 3, "b" => 1, "c" => 4, "d" => 2 }
p h.sort_by(&:last).map(&:first) #=> ["b", "d", "a", "c"]
You may try this also,
h = { "a" => 3, "b" => 1, "c" => 4, "d" => 2 }
Hash[h.sort_by{|k,v| v}].keys
#=> ["b", "d", "a", "c"]
This code
h.sort_by { |k,v| v}
h.keys
doesn't work because the sort_by method doesn't sort the original array, it returns a new sorted array, where each value is a (key, value) pair from the original hash:
[["b", 1], ["d", 2], ["a", 3], ["c", 4]]
If you're using Ruby 2.1.1, you can then just call to_h on the array, which will re-map the key/value pairs back into a hash:
h.sort_by { |k, v| v}.to_h.keys

Removing all elements of a column in a two-dimensional array

I have this array:
arr = [["a","b","c"],[2,3,5],[3,6,8],[1,3,1]]
which is representing a prawn-table containing columns "a", "b", and "c".
How do I remove the entire column "c" with all its values, 5, 8, 1?
Maybe there are useful hints in "Create two-dimensional arrays and access sub-arrays in Ruby" and "difficulty modifying two dimensional ruby array" but I can't transfer them to my problem.
Just out of curiosity sake here is an another approach (one-liner):
arr.transpose[0..-2].transpose
arr = [["a","b","c"],[2,3,5],[3,6,8],[1,3,1]]
i = 2 # the index of the column you want to delete
arr.each do |row|
row.delete_at i
end
=> [["a", "b"], [2, 3], [3, 6], [1, 3]]
class Matrix < Array
def delete_column(i)
arr.each do |row|
row.delete_at i
end
end
end
Since it is just the last value you can use Array#pop:
arr.each do |a|
a.pop
end
Or find the index of "c" and delete all elements at that index:
c_index = arr[0].index "c"
arr.each do |a|
a.delete_at c_index
end
Or using map:
c_index = arr[0].index "c"
arr.map{|a| a.delete_at c_index }
arr.map { |row| row.delete_at(2) }
#=> ["c", 5, 8, 1]
That's if you really want to remove the last column so it's not in the original array anymore. If you just want to return it while leaving arr intact:
arr.map { |row| row[2] }
#=> ["c", 5, 8, 1]
If you want to delete all the elements in a column corresponding to a particular heading:
if index = arr.index('c') then
arr.map { |row| row[index] } # or arr.map { |row| row.delete_at(index) }
end
# Assuming first row are headers
arr = [["a","b","c"],[2,3,5],[3,6,8],[1,3,1]]
col = arr.first.index "c"
arr.each { |a| a.delete_at(col) }
Assuming the array's first element is always an array of column names, then you could do:
def delete_column(col, array)
index = array.first.index(col)
return unless index
array.each{ |a| a.delete_at(index) }
end
It will modify the passed-in array. You shouldn't assign its output to anything.
arr = [["a","b","c"],[2,3,5],[3,6,8],[1,3,1]]
arr.map(&:pop)
p arr #=> [["a", "b"], [2, 3], [3, 6], [1, 3]]
I had a more generic need to remove one or more columns that matched a text pattern (not just delete the last column).
col_to_delete = 'b'
arr = [["a","b","c"],[2,3,5],[3,6,8],[1,3,1]]
arr.transpose.collect{|a| a if (a[0] != col_to_delete)}.reject(&:nil?).transpose
=> [["a", "c"], [2, 5], [3, 8], [1, 1]]

Converting an array of keys and an array of values into a hash in Ruby

I have two arrays like this:
keys = ['a', 'b', 'c']
values = [1, 2, 3]
Is there a simple way in Ruby to convert those arrays into the following hash?
{ 'a' => 1, 'b' => 2, 'c' => 3 }
Here is my way of doing it, but I feel like there should be a built-in method to easily do this.
def arrays2hash(keys, values)
hash = {}
0.upto(keys.length - 1) do |i|
hash[keys[i]] = values[i]
end
hash
end
The following works in 1.8.7:
keys = ["a", "b", "c"]
values = [1, 2, 3]
zipped = keys.zip(values)
=> [["a", 1], ["b", 2], ["c", 3]]
Hash[zipped]
=> {"a"=>1, "b"=>2, "c"=>3}
This appears not to work in older versions of Ruby (1.8.6). The following should be backwards compatible:
Hash[*keys.zip(values).flatten]
Another way is to use each_with_index:
hash = {}
keys.each_with_index { |key, index| hash[key] = values[index] }
hash # => {"a"=>1, "b"=>2, "c"=>3}
The same can be done using Array#transpose method. If you are using Ruby version >= 2.1, you can take the advantage of the method Array#to_h, otherwise use your old friend, Hash::[]
keys = ['a', 'b', 'c']
values = [1, 2, 3]
[keys, values].transpose.to_h
# => {"a"=>1, "b"=>2, "c"=>3}
Hash[[keys, values].transpose]
# => {"a"=>1, "b"=>2, "c"=>3}
Try this, this way the latter one d will overwrite the former one c
irb(main):001:0> hash = Hash[[[1,2,3,3], ['a','b','c','d']].transpose]
=> {1=>"a", 2=>"b", 3=>"d"}
irb(main):002:0>

Resources