Fill sparse array - ruby

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]]

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.

Index of multiple array maxima

To get the index of the maximum of an array arr = [1,1,2,3,1,2,3,3], I can do:
arr.each_with_index.max
and get [3, 7]. However, I don't want to miss the [3, 3] and [3, 6]. Any help is welcome!
arr.each_with_index.group_by(&:first).max.last
# => [[3, 3], [3, 6], [3, 7]]
mx = arr.max
[mx, arr.each_index.select { |i| arr[i] == mx }]
#=> [3, [3, 6, 7]]

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.

Comparing 2d arrays together

I have four 2d arrays of varying lengths. Is there a way to compare them to make sure none of the arrays share a value? Is there a simple way to code it or a gem I could use?
To back my question up with an example:
array1 = [[2,3],[2,4]]
array2 = [[1,3],[2,3],[3,3]]
array3 = [[5,3],[6,3],[7,3],[8,3],[9,3]]
I want a checker that would let me know that array1 and array2 have an element the same.
Is this doable?
You might do it as follows.
Code
def shared_values?(*arr)
a = arr.map(&:uniq).reduce(:+)
a.size > a.uniq.size
end
Examples
array1 = [[2, 3], [2, 4]]
array2 = [[1, 3], [2, 3], [3, 3]]
array3 = [[5, 3], [6, 3], [7, 3],[8, 3],[9, 3]]
shared_values? array1, array2, array3 #=> true
array1 = [[2, 4]]
shared_values? array1, array2, array3 #=> false
array1 = [[2, 4], [2, 4]]
shared_values? array1, array2, array3 #=> false
array1 = [[2, 3], [2, 4], [2, 4]]
shared_values? array1, array2, array3 #=> false
Explanation
For the last example above:
arr = [array1, array2, array3]
#=> [ [[2, 3], [2, 4], [2, 4]],
# [[1, 3], [2, 3], [3, 3]],
# [[5, 3], [6, 3], [7, 3], [8, 3], [9, 3]] ]
a = arr.map(&:uniq)
#=> [ [[2, 3], [2 ,4]],
# [[1, 3], [2, 3], [3, 3]],
# [[5, 3], [6, 3], [7, 3], [8, 3], [9, 3]] ]
b = a.reduce(:+)
#=> [[2, 3], [2, 4], [1, 3], [2, 3], [3, 3],
# [5, 3], [6, 3], [7, 3], [8, 3], [9, 3]]
c = b.uniq
#=> [[2, 3], [2, 4], [1, 3], [3, 3],
# [5, 3], [6, 3], [7, 3], [8, 3], [9, 3]]
b.size > c.size
# 10 > 9 #=> true
arr.map(&:uniq) is the same as:
arr.map { |a| a.uniq }
a.reduce(:+) uses the form of Enumerable#reduce that takes an argument that is a symbol, naming a method to be applied to each element of arr, the result being the sum of the three arrays that comprise the elements of b.
If the arrays are in a consistent format, like all numbers and not a mix of floating point and strings, you can do this:
array1 & array2
# => [[2, 3]]
That means to test if they overlap:
(array1 & array2).any?
If you can be assured each of the elements in all of the arrays are unique, then you can test quickly if there are any duplicates:
sum = array1 + array2 + array3 + array4
sum.length == sum.uniq.length
If each array may contain duplicates then you'd need to pair them off and compare A vs. B for all possible pairs.

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