Rolling average in Ruby? - ruby

I would like to do rolling average on data, x and y values.
e.g.
rolling_average([[0, 1], [1, 2], [2, 3]])
would give me
[[0, 2.0], [1, 2.5], [2, 3.0]]
I couldn't find anywhere how to do it

I believe rolling average (a.ka. moving average) is a misnomer here, but the desired calculation could be performed as follows.
def rolling_average(data)
tot = 0
sz = data.size
data.reverse_each.with_object([]) do |(i,n),a|
tot += n
a.unshift([i, tot.fdiv(sz-i)])
end
end
rolling_average [[0, 1], [1, 2], [2, 3]]
#=> [[0, 2.0], [1, 2.5], [2, 3.0]]
data = 10.times.map { rand(10) }
#=> [[0, 5], [1, 7], [2, 9], [3, 4], [4, 0],
# [5, 5], [6, 1], [7, 6], [8, 3], [9, 5]]
rolling_average data # to 3 decimal places
#=>[[0, 4.5], [1, 4.444], [2, 4.125], [3, 3.428], [4, 3.333],
# [5, 4.0], [6, 3.75], [7, 4.666], [8, 4.0], [9, 5.0]]
Keeping a running total avoids the need to perform a complete summation for each element in data.
Though ever-so-slightly less efficient, it may be clear to write the following.
def rolling_average(data)
tot = data.sum(&:last)
sz = data.size
data.map do |i,n|
avg = tot.fdiv(sz-i)
tot -= n
[i, avg]
end
end
To compute a rolling average of size avg_size one could compute the following, where data is simply an array of values to be averaged.
def rolling_average(data, avg_size)
raise ArgumentError if data.size < avg_size
block = data.first(avg_size)
tot = block.sum
sz = data.size
(avg_size..sz).map do |i|
avg = tot.fdiv(avg_size)
if i < sz
x = data[i]
tot += x - block.first
(block << x).shift
end
avg
end
end
data = [5, 7, 9, 4, 0, 5, 1, 6, 3, 5]
rolling_average(data, 3)
#=> [7.0, 6.667, 4.333, 3.0, 2.0, 4.0, 3.333, 4.667]
The first element of the array returned by rolling_average equals (5 + 7 + 9)/3.0 #=> 7.0, the second equals (7 + 9 + 4)/3.0 #=> 6.667, and so on.

Here's what I did:
def rolling_average(data, size = 10)
data.map.with_index do |_, i|
[
data[i].first,
data[i..(i + size)].map(&:last).then { |a| a.sum.to_f / a.size }
]
end
end

Related

Ruby Solution for codewars sum of intervals 4kyu doesnot work

