Indexing elements in hash - ruby

Lets supose that I have a hash:
class MyHash
H = { 1 => [1,2,3,4,5], 2 => [2,7,8,9,10] }
def self.get(id)
# code
end
end
How is the implementation for that method so that I can access the data this way?
element = MyHash.get 1
# => [1,2]
element = MyHash.get 6
# => [2,7]
element = MyHash.get 4
# => [1,5]
element = MyHash.get 5
# => [2,2]
I can write the method "manually" but maybe there is a simpler "rubyist" method to do that

You could do this:
H = { 1 => [1,2,3,4,5], 2 => [6,7,8,9,10] }
HINV = H.flat_map { |k,v| [k].product(v) }
.map.with_index { |(k,v),i| [i,[k,v]] }
.to_h
#=> {0=>[1, 1], 1=>[1, 2], 2=>[1, 3], 3=>[1, 4], 4=>[1, 5],
# 5=>[2, 6], 6=>[2, 7], 7=>[2, 8], 8=>[2, 9], 9=>[2, 10]}
class MyHash
def self.get(id)
HINV[id]
end
end
MyHash.get(1) #=> [1, 2]
MyHash.get(6) #=> [2, 7]
MyHash.get(4) #=> [1, 5]
There is, however, no need for the class MyHash:
HINV[1] #=> [1, 2]
HINV[6] #=> [2, 7]
HINV[4] #=> [1, 5]
If you don't wish the hash to be a constant:
def invert_hash_by_values_index(h)
Hash[h.flat_map { |k,v| [k].product(v) }
.map.with_index { |(k,v),i| [i,[k,v]] }]
end
h = { 1 => [1,2,3,4,5], 2 => [6,7,8,9,10] }
hinv = invert_hash_by_values_index(h)
#=> {0=>[1, 1], 1=>[1, 2], 2=>[1, 3], 3=>[1, 4], 4=>[1, 5],
# 5=>[2, 6], 6=>[2, 7], 7=>[2, 8], 8=>[2, 9], 9=>[2, 10]}
hinv[1] #=> [1, 0]
hinv[6] #=> [2, 0]
hinv[4] #=> [1, 3]
Here I've used the class method Hash::[], rather than instance method Array#to_h, the latter having been added in Ruby 2.0.

Related

Ruby, remove super-arrays

