Ruby: combine array values - ruby

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.

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.

Repeating a loop when it reaches the end

I am trying to conceptualize the iteration of two loops
numbers_array = [1,2,3,4,5,6,7,8,9,10]
add_to_array = [1,2,3,4]
While the numbers_array iterates, add_to_array iterates simultaneously adding both elements together at the same time. The caveat is once add_to_array reaches the end, it starts over adding its element to the next index in numbers_array. So at numbers_array[4] we would be adding add_to_array[0] then adding numbers_array[5] to add_to_array[1] and so on. This process would repeat until we reach the end of the numbers_array.
Any input would be greatly appreciated!
You are looking for Enumerable#zip and Enumerable#cycle:
numbers_array = [1,2,3,4,5,6,7,8,9,10]
#⇒ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
add_to_array = [1,2,3,4]
#⇒ [1, 2, 3, 4]
numbers_array.zip(add_to_array.cycle)
#⇒ [[1, 1], [2, 2], [3, 3], [4, 4], [5, 1],
# [6, 2], [7, 3], [8, 4], [9, 1], [10, 2]]
Now do whatever you want with the array returned. E.g. to reduce the zipped result summing elements, map ro Enumerable#sum:
numbers_array.zip(add_to_array.cycle).map(&:sum)
#⇒ [2, 4, 6, 8, 6, 8, 10, 12, 10, 12]
It works by using the % operator to cycle through the indexes.
numbers_array = [1,2,3,4,5,6,7,8,9,10]
add_to_array = [1,2,3,4]
numbers_array.map.with_index do |n, i|
n + add_to_array[i % add_to_array.length]
end
A cool method that's similar, if you didn't want to start over at the next array, would be .zip
https://apidock.com/ruby/Array/zip
add_to_array.zip(*numbers_array.each_slice(add_to_array.size)).
map { |a| a.sum { |e| e.to_i } }
#=> [16, 20, 13, 16]
e.to_i is needed to convert nil values to zeros. See NilClass#to_i.
Another option:
numbers_array.map { |e| e + add_to_array.rotate!.last }
# => [2, 4, 6, 8, 6, 8, 10, 12, 10, 12]
Drawback: add_to_array is mutated by rotate!

How to access a nested element, passing array with coordinates

Is there any short way to access an element of a nested array, passing the array with coordinates? I mean something like:
matrix = [[1,2,3,4],[5,6,7,8]]
array = [1,1]
matrix [array]
# => 6
I just wonder if there is a shorter version than:
matrix [array[0]][array[1]]
I believe you want to use the Matrix class:
require 'matrix'
arr = [[1,2,3,4],[5,6,7,8]]
matrix = Matrix[*arr] #=> Matrix[[1, 2, 3, 4], [5, 6, 7, 8]]
matrix[1,1] #=> 6
matrix.row(1) #=> Vector[5, 6, 7, 8]
c = matrix.column(1) #=> Vector[2, 6]
c.to_a #=> [2, 6]
m = matrix.transpose #=> Matrix[[1, 5], [2, 6], [3, 7], [4, 8]]
m.to_a #=> [[1, 5], [2, 6], [3, 7], [4, 8]]
array.inject(matrix, :fetch)
# => 6
matrix[1][1]
should equal 6. matrix[1] is the 2nd array, matrix[1][1] is the second element in that array.

Sum arrays by index using functional programming

