Combine two arrays in Ruby? - ruby-on-rails

Suppose we have two arrays:
foo = [1, 2] # can be mapped in [{id: 1}, {id: 2}]
bar = [{id: 2}, {id: 4}]
As a result I need to get following array:
res = [ [1, nil], [2, {id: 2}], [nil, {id: 4}] ]
# Is also ok [ [{id: 1}, nil], [{id: 2}, {id: 2}], [nil, {id: 4}] ]
I there any standard Ruby method to get such combinations? At first I'm looking for Ruby way, elegant, short solution.
In other words, I need to get an array diff map, which shows absent and newly added elements.

The elements:
elements = (foo + bar.map{ |h| h.values.first }).uniq
#=> [1, 2, 4]
and the combinations:
elements.map { |i| [foo.include?(i) ? i : nil, bar.include?({id: i}) ? {id: i} : nil] }
#=> [[1, nil], [2, {:id=>2}], [nil, {:id=>4}]]
Or as Sebastian suggested, you can also use #find instead of #includes?:
elements.map { |i| [foo.find { |e| e == i }, bar.find { |e| e == { id: i } }] }

I would transform the two arrays to lookup hashes. Then collect all the ids to iterate. Map the ids into the foo and bar values, then zip them together.
foo_lookup = foo.to_h { |id| [id, {id: id}] } # change `{id: id}` into `id` for the other output
bar_lookup = bar.to_h { |item| [item[:id], item] }
ids = foo_lookup.keys | bar_lookup.keys
res = ids.map(&foo_lookup).zip(ids.map(&bar_lookup))
#=> [[{:id=>1}, nil], [{:id=>2}, {:id=>2}], [nil, {:id=>4}]]

Just another option.
foo = [1, 2]
bar = [{id: 2}, {id: 4}]
(foo + bar).group_by { |e| e.is_a?(Hash) ? e[:id] : e }.values.map { |e| e.size == 1 ? e << nil : e }
#=> [[1, nil], [2, {:id=>2}], [{:id=>4}, nil]]
The first part returns
(foo + bar).group_by { |e| e.is_a?(Hash) ? e[:id] : e }.values
#=> [[1], [2, {:id=>2}], [{:id=>4}]]
If you need to sort the sub arrays, just map using Array#unshift or Array#push (prepend or append nil) depending in the element already there.

Related

How to convert array of 3 element arrays in to a hash where key is first 2 elements

My problem is that I need to do efficient lookups of if a 2 element array and their corresponding value is nil. So if I have the following arrays:
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101]
]
I want something like
h = {
[1, 2] => 100,
[3, 4] => nil,
[5, 6] => 101
}
So I can do something like:
error = []
arr2 = [
[1,2],
[3,4],
[7,8]
]
arr2.each do |val|
if h.include?(val)
if h[val] == nil
error << "Value is nil"
else
# Do something
end
else
error << "Key doesn't exist"
end
end
Given that overwriting or ignoring duplicates is acceptable per your comment.
You can use Enumerable#each_with_object to iterate the Array and create a Hash like so
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101],
[1, 2, nil],
]
arr1.each_with_object({}) do |(*first_two,last),obj|
obj[first_two] = last
end
#=> {[1, 2]=>nil, [3, 4]=>nil, [5, 6]=>101}
You can ignore duplicates in a similar fashion
arr1.each_with_object({}) do |(*first_two,last),obj|
obj[first_two] = last unless obj.key?(first_two)
end
#=> {[1, 2]=>100, [3, 4]=>nil, [5, 6]=>101}
Explanation:
each_with_object({}) will pass each element of of arr1 to the block along with an object (a Hash in this case)
(*first_two,last),obj - *first_two will collect everything up to last and obj is our Hash
obj[first_two] = last simple Hash key assignment
each_with_object returns the object (obj Hash in this case)
Update as recommended by #Stefan in ruby >= 2.7 you could also use
arr1.to_h {|*first_two,last| [first_two, last] }
This version will overwrite keys
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101]
]
result = {}
arr1.each { |i| result[i.first(2)] = i.last }
=> {[1, 2]=>100, [3, 4]=>nil, [5, 6]=>101}
You can destructure every subarray during mapping and then convert result to hash with Array#to_h method
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101],
[1, 2, nil],
]
arr1.map { |*first_two, last| [first_two, last] }.to_h
# => {[1, 2]=>nil, [3, 4]=>nil, [5, 6]=>101}
Duplicates will be overwritten
In case if you need for two last values as key:
arr.map { |b| { b.shift => b }.invert }