If I have an array of arrays, A, and want to get rid of all arrays in A who also have a sub-array in A, how would I do that. In this context, array_1 is a sub-array of array_2 if array_1 - array_2 = []. In the case that multiple arrays are simply rearranged versions of the same elements, bonus points if you can get rid of all but one of them, but you can handle this however you want if it's easier.
In python, I could easily use comprehension, with A being a set of frozen sets :
A = {a for a in A if all(b-a for b in A-{a})}
Is there a simple way to write this in ruby? I don't care if the order of A or it's arrays are preserved at all. Also, in my program, none of the arrays have duplicate elements, if that makes things any easier/faster.
Example
A = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
# [1,6] is a subarray of [1,3,6], so [1,3,6] should be removed
remove_super_arrays(A)
> A = [[1,6],[1,2],[2,4],[3,5],[2,3,6]]
A = [[1,2,4],[2,3,4],[1,4,5],[2,6]]
# although there is overlap, there are no subarrays, so nothing should be removed
remove_super_arrays(A)
> A = [[1,2,4],[2,3,4],[1,4,5],[2,6]]
A = [[1],[2,1,3],[2,4],[1,4]]
# [1] is a subarray of [2,1,3] and [1,4]
remove_super_arrays(A)
> A = [[1],[2,4]]
Code
def remove_super_arrays(arr)
order = arr.each_with_index.to_a.to_h
arr.sort_by(&:size).reject.with_index do |a,i|
arr[0,i].any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end.sort_by { |a| order[a] }
end
Examples
remove_super_arrays([[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]] )
#=> [[1,6],[1,2],[2,4],[3,5],[2,3,6]]
remove_super_arrays([[1,2,4],[2,3,4],[1,4,5],[2,6]])
#=> [[1,2,4],[2,3,4],[1,4,5],[2,6]]
remove_super_arrays([[1],[2,1,3],[2,4],[1,4]])
#=> [[1],[2,4]]
Explanation
Consider the first example.
arr = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
We first save the positions of the elements of a
order = arr.each_with_index.to_a.to_h # save original order
#=> {[1, 6]=>0, [1, 2]=>1, [2, 4]=>2, [3, 5]=>3, [1, 3, 6]=>4, [2, 3, 6]=>5}
Then reject elements of arr:
b = arr.sort_by(&:size)
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [1, 3, 6], [2, 3, 6]]
c = b.reject.with_index do |a,i|
arr[0,i].any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]
Lastly, reorder c to correspond to the original ordering of the elements of arr.
c.sort_by { |a| order[a] }
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]
which in this case happens to be the same order as the elements of c.
Let's look more carefully at the calculation of c:
enum1 = b.reject
#=> #<Enumerator: [[1, 6], [1, 2], [2, 4], [3, 5], [1, 3, 6],
# [2, 3, 6]]:reject>
enum2 = enum1.with_index
#=> #<Enumerator: #<Enumerator: [[1, 6], [1, 2], [2, 4], [3, 5],
# [1, 3, 6], [2, 3, 6]]:reject>:with_index>
The first element is generated by the enumerator enum2 and passed to the block and assigned as values of the block variables:
a, i = enum2.next
#=> [[1, 6], 0]
a #=> [1, 6]
i #=> 0
The block calculation is then performed:
d = arr[0,i]
#=> []
d.any? { |aa| (aa.size < a.size) && (aa-a).empty? }
#=> false
so a[0] is not rejected. The next pair passed to the block by enum2 is [[1, 2], 1]. That value is retained as well, but let's skip ahead to the last element passed to the block by enum2:
a, i = enum2.next
#=> [[1, 2], 1]
a, i = enum2.next
#=> [[2, 4], 2]
a, i = enum2.next
#=> [[3, 5], 3]
a, i = enum2.next
#=> [[1, 3, 6], 4]
a #=> [1, 3, 6]
i #=> 4
Perform the block calculation:
d = arr[0,i]
#=> [[1, 6], [1, 2], [2, 4], [3, 5]]
d.any? { |aa| (aa.size < a.size) && (aa-a).empty? }
#=> true
As true is returned, a is rejected. In the last calculation the first element of d is passed to the block and the following calculation is performed:
aa = [1, 6]
(aa.size < a.size)
#=> 2 < 3 => true
(aa-a).empty?
#=> ([1, 6] - [1, 3, 6]).empty? => [].empty? => true
As true && true #=> true, a ([1, 3, 6]) is rejected.
Alternative calculation
The following is a closer match to the OP's Python equivalent, but less efficient:
def remove_super_arrays(arr)
arr.select do |a|
(arr-[a]).all? { |aa| aa.size > a.size || (aa-a).any? }
end
end
or
def remove_super_arrays(arr)
arr.reject do |a|
(arr-[a]).any? { |aa| (aa.size < a.size) && (aa-a).empty? }
end
end
This was a nice exercise for me. I have used the logic from here.
My code iterates over each subarray (except the first), then there is the magic substraction using the first index, when it is empty the other array contained both numbers.
def remove_super_arrays(arr)
arr.each_with_index.with_object([]) do |(sub_array, index), result|
next if index == 0
result << sub_array unless (arr.first - sub_array).empty?
end.unshift(arr.first)
end
arr = [[1,6],[1,2],[2,4],[3,5],[1,3,6],[2,3,6]]
p remove_super_arrays(arr)
#=> [[1, 6], [1, 2], [2, 4], [3, 5], [2, 3, 6]]

2D array - How to remove duplicate values but keep the sub arrays separated

