Suppose the arrays were large and you were looking for an efficient solution.
Let arr1
and arr2
denote the two arrays, with arr2
being the smaller of the two.
First, convert arr2
to a hash:
h2 = Hash[arr2]
Now map each element of arr1
and append to the result the elements of arr2
that do not have matching dates among the elements of arr1
:
arr1.map { |k1,v1| [k1, h2.key?(k1) ? v1 + h2.delete(k1) : v] }.concat(h2.to_a)
Note that h2.delete(k)
does double-duty: it deletes the key-value pair k,v2
from h2
and returns v2
to be added to v1
.
You can of course tack on .sort
to sort the array by date.
I suspect this may be reasonably fast considering that only one hash is constructed and that is for the smaller of the two arrays.
Let's see how this compares with the other methods that have been suggested.
Test case
def test(n1, n2, f1)
m = [(n1*f1).to_i, n2].min # elements of `arr1` in `arr2`
[Array.new(n1) { |i| [i,i] }.shuffle,
Array.new(n2) { |i| [i+m-n2,i+i] }.shuffle]
end
Suppose, for example, arr1
and arr2
are to have 10
and 8
tuples, respectively, and in 10*0.5 => 5
cases, the first element of a tuple in arr1
matches the first element of a tuple in arr2
. Thus 0-5 => 5
tuples in arr1
have no match in arr2
and 8-5 => 3
tuples in arr2
have no match in arr1
.
Then
arr1, arr2 = test(10,8,0.5)
#=> [[[7, 7], [0, 0], [2, 2], [4, 4], [3, 3], [5, 5], [1, 1], [8, 8], [9, 9], [6, 6]],
# [[-2, 2], [-3, 0], [3, 12], [-1, 4], [4, 14], [0, 6], [2, 10], [1, 8]]]
arr1.sort
#=> [[0, 0], [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6], [7, 7], [8, 8], [9, 9]]
arr2.sort
#=> [[-3, 0], [-2, 2], [-1, 4], [0, 6], [1, 8], [2, 10], [3, 12], [4, 14]]
Methods to be benchmarked
def justin(arr1, arr2)
(arr1 + arr2).group_by(&:first)
.map { |k, v| [k, v.map(&:last).inject(:+)] }
.sort_by { |x| x.first }
end
I've corrected justin
by changing arr1 | arr2
to arr1 + arr2
, and removed to_date
.
def btea(arr1, arr2)
one = Hash[arr1]
two = Hash[arr2]
one.merge(two) { |_value, old, new| old + new }.to_a
end
In btea
I converted the merged hash back to an array.
def success(arr1, arr2)
aggregator = Hash.new(0)
arr1.each { |date, val| aggregator[date] += val }
arr2.each { |date, val| aggregator[date] += val }
aggregator.to_a.sort_by { |date, val| date }
end
I removed .to_date
in success
.
def cary(arr1, arr2)
h2 = Hash[arr2]
arr1.map { |d1,n1| [d1, h2.key?(d1) ? n1 + h2.delete(d1) : n1] }.concat(h2.to_a)
end
Confirm methods give the same results
arr1, arr2 = test(1000,800,0.8)
bt = btea(arr1, arr2).sort
su = success(arr1, arr2).sort
justin(arr1, arr2).sort == bt #=> true
bt == su #=> true
su == cary(arr1, arr2).sort #=> true
Benchmark code
require 'benchmark'
def bench(arr1, arr2)
Benchmark.bm(10) do |bm|
bm.report "justin" do
justin(arr1, arr2)
end
bm.report "btea" do
btea(arr1, arr2)
end
bm.report "success" do
success(arr1, arr2)
end
bm.report "cary" do
cary(arr1,arr2)
end
end
end
Results
arr1, arr2 = test(1_000_000, 800_000, 0.8)
bench(arr1, arr2)
user system total real
justin 6.650000 0.160000 6.810000 ( 6.855140)
btea 5.290000 0.100000 5.390000 ( 5.439332)
success 2.970000 0.020000 2.990000 ( 2.999598)
cary 3.970000 0.110000 4.080000 ( 4.090756)
arr1, arr2 = test(2_000_000, 200_000, 0.5)
bench(arr1, arr2)
user system total real
justin 10.690000 0.190000 10.880000 ( 10.958374)
btea 8.650000 0.180000 8.830000 ( 8.868550)
success 5.240000 0.010000 5.250000 ( 5.268781)
cary 1.330000 0.020000 1.350000 ( 1.351937)
arr1, arr2 = test(1_000_000, 1_000_000, 0.5)
bench(arr1, arr2)
user system total real
justin 10.660000 0.160000 10.820000 ( 10.845127)
btea 5.810000 0.110000 5.920000 ( 5.922358)
success 4.060000 0.020000 4.080000 ( 4.078427)
cary 2.400000 0.040000 2.440000 ( 2.447106)
k
,v
, andx
, I don't see any need to refactor anything here. I'd probably write the same thing. \$\endgroup\$|
, two pairs are not duplicated if they are on different arrays, aren't they? It seems you needuniq
for each array. \$\endgroup\$Array#+
. Here it will have the unfortunate (or desired?) effect of ignoring items inarr2
that are equal to items inarr1
(e.g. if["1 Jan", 1]
is in botharr1
andarr2
, the OPs code will return["Jan 1", 1]
instead of["Jan 1", 2]
). If this is intentional, both answers here are not valid, unfortunately. Could you clarify Justin? \$\endgroup\$arr1
contain["23 Jan",3]
and["23 Jan",5]
. If "yes" (I'm guessing the answer is "no") and ifarr2
contained["23 Jan",7]
, would["23 Jan",3+5+7]
be part of your result? The efficiency as well as the correctness of the solution depends on whetherarr1
orarr2
can contain pairs with the same date. \$\endgroup\$