Distribute items into containers in twos - Rails

I have a list of 10 items -- it is an array of hashes.
[{ id: 1, name: 'one'}, { id: 2, name: 'two' } .. { id: 10, name: 'ten' }]
I also have a random number of containers -- let's say 3, in this case. These containers are hashes with array values.
{ one: [], two: [], three: [] }
What I want to do, is iterate over the containers and drop 2 items at a time resulting in:
{
one: [{id:1}, {id:2}, {id:7}, {id:8}],
two: [{id:3}, {id:4}, {id:9}, {id:10}],
three: [{id:5}, {id:6}]
}
Also, if the item list is an odd number (11), the last item is still dropped into the next container.
{
one: [{id:1}, {id:2}, {id:7}, {id:8}],
two: [{id:3}, {id:4}, {id:9}, {id:10}],
three: [{id:5}, {id:6}, {id:11}]
}
note: the hashes are snipped here so it's easier to read.
My solution is something like this: (simplified)
x = 10
containers = { one: [], two: [], three: [] }
until x < 1 do
containers.each do |c|
c << 'x'
c << 'x'
end
x -= 2
end
puts containers
I'm trying to wrap my head around how I can achieve this but I can't seem to get it to work.
Round-robin pair distribution into three bins:
bins = 3
array = 10.times.map { |i| i + 1 }
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array.
each_slice(2). # divide into pairs
group_by. # group into bins
with_index { |p, i| i % bins }. # round-robin style
values. # get rid of bin indices
each(&:flatten!) # join pairs in each bin
Completely different approach, stuffing bins in order:
base_size, bins_with_extra = (array.size / 2).divmod(bins)
pos = 0
bins.times.map { |i|
length = 2 * (base_size + (i < bins_with_extra ? 1 : 0)) # how much in this bin?
array[pos, length].tap { pos += length } # extract and advance
}
# => [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
If you absolutely need to have this in a hash,
Hash[%i(one two three).zip(binned_array)]
# => {:one=>[1, 2, 7, 8], :two=>[3, 4, 9, 10], :three=>[5, 6]}
The lovely (but likely not as performant) solution hinted at by Stefan Pochmann:
bins.times.with_object(array.to_enum).map { |i, e|
Array.new(2 * (base_size + (i < bins_with_extra ? 1 : 0))) { e.next }
}
This is just to show a different approach (and I would probably not use this one myself).
Given an array of items and the containers hash:
items = (1..10).to_a
containers = { one: [], two: [], three: [] }
You could dup the array (in order not to modify the original one) and build an enumerator that cycles each_value in the hash:
array = items.dup
enum = containers.each_value.cycle
Using the above, you can shift 2 items off the array and push them to the next container until the array is emtpy?:
enum.next.push(*array.shift(2)) until array.empty?
Result:
containers
#=> {:one=>[1, 2, 7, 8], :two=>[3, 4, 9, 10], :three=>[5, 6]}
You can use Enumerable#each_slice to iterate over a range from 0 to 10 in 3s and then append to an array of arrays:
containers = [
[],
[],
[]
]
(1...10).each_slice(3) do |slice|
containers[0] << slice[0]
containers[1] << slice[1]
containers[2] << slice[2]
end
p containers
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

Sum and Average Array of Array