I want to remove duplicates from my 2d array but I need to keep sub-arrays separately.
Arrays:
a = [1,2,3,4]
b = [2,3,4,5]
c = [3,4,5,6]
d = [4,5,6,7]
newarray = [[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]]
want to get the following result:
newarraynoduplicates = [[1,2,3,4], [5], [6], [7]]
I have tried the following things
[a|b|c|d] => [[1, 2, 3, 4, 5, 6, 7]]
[a|b|c|d] => [1, 2, 3, 4, 5, 6, 7]
also tried
newarray.uniq! => nil!
The most generic approach would be:
[[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]].
each_with_object([]) { |a, acc| acc << a - acc.flatten }
#⇒ [[1, 2, 3, 4], [5], [6], [7]]
or
[[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]].
reduce([]) { |acc, a| acc << a - acc.flatten }
#⇒ [[1, 2, 3, 4], [5], [6], [7]]
I think you are looking for:
new_array = [a, b - a, c - b - a, d - c - b - a ]
#=> [[1,2,3,4], [5], [6], [7]]
require 'set'
def doit(arr)
s = Set.new
arr.map { |a| a.select { |e| s.add?(e) } }
end
doit [[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]]
#=> [[1, 2, 3, 4], [5], [6], [7]]
doit [[1,2,2,3,4], [2,3,4,5,3], [3,4,5,6], [4,5,6,7]]
#=> [[1, 2, 3, 4], [5], [6], [7]]
See Set#add?. This should be quite efficient as set lookups are very fast.
I did not make many tests, but is seems to work:
newarray = [[1,2,3,4], [2,3,4,5], [3,4,5,6], [4,5,6,7]]
newarray.map.with_index { |e, i| (e - newarray.reverse[newarray.size - i..].flatten).uniq }
#=> [[1, 2, 3, 4], [5], [6], [7]]
Or, to avoid reversing at each loop:
newarray.reverse.then{ |ary| newarray.map.with_index { |e, i| (e - ary[newarray.size - i..].flatten).uniq } }

Transposing rows to columns (with limitations on what methods I can use)

I'm doing a practice problem and the question is to transpose rows to columns in Ruby.
I understand while loops are "rookie-esque" in Ruby, but I think they prefer I use basic methods and control flow statements: while, each, map. Obviously, I can't use Array#transpose. They want me to basically write Array#transpose from scratch.
It keeps telling me:
undefined method `[]=' for nil:NilClass (NoMethodError)
Here is my code:
def transpose(rows)
idx1 = 0
cols = []
while idx1 < rows.count
idx2 = 0
while idx2 < rows[idx1].count
cols[idx1][idx2] = rows[idx2][idx1]
idx2 += 1
end
idx1 += 1
end
return cols
end
puts transpose(rows = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
])
Perhaps this will help you:
def transpose(rows)
idx1 = 0 # => 0
cols = [] # => []
while idx1 < rows.count # => true
idx2 = 0 # => 0
while idx2 < rows[idx1].count # => true
cols[idx1][idx2] = rows[idx2][idx1] # ~> NoMethodError: undefined method `[]=' for nil:NilClass
idx2 += 1
end
idx1 += 1
end
return cols
end
puts transpose(rows = [
[0, 1, 2], # => [0, 1, 2]
[3, 4, 5], # => [3, 4, 5]
[6, 7, 8] # => [6, 7, 8]
])
# ~> NoMethodError
# ~> undefined method `[]=' for nil:NilClass
That was created using "Seeing Is Believing" in Sublime Text 2.
Breaking it down, here's where you're going wrong:
cols = [] # => []
cols[0] # => nil
cols[0][0] # ~> NoMethodError: undefined method `[]' for nil:NilClass
You can't use a sub-index on a nil. The first index doesn't exist in the empty array.
In your outer while loop you need to make cols[idx1] an empty array, else it is nil in your inner while loop:
while idx1 < rows.count
cols[idx1] = []
idx2 = 0
while idx2 < rows[idx1].count
# ...
end
end
You could do that using Array#new with a block:
Array.new(rows.size) { |i| Array.new(rows.size) { |j| rows[j][i] } }
#=> [[0, 3, 6], [1, 4, 7], [2, 5, 8]]
Alternatively, you could use methods from the class Matrix, for which you need:
require 'matrix'
Here's an easy way that does not use Array#transpose, though it may not satisfy the spirit of the question:
Matrix[*rows].transpose.to_a
#=> [[0, 3, 6], [1, 4, 7], [2, 5, 8]]
However, you could do this:
Matrix.build(rows.size) { |i,j| rows[j][i] }.to_a
#=> [[0, 3, 6], [1, 4, 7], [2, 5, 8]]
Another way to do this for comparison using nested Enumerable#maps:
rows = [[0, 1, 2],
[3, 4, 5],
[6, 7, 8]]
def my_transpose(matrix)
matrix.first.size.times.map { |column| matrix.map { |row| row[column] } }
end
my_transpose(rows)
# => [[0, 3, 6], [1, 4, 7], [2, 5, 8]]
my_transpose(my_transpose(rows))
# => [[0, 1, 2], [3, 4, 5], [6, 7, 8]]

How to merge embedded array with array in Ruby