[Sum of intervals] (https://www.codewars.com/kata/52b7ed099cdc285c300001cd/ruby)
My solution for this kyu
def sum_of_intervals(intervals)
intervals.uniq.sort_by!(&:last)
sum = 0
new_intervals = intervals.sort_by(&:first).each_with_object([intervals.first]) do |interval, arr|
if interval.first <= arr.last.last
arr[-1] = arr.last.first, [arr.last.last, interval.last].max
else
arr << interval
end
end
new_intervals.each do |interval|
sum += (interval[1] - interval[0])
end
p sum
end
After writing code we have two options - test and attempt
My def pass successfully with test and failed with attempt
I cannot see test for attempt
May be sombody could teke a look what`s wrong with my code?
Thanks a lot
intervals.uniq.sort_by!(&:last)
This almost certainly doesn't do what you think it does. Consider:
irb(main):006:0> a = [[1, 2], [3, 4], [5, 0], [1, 2]]
irb(main):007:0> a.uniq.sort_by!(&:last)
=> [[5, 0], [1, 2], [3, 4]]
irb(main):008:0> a
=> [[1, 2], [3, 4], [5, 0], [1, 2]]
irb(main):009:0> (b = a.uniq).sort_by!(&:last)
=> [[5, 0], [1, 2], [3, 4]]
irb(main):010:0> a
=> [[1, 2], [3, 4], [5, 0], [1, 2]]
irb(main):011:0> b
=> [[5, 0], [1, 2], [3, 4]]
intervals.uniq is creating a new array, which #sort_by! does sort destructively, but that does not affect intervals.
You can use the destructive #uniq! in this case, but that method will return nil if the array is already "unique", leading to an exception when you try to call #sort_by! on nil. Using &. (intervals.uniq!&.sort_by!(&:last)) will prevent the exception, but may leave your data unsorted.
You may be better served by the much simpler:
intervals = intervals.uniq.sort_by(&:last)
Though Chris has answered your question, I would like to suggest an alternative solution.
First define a helper method, where the argument r is a range.
def completed_range_span(r)
r.end - r.begin
end
Now define the main method.
def total_arr_lengths(arr)
# convert arr to an array of ranges ordered by beginning of range
a = arr.map { |e| e.first..e.last }.sort_by(&:begin)
tot = 0
loop do
# If a contains only a single range add the span of that range to tot,
# after which we are finished
break (tot + completed_range_span(a.first)) if a.size == 1
# We're not finished
# For readability, assign first two elements of a to variables
r0 = a[0]
r1 = a[1]
# If r0 and r1 do not overlap add the span of r0 to tot
# else alter r1 to be the range formed by r0 and r1
if r0.end < r1.begin
tot += completed_range_span(r0)
else
a[1]= r0.begin..[r0.end, r1.end].max
end
# remove r0
a.shift
end
end
Let's try it.
total_arr_lengths [[1,4], [7, 10], [3, 5]] #=> 7
total_arr_lengths [[1,2], [6, 10], [11, 15]] #=> 9
total_arr_lengths [[1,4], [7, 10], [3, 5]] #=> 7
total_arr_lengths [[1,5], [10, 20], [1, 6], [16, 19], [5, 11]] #=> 19
total_arr_lengths [[0, 20], [-100000000, 10], [30, 40]] #=> 100000030
To help the reader confirm the results for these examples, for each argument (an array) I have displayed below the corresponding value of the array of ordered ranges obtained by the first calculation performed by the main method:
arr.map { |e| e.first..e.last }.sort_by(&:begin)
arr array of ordered ranges
-------------------------------------------- -----------------------------------
[[1,4], [7, 10], [3, 5]] [1..4, 3..5, 7..10]
[[1,2], [6, 10], [11, 15]] [1..2, 6..10, 11..15]
[[1,4], [7, 10], [3, 5]] [1..4, 3..5, 7..10]
[[1,5], [10, 20], [1, 6], [16, 19], [5, 11]] [1..5, 1..6, 5..11, 10..20, 16..19]
[[0, 20], [-100000000, 10], [30, 40]] [-100000000..10, 0..20, 30..40]
I converted the arrays to ranges to improve readability (in my opinion). I don't expect it affects computational efficiency, though it generally saves some memory.

How can i avoid using the same element in my two sum solution

So I am trying to get a solution to my two sum problem and I am stuck, I need to print the indices for the elements which add up to the target and my solution will return an element twice if it is one half of the target
def two_sum(nums, target)
num_hash = Hash.new(0)
nums.each_with_index do |num,idx|
num_hash[num] = idx
if num_hash.key?(target - num) && target % num != 0
return [num_hash[num], idx]
end
end
end
So I don't think the problem is related to the number being 1/2 of the target, it just seems to be "if a solution is found, it returns the same index twice". For instance, using the sample set [2, 7, 11, 15]
two_sum([2, 7, 11, 15], 14) # => [2, 7, 11, 15]
So, 7 is half of 14, which is the target, and instead of returning the index 1 twice, as you suggest it would, it returns the original input array (the result of nums.each_with_index. However, if we try passing a target of 9, it behaves as you describe:
two_sum([2, 7, 11, 15], 9) # => [1, 1]
The reason for this, is because of the line:
return [num_hash[num], idx]
you have already set num into the num_hash (num_hash[num] = idx) and then you are returning both the idx and num_hash[num], which is also idx. So what you want to do is:
return [num_hash[target - num], idx]
and then to 'fix' all the elements being returned when no result is found, just return [] at the end of the method:
def two_sum(nums, target)
num_hash = Hash.new(0)
nums.each_with_index do |num,idx|
num_hash[num] = idx
if num_hash.key?(target - num) && target % num != 0
return [num_hash[target - num], idx]
end
end
[]
end
and now:
two_sum([2, 7, 11, 15], 14) # => []
two_sum([2, 7, 11, 15], 9) # => [0, 1]
Note: you also have a problem with the code where, if you have the same number twice, it doesn't find the answer:
two_sum([2, 7, 11, 7, 15], 14) # => []
left for you to figure out, just wanted to point this out to you.
You can use the method Array#combination to advantage here.
def two_sum(nums, target)
nums.each_index.to_a.combination(2).select { |i,j| nums[i] + nums[j] == target }
end
two_sum([2, 7, 11, 15], 14)
#=> []
two_sum([2, 7, 11, 15], 9)
#=> [[0, 1]]
two_sum([2, 4, 7, 5], 9)
#=> [[0, 2], [1, 3]]
two_sum([2, 2, 2, 2], 4)
#=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
two_sum([2, 4, 7, 5], 8)
#=> []
For
nums = [2, 4, 7, 5]
target = 9
the steps are as follows.
a = nums.each_index
#=> #<Enumerator: [2, 4, 7, 5]:each_index>
We can see the elements that will be generated by this enumerator by converting it to an array.
b = a.to_a
#=> [0, 1, 2, 3]
Next,
c = b.combination(2)
#=> #<Enumerator: [0, 1, 2, 3]:combination(2)>
c.to_a
#=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
The rest is straightforward as select merely selects those pairs of indices passed to it (i,j) whose corresponding values, num[i] and num[j], sum to target.
I think what you want is ...
return [num_hash[target-num], idx]

Algorithmic Optimization [duplicate]

The question I'm working on is:
Find which sum of squared factors are a perfect square given a specific range.
So if the range was (1..10) you would get each number's factors (all factors for 1, all factors for 2, all factors for 3 ect..) Square those factors, then add them together. Finally check if that sum is a perfect square.
I am stuck on refactoring/optimization because my solution is too slow.
Here is what I came up with:
def list_squared(m, n)
ans = []
range = (m..n)
range.each do |i|
factors = (1..i).select { |j| i % j == 0 }
squares = factors.map { |k| k ** 2 }
sum = squares.inject { |sum,x| sum + x }
if sum == Math.sqrt(sum).floor ** 2
all = []
all += [i, sum]
ans << all
end
end
ans
end
This is an example of what I would put in the method:
list_squared(1, 250)
And then the desired output would be an array of arrays with each array containing the number whose sum of squared factors was a perfect square and the sum of those squared factors:
[[1, 1], [42, 2500], [246, 84100]]
I would start by introducing some helper methods (factors and square?) to make your code more readable.
Furthermore, I would reduce the number of ranges and arrays to improve memory usage.
require 'prime'
def factors(number)
[1].tap do |factors|
primes = number.prime_division.flat_map { |p, e| Array.new(e, p) }
(1..primes.size).each do |i|
primes.combination(i).each do |combination|
factor = combination.inject(:*)
factors << factor unless factors.include?(factor)
end
end
end
end
def square?(number)
square = Math.sqrt(number)
square == square.floor
end
def list_squared(m, n)
(m..n).map do |number|
sum = factors(number).inject { |sum, x| sum + x ** 2 }
[number, sum] if square?(sum)
end.compact
end
list_squared(1, 250)
A benchmark with a narrow range (up to 250) shows only a minor improvement:
require 'benchmark'
n = 1_000
Benchmark.bmbm(15) do |x|
x.report("original_list_squared :") { n.times do; original_list_squared(1, 250); end }
x.report("improved_list_squared :") { n.times do; improved_list_squared(1, 250); end }
end
# Rehearsal -----------------------------------------------------------
# original_list_squared : 2.720000 0.010000 2.730000 ( 2.741434)
# improved_list_squared : 2.590000 0.000000 2.590000 ( 2.604415)
# -------------------------------------------------- total: 5.320000sec
# user system total real
# original_list_squared : 2.710000 0.000000 2.710000 ( 2.721530)
# improved_list_squared : 2.620000 0.010000 2.630000 ( 2.638833)
But a benchmark with a wider range (up to 10000) shows a much better performance than the original implementation:
require 'benchmark'
n = 10
Benchmark.bmbm(15) do |x|
x.report("original_list_squared :") { n.times do; original_list_squared(1, 10000); end }
x.report("improved_list_squared :") { n.times do; improved_list_squared(1, 10000); end }
end
# Rehearsal -----------------------------------------------------------
# original_list_squared : 36.400000 0.160000 36.560000 ( 36.860889)
# improved_list_squared : 2.530000 0.000000 2.530000 ( 2.540743)
# ------------------------------------------------- total: 39.090000sec
# user system total real
# original_list_squared : 36.370000 0.120000 36.490000 ( 36.594130)
# improved_list_squared : 2.560000 0.010000 2.570000 ( 2.581622)
tl;dr: The bigger the N the better my code performs compared to the original implementation...
One way to make it more efficient is to use Ruby's built-in method Prime::prime_division.
For any number n, if prime_division returns an array containing a single element, that element will be [n,1] and n will have been shown to be prime. That prime number has factors n and 1, so must be treated differently than numbers that are not prime.
require 'prime'
def list_squared(range)
range.each_with_object({}) do |i,h|
facs = Prime.prime_division(i)
ssq =
case facs.size
when 1 then facs.first.first**2 + 1
else facs.inject(0) { |tot,(a,b)| tot + b*(a**2) }
end
h[i] = facs if (Math.sqrt(ssq).to_i)**2 == ssq
end
end
list_squared(1..10_000)
#=> { 1=>[], 48=>[[2, 4], [3, 1]], 320=>[[2, 6], [5, 1]], 351=>[[3, 3], [13, 1]],
# 486=>[[2, 1], [3, 5]], 1080=>[[2, 3], [3, 3], [5, 1]],
# 1260=>[[2, 2], [3, 2], [5, 1], [7, 1]], 1350=>[[2, 1], [3, 3], [5, 2]],
# 1375=>[[5, 3], [11, 1]], 1792=>[[2, 8], [7, 1]], 1836=>[[2, 2], [3, 3], [17, 1]],
# 2070=>[[2, 1], [3, 2], [5, 1], [23, 1]], 2145=>[[3, 1], [5, 1], [11, 1], [13, 1]],
# 2175=>[[3, 1], [5, 2], [29, 1]], 2730=>[[2, 1], [3, 1], [5, 1], [7, 1], [13, 1]],
# 2772=>[[2, 2], [3, 2], [7, 1], [11, 1]], 3072=>[[2, 10], [3, 1]],
# 3150=>[[2, 1], [3, 2], [5, 2], [7, 1]], 3510=>[[2, 1], [3, 3], [5, 1], [13, 1]],
# 4104=>[[2, 3], [3, 3], [19, 1]], 4305=>[[3, 1], [5, 1], [7, 1], [41, 1]],
# 4625=>[[5, 3], [37, 1]], 4650=>[[2, 1], [3, 1], [5, 2], [31, 1]],
# 4655=>[[5, 1], [7, 2], [19, 1]], 4998=>[[2, 1], [3, 1], [7, 2], [17, 1]],
# 5880=>[[2, 3], [3, 1], [5, 1], [7, 2]], 6000=>[[2, 4], [3, 1], [5, 3]],
# 6174=>[[2, 1], [3, 2], [7, 3]], 6545=>[[5, 1], [7, 1], [11, 1], [17, 1]],
# 7098=>[[2, 1], [3, 1], [7, 1], [13, 2]], 7128=>[[2, 3], [3, 4], [11, 1]],
# 7182=>[[2, 1], [3, 3], [7, 1], [19, 1]], 7650=>[[2, 1], [3, 2], [5, 2], [17, 1]],
# 7791=>[[3, 1], [7, 2], [53, 1]], 7889=>[[7, 3], [23, 1]],
# 7956=>[[2, 2], [3, 2], [13, 1], [17, 1]],
# 9030=>[[2, 1], [3, 1], [5, 1], [7, 1], [43, 1]],
# 9108=>[[2, 2], [3, 2], [11, 1], [23, 1]], 9295=>[[5, 1], [11, 1], [13, 2]],
# 9324=>[[2, 2], [3, 2], [7, 1], [37, 1]]}
This calculation took approximately 0.15 seconds.
For i = 6174
(2**1) * (3**2) * (7**3) #=> 6174
and
1*(2**2) + 2*(3**2) + 3*(7**2) #=> 169 == 13*13
The trick that frequently solves questions like this is to switch from trial division to a sieve. In Python (sorry):
def list_squared(m, n):
factor_squared_sum = {i: 0 for i in range(m, n + 1)}
for factor in range(1, n + 1):
i = n - n % factor # greatest multiple of factor less than or equal to n
while i >= m:
factor_squared_sum[i] += factor ** 2
i -= factor
return {i for (i, fss) in factor_squared_sum.items() if isqrt(fss) ** 2 == fss}
def isqrt(n):
# from http://stackoverflow.com/a/15391420
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
The next optimization is to step factor only to isqrt(n), adding the factor squares in pairs (e.g., 2 and i // 2).

Optimization for finding perfect-square algorithm

The question I'm working on is:
Find which sum of squared factors are a perfect square given a specific range.
So if the range was (1..10) you would get each number's factors (all factors for 1, all factors for 2, all factors for 3 ect..) Square those factors, then add them together. Finally check if that sum is a perfect square.
I am stuck on refactoring/optimization because my solution is too slow.
Here is what I came up with:
def list_squared(m, n)
ans = []
range = (m..n)
range.each do |i|
factors = (1..i).select { |j| i % j == 0 }
squares = factors.map { |k| k ** 2 }
sum = squares.inject { |sum,x| sum + x }
if sum == Math.sqrt(sum).floor ** 2
all = []
all += [i, sum]
ans << all
end
end
ans
end
This is an example of what I would put in the method:
list_squared(1, 250)
And then the desired output would be an array of arrays with each array containing the number whose sum of squared factors was a perfect square and the sum of those squared factors:
[[1, 1], [42, 2500], [246, 84100]]
I would start by introducing some helper methods (factors and square?) to make your code more readable.
Furthermore, I would reduce the number of ranges and arrays to improve memory usage.
require 'prime'
def factors(number)
[1].tap do |factors|
primes = number.prime_division.flat_map { |p, e| Array.new(e, p) }
(1..primes.size).each do |i|
primes.combination(i).each do |combination|
factor = combination.inject(:*)
factors << factor unless factors.include?(factor)
end
end
end
end
def square?(number)
square = Math.sqrt(number)
square == square.floor
end
def list_squared(m, n)
(m..n).map do |number|
sum = factors(number).inject { |sum, x| sum + x ** 2 }
[number, sum] if square?(sum)
end.compact
end
list_squared(1, 250)
A benchmark with a narrow range (up to 250) shows only a minor improvement:
require 'benchmark'
n = 1_000
Benchmark.bmbm(15) do |x|
x.report("original_list_squared :") { n.times do; original_list_squared(1, 250); end }
x.report("improved_list_squared :") { n.times do; improved_list_squared(1, 250); end }
end
# Rehearsal -----------------------------------------------------------
# original_list_squared : 2.720000 0.010000 2.730000 ( 2.741434)
# improved_list_squared : 2.590000 0.000000 2.590000 ( 2.604415)
# -------------------------------------------------- total: 5.320000sec
# user system total real
# original_list_squared : 2.710000 0.000000 2.710000 ( 2.721530)
# improved_list_squared : 2.620000 0.010000 2.630000 ( 2.638833)
But a benchmark with a wider range (up to 10000) shows a much better performance than the original implementation:
require 'benchmark'
n = 10
Benchmark.bmbm(15) do |x|
x.report("original_list_squared :") { n.times do; original_list_squared(1, 10000); end }
x.report("improved_list_squared :") { n.times do; improved_list_squared(1, 10000); end }
end
# Rehearsal -----------------------------------------------------------
# original_list_squared : 36.400000 0.160000 36.560000 ( 36.860889)
# improved_list_squared : 2.530000 0.000000 2.530000 ( 2.540743)
# ------------------------------------------------- total: 39.090000sec
# user system total real
# original_list_squared : 36.370000 0.120000 36.490000 ( 36.594130)
# improved_list_squared : 2.560000 0.010000 2.570000 ( 2.581622)
tl;dr: The bigger the N the better my code performs compared to the original implementation...
One way to make it more efficient is to use Ruby's built-in method Prime::prime_division.
For any number n, if prime_division returns an array containing a single element, that element will be [n,1] and n will have been shown to be prime. That prime number has factors n and 1, so must be treated differently than numbers that are not prime.
require 'prime'
def list_squared(range)
range.each_with_object({}) do |i,h|
facs = Prime.prime_division(i)
ssq =
case facs.size
when 1 then facs.first.first**2 + 1
else facs.inject(0) { |tot,(a,b)| tot + b*(a**2) }
end
h[i] = facs if (Math.sqrt(ssq).to_i)**2 == ssq
end
end
list_squared(1..10_000)
#=> { 1=>[], 48=>[[2, 4], [3, 1]], 320=>[[2, 6], [5, 1]], 351=>[[3, 3], [13, 1]],
# 486=>[[2, 1], [3, 5]], 1080=>[[2, 3], [3, 3], [5, 1]],
# 1260=>[[2, 2], [3, 2], [5, 1], [7, 1]], 1350=>[[2, 1], [3, 3], [5, 2]],
# 1375=>[[5, 3], [11, 1]], 1792=>[[2, 8], [7, 1]], 1836=>[[2, 2], [3, 3], [17, 1]],
# 2070=>[[2, 1], [3, 2], [5, 1], [23, 1]], 2145=>[[3, 1], [5, 1], [11, 1], [13, 1]],
# 2175=>[[3, 1], [5, 2], [29, 1]], 2730=>[[2, 1], [3, 1], [5, 1], [7, 1], [13, 1]],
# 2772=>[[2, 2], [3, 2], [7, 1], [11, 1]], 3072=>[[2, 10], [3, 1]],
# 3150=>[[2, 1], [3, 2], [5, 2], [7, 1]], 3510=>[[2, 1], [3, 3], [5, 1], [13, 1]],
# 4104=>[[2, 3], [3, 3], [19, 1]], 4305=>[[3, 1], [5, 1], [7, 1], [41, 1]],
# 4625=>[[5, 3], [37, 1]], 4650=>[[2, 1], [3, 1], [5, 2], [31, 1]],
# 4655=>[[5, 1], [7, 2], [19, 1]], 4998=>[[2, 1], [3, 1], [7, 2], [17, 1]],
# 5880=>[[2, 3], [3, 1], [5, 1], [7, 2]], 6000=>[[2, 4], [3, 1], [5, 3]],
# 6174=>[[2, 1], [3, 2], [7, 3]], 6545=>[[5, 1], [7, 1], [11, 1], [17, 1]],
# 7098=>[[2, 1], [3, 1], [7, 1], [13, 2]], 7128=>[[2, 3], [3, 4], [11, 1]],
# 7182=>[[2, 1], [3, 3], [7, 1], [19, 1]], 7650=>[[2, 1], [3, 2], [5, 2], [17, 1]],
# 7791=>[[3, 1], [7, 2], [53, 1]], 7889=>[[7, 3], [23, 1]],
# 7956=>[[2, 2], [3, 2], [13, 1], [17, 1]],
# 9030=>[[2, 1], [3, 1], [5, 1], [7, 1], [43, 1]],
# 9108=>[[2, 2], [3, 2], [11, 1], [23, 1]], 9295=>[[5, 1], [11, 1], [13, 2]],
# 9324=>[[2, 2], [3, 2], [7, 1], [37, 1]]}
This calculation took approximately 0.15 seconds.
For i = 6174
(2**1) * (3**2) * (7**3) #=> 6174
and
1*(2**2) + 2*(3**2) + 3*(7**2) #=> 169 == 13*13
The trick that frequently solves questions like this is to switch from trial division to a sieve. In Python (sorry):
def list_squared(m, n):
factor_squared_sum = {i: 0 for i in range(m, n + 1)}
for factor in range(1, n + 1):
i = n - n % factor # greatest multiple of factor less than or equal to n
while i >= m:
factor_squared_sum[i] += factor ** 2
i -= factor
return {i for (i, fss) in factor_squared_sum.items() if isqrt(fss) ** 2 == fss}
def isqrt(n):
# from http://stackoverflow.com/a/15391420
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
The next optimization is to step factor only to isqrt(n), adding the factor squares in pairs (e.g., 2 and i // 2).

Ruby: combine array values

I have an array of arrays indicating coordinate values, like so:
cells = [ [0,0], [0,1] ]
Each array in the array is an X and Y value. So, if I want to shift this right, that would be X+1 on each cell. I could express this as a cell like so:
delta = [1,0]
Now, what I'd like to do is merge that value into each cell so that the X value of each cell is summed with the value of delta, so in this case the final output should be:
new_cells = [ [1,0], [1,1] ]
Here's the best I've been able to think of so far, it seems really heavy:
cells = [[0,0],[0,1]]
delta = [1,0]
cells.each do |cell|
cell[0] = cell[0] + delta[0]
cell[1] = cell[1] + delta[1]
end
# Now cells = [[1,0],[1,1]]
Is there a cleaner one-liner kind of method that would sum an array onto each array in a chain of arrays, or is the above the best solution to that problem?
I think your best solution is to recognize that cells and deltas are a different data type that you could operate on more clearly if it weren't an array:
Cell = Struct.new(:x, :y) do
def + other
Cell.new(self.x + other.x, self.y + other.y)
end
end
# cells is some array of Cell objects
# delta is some Cell object
cells.map! {|cell| cell + delta}
Here:
cells = cells.map {|c| [c[0] + delta[0], c[1] + delta[1]] }
cells.map! {|x, y| [x + delta[0], y + delta[1]] }
Note that Linuxios's answer, my answer, and your solution all have different effects if somebody else has a reference to the cells array or to one of the cells contained in the array. Only your answer modifies the original cells in place, so with my solution or Linuxios's solution references might still point to old data.
cells.map {|cell| cell.zip(delta).map{|x, y| x + y }}
I don't consider this cleaner than the other solutions that have been proposed.
Here's how you don't have to worry about matching the cell keys with delta's:
cells = cells.map {|cell| [cell,delta].transpose.map {|value| value.reduce(:+)}}
Step by step:
cells = cells.map { |cell| # => [0,0]
combined = [cell, delta] # => [[0,0], [1,0]]
transposed = combined.transpose # => [[0, 1], [0, 0]]
new_c = transposed.map { |value| # => [0, 1]
value.reduce(:+) # => 1, => 0
}
new_c # => [1,0] As expected for first cell.
}
cells # => [[1,0],[1,1]] Final result
With another sample data:
cells = [[0,0],[1,1],[2,2]]
delta = [1,1]
plug = Proc.new {
cells = cells.map { |cell| [cell, delta].transpose.map { |value| value.reduce(:+) } }
}
plug.call # => [[1, 1], [2, 2], [3, 3]]
plug.call # => [[2, 2], [3, 3], [4, 4]]
plug.call # => [[3, 3], [4, 4], [5, 5]]
plug.call # => [[4, 4], [5, 5], [6, 6]]
plug.call # => [[5, 5], [6, 6], [7, 7]]
Another one:
cells = [[0,0,0],[0,1,2],[1,2,3],[2,3,4]]
delta = [3,2,1]
plug.call # => [[3, 2, 1], [3, 3, 3], [4, 4, 4], [5, 5, 5]]
plug.call # => [[6, 4, 2], [6, 5, 4], [7, 6, 5], [8, 7, 6]]
plug.call # => [[9, 6, 3], [9, 7, 5], [10, 8, 6], [11, 9, 7]]
plug.call # => [[12, 8, 4], [12, 9, 6], [13, 10, 7], [14, 11, 8]]
plug.call # => [[15, 10, 5], [15, 11, 7], [16, 12, 8], [17, 13, 9]]
Hope this answers your question better.

Resources