Array elements are corrupted by lazyness - ruby

Why do we see such difference below? Why exactly such array?
enum = [1,2].lazy.flat_map{[[3,4],[5,6]]}.with_index
enum.to_a.map(&:first)
# => [[3, 4], [5, 6], [3, 4], [5, 6]]
enum.map(&:first).to_a
# => [[], [5], [3, 4], [5, 6]]

The reason this happens has nothing to do with lazy - it has something to do with the subtleties of map(&:...).
The first option (enum.to_a.map(&:first)) translates to:
enum.to_a.map { |x| x.first }
# => [[3, 4], [5, 6], [3, 4], [5, 6]]
when x is [[3,4], 0], then [[5,6], 1]...
The second option, one the other hand translates to:
enum.map { |x, i| x.first(i) }
# => [[], [5], [3, 4], [5, 6]]
which brings the first i item for each x array (since the arrays in our example have only 2 elements, x.first(3) still returns two elements...).
The same would happen without lazy:
enum2 = [1,2].flat_map{[[3,4],[5,6]]}.to_enum.with_index
What to_a does in this case is takes the two parameters of each item in the enumeration, and turns them to a single array item.

Related

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(:+) }

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.

Creating pairs from an Array?

Is there a simple way to create pairs from an array?
For example, if I have an array [1,2,3,4] how would I go about trying to return this array?
[[1,2], [1,3], [1,4], [2,1], [2,3], [2,4], [3,1], [3,2], [3,4], [4,1], [4,2], [4,3]]
Every element is paired with every other other element except itself, and duplicates are allowed.
You can use Array#permutation for this:
[1,2,3,4].permutation(2).to_a
# => [[1, 2], [1, 3], [1, 4], [2, 1], [2, 3], [2, 4], [3, 1], [3, 2], [3, 4], [4, 1], [4, 2], [4, 3]]
[1,2,3,4].permutation(2).map{ |n| "(#{ n.join(",") })" }
# => ["(1,2)", "(1,3)", "(1,4)", "(2,1)", "(2,3)", "(2,4)", "(3,1)", "(3,2)", "(3,4)", "(4,1)", "(4,2)", "(4,3)"]

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

Sorting an array by two values

Suppose I have
an_array = [[2, 3], [1, 4], [1, 3], [2, 1], [1, 2]]
I want to sort this array by the first value of each inner array, and then by the second (so the sorted array should look like this: [[1, 2], [1, 3], [1, 4], [2, 1], [2, 3]])
What's the most readable way to do this?
This is the default behavior for sorting arrays (see the Array#<=> method definition for proof). You should just be able to do:
an_array.sort
If you want some non-default behaviour, investigate sort_by (ruby 1.8.7+)
e.g. sort by the second element then by the first
a.sort_by {|e| [e[1], e[0]]} # => [[2, 1], [1, 2], [1, 3], [2, 3], [1, 4]]
or sort by the first element ascending and then the second element descending
a.sort_by {|e| [e[0], -e[1]]} # => [[1, 4], [1, 3], [1, 2], [2, 3], [2, 1]]
an_array.sort

Resources