I have arrays and nested arrays, such that:
a = [1,2]
b = [[3,4],[5,6]]
c = [7,8]
What's the best way to create
d = [[1,2],[3,4],[5,6],[7,8]]
in Ruby?
UPDATE:
The goal is to create a method below:
def foo([a,b,c])
--some logic that iterates through each array--
end
def foo(xss)
xss.flat_map { |xs| xs.first.is_a?(Array) ? xs : [xs] }
en
foo([a, b, c]) #=> [[1, 2], [3, 4], [5, 6], [7, 8]]
This will work assuming inputs are always arrays. Might have some side effects if any input is formatted differently.
def foo(*args)
args.map {|a| a.first.is_a?(Array) ? a : [a]}.inject([], :+)
end
foo([1,2], [[3,4],[5,6]], [7,8]) #=> [[1, 2], [3, 4], [5, 6], [7, 8]]
def foo(a,b,c)
final = []
a.first.is_a?(Array) ? a.each { |x| final << x } : final << a
b.first.is_a?(Array) ? b.each { |x| final << x } : final << b
c.first.is_a?(Array) ? c.each { |x| final << x } : final << c
final
end
I'd do :
a = [1,2]
b = [[3,4],[5,6]]
c = [7,8]
final_ary = [a,b,c].inject([]) do |out_ary,ary|
if ary.first.is_a?(Array)
out_ary.concat(ary)
else
out_ary.push(ary)
end
end
final_ary
# => [[1, 2], [3, 4], [5, 6], [7, 8]]
def foo (*args)
args.flatten.each_slice(2).to_a
end
foo(a,b,c) # => [[1, 2], [3, 4], [5, 6], [7, 8]]
A general solution:
def extract(arr, result=[])
return result if arr.empty?
arr.each {|e| (e.first.is_a? Array) ? result=extract(e,result) : result << e}
end
p extract( [ [1,2],[3,4,5],[6,7] ] )
#=> [[1, 2], [3, 4, 5], [6, 7]]
p extract( [ [1,2],[ [3,4],[5,6] ],[7,8]] )
#=> [[1, 2], [3, 4], [5, 6], [7, 8]]
p extract( [ [1,2],[ [3,4],[ [5,6],[7,8] ] ],[9,10,11] ] )
#=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10, 11]]
p extract( [ [1,2],[ [3,4],[ [5,6],[ [7,8],[9,10] ] ] ],[11,12] ])
#=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]

Array of arrays into array of hashes

i want to convert in ruby
[[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
into
[{1=>1}, {2=>3}, {3=>5}, {4=>1}, {1=>2}, {2=>3}, {3=>5}, {4=>1}]
and after this to obtain sum of all different keys:
{1=>3,2=>6,3=>10,4=>2}
For the second question
sum = Hash.new(0)
original_array.each{|x, y| sum[x] += y}
sum # => {1 => 3, 2 => 6, 3 => 10, 4 => 2}
Functional approach:
xs = [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
Hash[xs.group_by(&:first).map do |k, pairs|
[k, pairs.map { |x, y| y }.inject(:+)]
end]
#=> {1=>3, 2=>6, 3=>10, 4=>2}
Using Facets is much simpler thanks to the abstractions map_by (a variation of group_by) and mash (map + Hash):
require 'facets'
xs.map_by { |k, v| [k, v] }.mash { |k, vs| [k, vs.inject(:+)] }
#=> {1=>3, 2=>6, 3=>10, 4=>2}
You don't need the intermediate form.
arrays = [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
aggregate = arrays.each_with_object Hash.new do |(key, value), hash|
hash[key] = hash.fetch(key, 0) + value
end
aggregate # => {1=>3, 2=>6, 3=>10, 4=>2}
arr= [[1, 1], [2, 3], [3, 5], [4, 1], [1, 2], [2, 3], [3, 5], [4, 1]]
final = Hash.new(0)
second_step = arr.inject([]) do |arr,inner|
arr << Hash[*inner]
final[inner.first] += inner.last
arr
end
second_step
#=> [{1=>1}, {2=>3}, {3=>5}, {4=>1}, {1=>2}, {2=>3}, {3=>5}, {4=>1}]
final
#=> {1=>3, 2=>6, 3=>10, 4=>2}
if you directly only need the last step
arr.inject(Hash.new(0)){|hash,inner| hash[inner.first] += inner.last;hash}
=> {1=>3, 2=>6, 3=>10, 4=>2}

Resources