I am trying to sum array of array and get average at the same time. The original data is in the form of JSON. I have to parse my data to array of array in order to render the graph. The graph does not accept array of hash.
I first convert the output to JSON using the definition below.
ActiveSupport::JSON.decode(#output.first(10).to_json)
And the result of the above action is shown below.
output =
[{"name"=>"aaa", "job"=>"a", "pay"=> 2, ... },
{"name"=>"zzz", "job"=>"a", "pay"=> 4, ... },
{"name"=>"xxx", "job"=>"a", "pay"=> 6, ... },
{"name"=>"yyy", "job"=>"a", "pay"=> 8, ... },
{"name"=>"aaa", "job"=>"b", "pay"=> 2, ... },
{"name"=>"zzz", "job"=>"b", "pay"=> 4, ... },
{"name"=>"xxx", "job"=>"b", "pay"=> 6, ... },
{"name"=>"yyy", "job"=>"b", "pay"=> 10, ... },
]
Then I retrieved the job and pay by converting to array of array.
ActiveSupport::JSON.decode(output.to_json).each { |h|
a << [h['job'], h['pay']]
}
The result of the above operation is as below.
a = [["a", 2], ["a", 4], ["a", 6], ["a", 8],
["b", 2], ["b", 4], ["b", 6], ["b", 10]]
The code below will give me the sum of each element in the form of array of array.
a.inject({}) { |h,(job, data)| h[job] ||= 0; h[job] += data; h }.to_a
And the result is as below
[["a", 20], ["b", 22]]
However, I am trying to get the average of the array. The expected output is as below.
[["a", 5], ["b", 5.5]]
I can count how many elements in an array and divide the sum array by the count array. I was wondering if there is an easier and more efficient way to get the average.
output = [
{"name"=>"aaa", "job"=>"a", "pay"=> 2 },
{"name"=>"zzz", "job"=>"a", "pay"=> 4 },
{"name"=>"xxx", "job"=>"a", "pay"=> 6 },
{"name"=>"yyy", "job"=>"a", "pay"=> 8 },
{"name"=>"aaa", "job"=>"b", "pay"=> 2 },
{"name"=>"zzz", "job"=>"b", "pay"=> 4 },
{"name"=>"xxx", "job"=>"b", "pay"=> 6 },
{"name"=>"yyy", "job"=>"b", "pay"=> 10 },
]
output.group_by { |obj| obj['job'] }.map do |key, list|
[key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end
The group_by method will transform your list into a hash with the following structure:
{"a"=>[{"name"=>"aaa", "job"=>"a", "pay"=>2}, ...], "b"=>[{"name"=>"aaa", "job"=>"b", ...]}
After that, for each pair of that hash, we want to calculate the mean of its 'pay' values, and return a pair [key, mean]. We use a map for that, returning a pair with:
They key itself ("a" or "b").
The mean of the values. Note that the values list has the form of a list of hashes. To retrieve the values, we need to extract the last element of each pair; that's what list.map { |obj| obj['pay'] } is used for. Finally, calculate the mean by suming all elements with .reduce(:+) and dividing them by the list size as a float.
Not the most efficient solution, but it's practical.
Comparing the answer with #EricDuminil's, here's a benchmark with a list of size 8.000.000:
def Wikiti(output)
output.group_by { |obj| obj['job'] }.map do |key, list|
[key, list.map { |obj| obj['pay'] }.reduce(:+) / list.size.to_f]
end
end
def EricDuminil(output)
count_and_sum = output.each_with_object(Hash.new([0, 0])) do |hash, mem|
job = hash['job']
count, sum = mem[job]
mem[job] = count + 1, sum + hash['pay']
end
result = count_and_sum.map do |job, (count, sum)|
[job, sum / count.to_f]
end
end
require 'benchmark'
Benchmark.bm do |x|
x.report('Wikiti') { Wikiti(output) }
x.report('EricDuminil') { EricDuminil(output) }
end
user system total real
Wikiti 4.100000 0.020000 4.120000 ( 4.130373)
EricDuminil 4.250000 0.000000 4.250000 ( 4.272685)
This method should be reasonably efficient. It creates a temporary hash with job name as key and [count, sum] as value:
output = [{ 'name' => 'aaa', 'job' => 'a', 'pay' => 2 },
{ 'name' => 'zzz', 'job' => 'a', 'pay' => 4 },
{ 'name' => 'xxx', 'job' => 'a', 'pay' => 6 },
{ 'name' => 'yyy', 'job' => 'a', 'pay' => 8 },
{ 'name' => 'aaa', 'job' => 'b', 'pay' => 2 },
{ 'name' => 'zzz', 'job' => 'b', 'pay' => 4 },
{ 'name' => 'xxx', 'job' => 'b', 'pay' => 6 },
{ 'name' => 'yyy', 'job' => 'b', 'pay' => 10 }]
count_and_sum = output.each_with_object(Hash.new([0, 0])) do |hash, mem|
job = hash['job']
count, sum = mem[job]
mem[job] = count + 1, sum + hash['pay']
end
#=> {"a"=>[4, 20], "b"=>[4, 22]}
result = count_and_sum.map do |job, (count, sum)|
[job, sum / count.to_f]
end
#=> [["a", 5.0], ["b", 5.5]]
It requires 2 passes, but the created objects aren't big. In comparison, calling group_by on a huge array of hashes isn't very efficient.
How about this (Single pass iterative average calculation)
accumulator = Hash.new {|h,k| h[k] = Hash.new(0)}
a.each_with_object(accumulator) do |(k,v),obj|
obj[k][:count] += 1
obj[k][:sum] += v
obj[k][:average] = (obj[k][:sum] / obj[k][:count].to_f)
end
#=> {"a"=>{:count=>4, :sum=>20, :average=>5.0},
# "b"=>{:count=>4, :sum=>22, :average=>5.5}}
Obviously average is just recalculated on every iteration but since you asked for them at the same time this is probably as close as you are going to get.
Using your "output" instead looks like
output.each_with_object(accumulator) do |h,obj|
key = h['job']
obj[key][:count] += 1
obj[key][:sum] += h['pay']
obj[key][:average] = (obj[key][:sum] / obj[key][:count].to_f)
end
#=> {"a"=>{:count=>4, :sum=>20, :average=>5.0},
# "b"=>{:count=>4, :sum=>22, :average=>5.5}}
as Sara Tibbetts comment suggests, my first step would be to convert it like this
new_a = a.reduce({}){ |memo, item| memo[item[0]] ||= []; memo[item[0]] << item[1]; memo}
which puts it in this format
{a: [2, 4, 6, 8], b: [2, 4, 6, 20]}
you can then use slice to filter the keys you want
new_a.slice!(key1, key2, ...)
Then do another pass through to do get the final format
new_a.reduce([]) do |memo, (k,v)|
avg = v.inject{ |sum, el| sum + el }.to_f / v.size
memo << [k,avg]
memo
end
I elected to use Enumerable#each_with_object with the object being an array of two hashes, the first to compute totals, the second to count the number of numbers that are totalled. Each hash is defined Hash.new(0), zero being the default value. See Hash::new for a fuller explanation, In short, if a hash defined h = Hash.new(0) does not have a key k, h[k] returns 0. (h is not modified.) h[k] += 1 expands to h[k] = h[k] + 1. If h does not have a key k, h[k] on the right of the equality returns 0.1
output =
[{"name"=>"aaa", "job"=>"a", "pay"=> 2},
{"name"=>"zzz", "job"=>"a", "pay"=> 4},
{"name"=>"xxx", "job"=>"a", "pay"=> 6},
{"name"=>"yyy", "job"=>"a", "pay"=> 8},
{"name"=>"aaa", "job"=>"b", "pay"=> 2},
{"name"=>"zzz", "job"=>"b", "pay"=> 4},
{"name"=>"xxx", "job"=>"b", "pay"=> 6},
{"name"=>"yyy", "job"=>"b", "pay"=>10}
]
htot, hnbr = output.each_with_object([Hash.new(0), Hash.new(0)]) do |f,(g,h)|
s = f["job"]
g[s] += f["pay"]
h[s] += 1
end
htot.merge(hnbr) { |k,o,n| o.to_f/n }.to_a
#=> [["a", 5.0], ["b", 5.5]]
If .to_a at the end is dropped the the hash {"a"=>5.0, "b"=>5.5} is returned. The OP might find that more useful than the array.
I've used the form of Hash#merge that uses a block to determine the values of keys that are present in both hashes being merged.
Note that htot={"a"=>20, "b"=>22} and hnbr=>{"a"=>4, "b"=>4}.
1 If the reader is wondering why h[k] on the left of = doesn't return zero as well, it's a different method: Hash#[]= versus Hash#[]

How does Ruby's map method work in this case?

I got the mistake when I want to add doubled values to an array:
arr = [1,2,3]
def my_mistake(arr)
result = Array.new
arr.map { |element| result << element * 2 }
end
#=> [[2, 4, 6], [2, 4, 6], [2, 4, 6]]
def solution(arr)
arr.map { |element| element * 2 }
end
#=> [2,4,6]
However, come back to my mistake and the definition of map method in Ruby.
Invokes the given block once for each element of self. Creates a new array containing the values returned by the block.
I think my_mistake method has to return [[2], [2, 4], [2, 4, 6]] but it doesn't.
Everyone can explain this case for me ?
The resulting array will contain three occurrences of the same reference to the same array, as result is the result of the expression result << element * 2. So the result of the map is (kind of) [result, result, result]. These all point to the same content, which is the content of result at the end of the process ([2, 4, 6]).
What you expected would be achieved if you clone the array at each point, so that every resulting element would point to a different array, and each addition would not affect the previously computed arrays:
arr.map { |element| (result << element * 2).clone }
=> [[2], [2, 4], [2, 4, 6]]
.map returns the last evaluated expression, so no need for the result << part there. Here's something that worked for me:
def my_mistake(arr)
result = [] # '= []' is same like '= Array.new', look-up "literal constructors in Ruby"
new_arr = [] # same like new_arr = Array.new
until arr.empty?
new_arr << arr.shift # we add each element of arr, one by one, starting from the beginning
output << new_arr.map { |e| e * 2} # we calculate *2 for each element
end
return result
end
p my_mistake(arr) #=> [[2], [2, 4], [2, 4, 6]]
If you're nto sure how this works, try putting "p output" after the 6th line:
def my_mistake(arr)
output = []
new_arr = []
until arr.empty?
new_arr << arr.shift
output << new_arr.map { |e| e * 2}
p output
end
return output
end
my_mistake(arr)
The program will print:
[[2]]
[[2], [2, 4]]
[[2], [2, 4], [2, 4, 6]]

Ruby/Rails: get elements from array where indices are divisible by x

How could I implement this? I think my solution is very dirty, and I would like to do it better. I think there is an easy way to do this in Ruby, but I can't remember. I want to use it with Rails, so if Rails provides something similar that's ok, too. usage should be like this:
fruits = ['banana', 'strawberry', 'kiwi', 'orange', 'grapefruit', 'lemon', 'melon']
# odd_fruits should contain all elements with odd indices (index % 2 == 0)
odd_fruits = array_mod(fruits, :mod => 2, :offset => 0)
# even_fruits should contain all elements with even indices (index % 2 == 1)
even_fruits = array_mod(fruits, :mod => 2, :offset => 1)
puts odd_fruits
banana
kiwi
grapefruit
melon
puts even_fruits
strawberry
orange
lemon
******* EDIT *******
for those wo want to know, that is what i finally did:
in a rails project, i created a new file config/initializers/columnize.rb which looks like this:
class Array
def columnize args = { :columns => 1, :offset => 0 }
column = []
self.each_index do |i|
column << self[i] if i % args[:columns] == args[:offset]
end
column
end
end
Rails automatically loads these files immediately after Rails has been loaded. I also used the railsy way of supplying arguments to a method, because i think that serves the purpose of better readable code, and i'm a good-readable-code-fetishist :) I extended the core class "Array", and now i can do things like the following with every array in my project:
>> arr = [1,2,3,4,5,6,7,8]
=> [1, 2, 3, 4, 5, 6, 7, 8]
>> arr.columnize :columns => 2, :offset => 0
=> [1, 3, 5, 7]
>> arr.columnize :columns => 2, :offset => 1
=> [2, 4, 6, 8]
>> arr.columnize :columns => 3, :offset => 0
=> [1, 4, 7]
>> arr.columnize :columns => 3, :offset => 1
=> [2, 5, 8]
>> arr.columnize :columns => 3, :offset => 2
=> [3, 6]
I will now use it to display entries from the database in different columns in my views. What i like about it, is that i don't have to call any compact methods or stuff, because rails complains when you pass a nil object to a view. now it just works. I also thought about letting JS do all that for me, but i like it better this way, working with the 960 Grid system (http://960.gs)
fruits = ["a","b","c","d"]
even = []
x = 2
fruits.each_index{|index|
even << fruits[index] if index % x == 0
}
odds = fruits - even
p fruits
p even
p odds
["a", "b", "c", "d"]
["a", "c"]
["b", "d"]
def array_mod(arr, mod, offset = 0)
arr.shift(offset)
out_arr = []
arr.each_with_index do |val, idx|
out_arr << val if idx % mod == 0
end
out_arr
end
Usage:
>> fruits = ['banana', 'strawberry', 'kiwi', 'orange', 'grapefruit', 'lemon', 'melon']
>> odd_fruits = array_mod(fruits, 2)
=> ["banana", "kiwi", "grapefruit", "melon"]
>> even_fruits = array_mod(fruits, 2, 1)
=> ["strawberry", "orange", "lemon"]
>> even_odder_fruits = array_mod(fruits, 3, 2)
=> ["kiwi", "lemon"]
The simplest method I can think of is this:
fruits = ["a","b","c","d"]
evens = fruits.select {|x| fruits.index(x) % 2 == 0}
odds = fruits - evens
You don't need to mess with select_with_index if the array can look up indices for you. I suppose the drawback to this method is if you have multiple entries in 'fruits' with the same value (the index method returns the index of the first matching entry only).
What you want is:
even_fruits = fruits.select_with_index { |v,i| i % 2 == 0) }
odd_fruits = fruits - even_fruits
Unfortunately Enumerable#select_with_index does not exist as standard, but several people have extended Enumerable with such a method.
http://snippets.dzone.com/posts/show/3746
http://webget.com/gems/webget_ruby_ramp/doc/Enumerable.html#M000058
Solution using just core capabilities:
(0...((fruits.size+1-offset)/mod)).map {|i| fruits[i*mod+offset]}
Rails provides an ActiveSupport extension to Array that provides an "in_groups_of" method. That's what I usually use for things like this. It allows you to do this:
to pull the even fruits (remember to compact to pull off nils at the end):
fruits = ['banana', 'strawberry', 'kiwi', 'orange', 'grapefruit', 'lemon', 'melon']
fruits.in_groups_of(2).collect{|g| g[1]}.compact
=> ["strawberry", "orange", "lemon"]
to pull the odd fruits:
fruits.in_groups_of(2).collect{|g| g[0]}.compact
=> ["banana", "kiwi", "grapefruit", "melon"]
to get every third fruit, you could use:
fruits.in_groups_of(3).collect{|g| g[0]}.compact
=> ["banana", "orange", "melon"]
functional way
#fruits = [...]
even = []
odd = []
fruits.inject(true ){|_is_even, _el| even << _el if _is_even; !_is_even}
fruits.inject(false){|_is_odd, _el| odd << _el if _is_odd; !_is_odd }
Here's a solution using #enum_for, which allows you to specify a method to use "in place" of #each:
require 'enumerator'
mod = 2
[1, 2, 3, 4].enum_for(:each_with_index).select do |item, index|
index % mod == 0
end.map { |item, index| item }
# => [1, 2]

Resources