I have several equally sized arrays containing numbers (matrix), and I want to sum them all by their index (matrix columns).
For example, if I have:
data = [[1, 2, 3, 4], [5, 6, 7, 8]]
I want to get the result:
column_totals = [6, 8, 10, 12]
I understand how to do this imperatively, but how would I do this using functional programming? (Preferably, using built in Enumerable methods.) I wasn't very happy with any of the functional solutions I came up with.
I ended up using the Matrix class:
require 'matrix'
data = [[1, 2, 3, 4], [5, 6, 7, 8]]
matrix = Matrix[*data]
# Added sum method to Vector class.
matrix.column_vectors.map { |column| column.sum }
I'm happy enough with that solution, but am frustrated that I couldn't wrap my mind around a good functional solution without relying on the Matrix class.
Specifically, I was tripped up on the step to transform this:
data = [[1, 2, 3, 4], [5, 6, 7, 8]]
into this:
columns = [[1, 5], [2, 6], [3, 7], [4, 8]]
Any reason to not use Array#transpose?
data.transpose
# => [[1, 5], [2, 6], [3, 7], [4, 8]]
Alternatively, if you only want to use Enumerable methods to iterate, you can do
columns = data.inject(Array.new(data.first.length){[]}) { |matrix,row|
row.each_with_index { |e,i| matrix[i] << e }
matrix }
# => [[1, 5], [2, 6], [3, 7], [4, 8]]
or
columns = data.flatten.group_by.with_index { |e,i| i % data[0].size }.values
# => [[1, 5], [2, 6], [3, 7], [4, 8]]
To sum:
columns.map { |row| row.inject :+ }
# => [6, 8, 10, 12]
Thirdly, if you don't need the intermediate columns:
data.inject { |s,r| s.zip(r).map { |p| p.inject :+ } }
# => [6, 8, 10, 12]
You could use Array#transpose, as #Matt hinted, and then sum the arrays inside:
data.transpose.map {|a| a.reduce(:+) }

Fill sparse array

I have a sparse array, for example:
rare = [[0,1], [2,3], [4,5], [7,8]]
I want to plot a chart with these data, each pair are point coordinates.
As you can see I don't have points for x=1, x=3 , x=5, x=6
I want to fill the array with the previous values, so for the above example I will get:
filled = [[0,1], [1,1], [2,3], [3,3], [4,5], [5,5], [6,5], [7,8]
As you can see, for calculating the y value, I simply take the last y value I used.
What is the best aproach to accomplish this ?
Range.new(*rare.transpose.first.sort.values_at(0,-1)).inject([]){|a,i|
a<<[i, Hash[rare][i] || a.last.last]
}
Step-by-step explanation:
rare.transpose.first.sort.values_at(0,-1) finds min and max x ([0,7] in your example)
Range.new() makes a range out of it (0..7)
inject iterates through the range and for every x returns pair [x,y], where y is:
y from input array, where defined
y from previously evaluated pair, where not
Note: here are some other ways of finding min and max x:
[:min,:max].map{|m| Hash[rare].keys.send m}
rare.map{|el| el.first}.minmax # Ruby 1.9, by steenslag
rare = [[0,1], [2,3], [4,5], [7,8]]
filled = rare.inject([]) do |filled, point|
extras = if filled.empty?
[]
else
(filled.last[0] + 1 ... point[0]).collect do |x|
[x, filled.last[1]]
end
end
filled + extras + [point]
end
p filled
# => [[0, 1], [1, 1], [2, 3], [3, 3], [4, 5], [5, 5], [6, 5], [7, 8]]
An inject solution:
filled = rare.inject([]) do |filled_acc, (pair_x, pair_y)|
padded_pairs = unless filled_acc.empty?
last_x, last_y = filled_acc.last
(last_x+1...pair_x).map { |x| [x, last_y] }
end || []
filled_acc + padded_pairs + [[pair_x, pair_y]]
end
More about Enumerable#inject and functional programming with Ruby here.
irb(main):001:0> rare = [[0,1], [2,3], [4,5], [7,8]]
=> [[0, 1], [2, 3], [4, 5], [7, 8]]
irb(main):002:0> r=rare.transpose
=> [[0, 2, 4, 7], [1, 3, 5, 8]]
irb(main):003:0> iv = (r[0][0]..r[0][-1]).to_a.select {|w| !r[0].include?(w) }
=> [1, 3, 5, 6]
irb(main):004:0> r[1][-1]=r[1][-2]
=> 5
irb(main):005:0> p (iv.zip(r[1]) + rare).sort
[[0, 1], [1, 1], [2, 3], [3, 3], [4, 5], [5, 5], [6, 5], [7, 8]]
=> [[0, 1], [1, 1], [2, 3], [3, 3], [4, 5], [5, 5], [6, 5], [7, 8]]

Resources