How to interleave arrays of different length in Ruby - ruby

If I want to interleave a set of arrays in Ruby, and each array was the same length, we could do so as:
a.zip(b).zip(c).flatten
However, how do we solve this problem if the arrays can be different sizes?
We could do something like:
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.inject(0) { |length, elem| length = [length, elem.length].max }
output = Array.new
for i in 0...max_length
args.each { |elem|
output << elem[i] if i < elem.length
}
end
return output
end
But is there a better 'Ruby' way, perhaps using zip or transpose or some such?

Here is a simpler approach. It takes advantage of the order that you pass the arrays to zip:
def interleave(a, b)
if a.length >= b.length
a.zip(b)
else
b.zip(a).map(&:reverse)
end.flatten.compact
end
interleave([21, 22], [31, 32, 33])
# => [21, 31, 22, 32, 33]
interleave([31, 32, 33], [21, 22])
# => [31, 21, 32, 22, 33]
interleave([], [21, 22])
# => [21, 22]
interleave([], [])
# => []
Be warned: this removes all nil's:
interleave([11], [41, 42, 43, 44, nil])
# => [11, 41, 42, 43, 44]

If the source arrays don't have nil in them, you only need to extend the first array with nils, zip will automatically pad the others with nil. This also means you get to use compact to clean the extra entries out which is hopefully more efficient than explicit loops
def interleave(a,*args)
max_length = args.map(&:size).max
padding = [nil]*[max_length-a.size, 0].max
(a+padding).zip(*args).flatten.compact
end
Here is a slightly more complicated version that works if the arrays do contain nil
def interleave(*args)
max_length = args.map(&:size).max
pad = Object.new()
args = args.map{|a| a.dup.fill(pad,(a.size...max_length))}
([pad]*max_length).zip(*args).flatten-[pad]
end

Your implementation looks good to me. You could achieve this using #zip by filling the arrays with some garbage value, zip them, then flatten and remove the garbage. But that's too convoluted IMO. What you have here is clean and self explanatory, it just needs to be rubyfied.
Edit: Fixed the booboo.
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
output = []
max_length.times do |i|
args.each do |elem|
output << elem[i] if i < elem.length
end
end
output
end
a = [*1..5]
# => [1, 2, 3, 4, 5]
b = [*6..15]
# => [6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
c = [*16..18]
# => [16, 17, 18]
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]
Edit: For fun
def interleave(*args)
raise 'No arrays to interleave' if args.empty?
max_length = args.map(&:size).max
# assumes no values coming in will contain nil. using dup because fill mutates
args.map{|e| e.dup.fill(nil, e.size...max_length)}.inject(:zip).flatten.compact
end
interleave(a,b,c)
# => [1, 6, 16, 2, 7, 17, 3, 8, 18, 4, 9, 5, 10, 11, 12, 13, 14, 15]

Related

Ruby: replacing a matching element in a multidimensional array?

Could someone tell me how I can achieve replacing an element in this 2D array? I tried each, include and replace and wasn't able to figure out where I am going wrong. Thank you in advance for any help.
class Lotto
def initialize
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..60)})}
end
def current_pick
#number = rand(1..60).to_s
puts "The number is #{#number}."
end
def has_number
#prints out initial slip
#lotto_slip.each {|x| p x}
#Prints slip with an "X" replacing number if is on slip
#Ex: #number equals 4th number on slip --> 1, 2, 3, X, 5, 6
#lotto_slip.each do |z|
if z.include?(#number)
z = "X"
p #lotto_slip
else
z = z
p #lotto_slip
end
end
end
end
test = Lotto.new
test.current_pick
test.has_number
Let me know if this works out (tried to reduce the variations from 1 to 10 in order to be able to test easier):
class Lotto
def initialize
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..10)})}
end
def current_pick
#number = rand(1..10)
puts "The number is #{#number}."
end
def has_number
#prints out initial slip
#lotto_slip.each {|x| p x}
#Prints slip with an "X" replacing number if is on slip
#Ex: #number equals 4th number on slip --> 1, 2, 3, X, 5, 6
#lotto_slip.each do |z|
if z.include?(#number)
p "#{#number} included in #{z}"
z.map! { |x| x == #number ? 'X' : x}
end
end
#lotto_slip
end
end
test = Lotto.new
test.current_pick
p test.has_number
The problems I saw with your code are:
You don't need the to_s for this line #number = rand(1..60).to_s, else how are you going to compare the numbers produced by the array with an actual string?
You need to re-generate the array instead of re-assigning, that's why I've replaced all of that code with z.map! { |x| x == #number ? 'X' : x} which basically re-generates the entire array.
Not necessary iterate with each, use map:
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..60)})}
#=> [[25, 22, 10, 10, 57, 17], [37, 4, 8, 52, 55, 7], [44, 30, 58, 58, 50, 19], [49, 49, 24, 31, 26, 28], [24, 18, 39, 27, 8, 54]]
#number = 24
#lotto_slip.map{|x| x.map{|x| x == #number ? 'X' : x}}
#=> [[25, 22, 10, 10, 57, 17], [37, 4, 8, 52, 55, 7], [44, 30, 58, 58, 50, 19], [49, 49, "X", 31, 26, 28], ["X", 18, 39, 27, 8, 54]]

Ruby reducing a number array into start end range array

I have an array of numbers as below:
[11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
I would like to reduce this array to:
[[11,14], [19,21], [29,30], [33,33]]
Identify consequent numbers in an array and push only the start and end of its ranges.
How to achieve this?
Exactly some problem is solved to give an example for slice_before method in ruby docs:
a = [0, 2, 3, 4, 6, 7, 9]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
es.length <= 2 ? es.join(",") : "#{es.first}-#{es.last}"
}.join(",")
In your case you need to tweak it a little:
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
[es.first, es.last]
}
Here's another way, using an enumerator with Enumerator#next and Enumerator#peek. It works for any collection that implements succ (aka next).
Code
def group_consecs(a)
enum = a.each
pairs = [[enum.next]]
loop do
if pairs.last.last.succ == enum.peek
pairs.last << enum.next
else
pairs << [enum.next]
end
end
pairs.map { |g| (g.size > 1) ? g : g*2 }
end
Note that Enumerator#peek raises a StopInteration exception if the enumerator enum is already at the end when enum.peek is invoked. That exception is handled by Kernel#loop, which breaks the loop.
Examples
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
group_consecs(a)
#=> [[11, 12, 13, 14], [19, 20, 21], [29, 30], [33, 33]]
a = ['a','b','c','f','g','i','l','m']
group_consecs(a)
#=> [["a", "b", "c"], ["f", "g"], ["i", "i"], ["l", "m"]]
a = ['aa','ab','ac','af','ag','ai','al','am']
group_consecs(a)
#=> [["aa", "ab", "ac"], ["af", "ag"], ["ai, ai"], ["al", "am"]]
a = [:a,:b,:c,:f,:g,:i,:l,:m]
group_consecs(a)
#=> [[:a, :b, :c], [:f, :g], [:i, :i], [:l, :m]]
Generate an array of seven date objects for an example, then group consecutive dates:
require 'date'
today = Date.today
a = 10.times.map { today = today.succ }.values_at(0,1,2,5,6,8,9)
#=> [#<Date: 2014-08-07 ((2456877j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-08 ((2456878j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-09 ((2456879j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-12 ((2456882j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-13 ((2456883j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-15 ((2456885j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-16 ((2456886j,0s,0n),+0s,2299161j)>]
group_consecs(a)
#=> [[#<Date: 2014-08-07 ((2456877j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-08 ((2456878j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-09 ((2456879j,0s,0n),+0s,2299161j)>
# ],
# [#<Date: 2014-08-12 ((2456882j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-13 ((2456883j,0s,0n),+0s,2299161j)>
# ],
# [#<Date: 2014-08-15 ((2456885j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-16 ((2456886j,0s,0n),+0s,2299161j)>
# ]]
This is some code I wrote for a project a while ago:
class Array
# [1,2,4,5,6,7,9,13].to_ranges # => [1..2, 4..7, 9..9, 13..13]
# [1,2,4,5,6,7,9,13].to_ranges(true) # => [1..2, 4..7, 9, 13]
def to_ranges(non_ranges_ok=false)
self.sort.each_with_index.chunk { |x, i| x - i }.map { |diff, pairs|
if (non_ranges_ok)
pairs.first[0] == pairs.last[0] ? pairs.first[0] : pairs.first[0] .. pairs.last[0]
else
pairs.first[0] .. pairs.last[0]
end
}
end
end
if ($0 == __FILE__)
require 'awesome_print'
ary = [1, 2, 4, 5, 6, 7, 9, 13, 12]
ary.to_ranges(false) # => [1..2, 4..7, 9..9, 12..13]
ary.to_ranges(true) # => [1..2, 4..7, 9, 12..13]
ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
ary.to_ranges(false) # => [1..12]
ary.to_ranges(true) # => [1..12]
end
It's easy to change that to only return the start/end pairs:
class Array
def to_range_pairs(non_ranges_ok=false)
self.sort.each_with_index.chunk { |x, i| x - i }.map { |diff, pairs|
if (non_ranges_ok)
pairs.first[0] == pairs.last[0] ? [pairs.first[0]] : [pairs.first[0], pairs.last[0]]
else
[pairs.first[0], pairs.last[0]]
end
}
end
end
if ($0 == __FILE__)
require 'awesome_print'
ary = [1, 2, 4, 5, 6, 7, 9, 13, 12]
ary.to_range_pairs(false) # => [[1, 2], [4, 7], [9, 9], [12, 13]]
ary.to_range_pairs(true) # => [[1, 2], [4, 7], [9], [12, 13]]
ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
ary.to_range_pairs(false) # => [[1, 12]]
ary.to_range_pairs(true) # => [[1, 12]]
end
Here's an elegant solution:
arr = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
output = []
# Sort array
arr.sort!
# Loop through each element in the list
arr.each do |element|
# Set defaults - for if there are no consecutive numbers in the list
start = element
endd = element
# Loop through consecutive numbers and check if they are inside the list
i = 1
while arr.include?(element+i) do
# Set element as endd
endd = element+i
# Remove element from list
arr.delete(element+i)
# Increment i
i += 1
end
# Push [start, endd] pair to output
output.push([start, endd])
end
[Edit: Ha! I misunderstood the question. In your example, for the array
a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
you showed the desired array of pairs to be:
[[11,14], [19,21], [29,30], [33,33]]
which correspond to the following offsets in a:
[[0,3], [4,6], [7,8], [9,9]]
These pairs respective span the first 4 elements, the next 3 elements, then next 2 elements and the next element (by coincidence, evidently). I thought you wanted such pairs, each with a span one less than the previous, and the span of the first being as large as possible. If you have a quick look at my examples below, my assumption may be clearer. Looking back I don't know why I didn't understand the question correctly (I should have looked at the answers), but there you have it.
Despite my mistake, I'll leave this up as I found it an interesting problem, and had the opportunity to use the quadratic formula in the solution.
tidE]
This is how I would do it.
Code
def pull_pairs(a)
n = ((-1 + Math.sqrt(1.0 + 8*a.size))/2).to_i
cum = 0
n.downto(1).map do |i|
first = cum
cum += i
[a[first], a[cum-1]]
end
end
Examples
a = %w{a b c d e f g h i j k l}
#=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]
pull_pairs(a)
#=> [["a", "d"], ["e", "g"], ["h", "i"], ["j", "j"]]
a = [*(1..25)]
#=> [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
# 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
pull_pairs(a)
#=> [[1, 6], [7, 11], [12, 15], [16, 18], [19, 20], [21, 21]]
a = [*(1..990)]
#=> [1, 2,..., 990]
pull_pairs(a)
#=> [[1, 44], [45, 87],..., [988, 989], [990, 990]]
Explanation
First, we'll compute the the number of pairs of values in the array we will produce. We are given an array (expressed algebraically):
a = [a0,a1,...a(m-1)]
where m = a.size.
Given n > 0, the array to be produced is:
[[a0,a(n-1)], [a(n),a(2n-2)],...,[a(t),a(t)]]
These elements span the first n+(n-1)+...+1 elements of a. As this is an arithmetic progession, the sum equals n(n+1)/2. Ergo,
t = n(n+1)/2 - 1
Now t <= m-1, so we maximize the number of pairs in the output array by choosing the largest n such that
n(n+1)/2 <= m
which is the float solution for n in the quadratic:
n^2+n-2m = 0
rounded down to an integer, which is
int((-1+sqrt(1^1+4(1)(2m))/2)
or
int((-1+sqrt(1+8m))/2)
Suppose
a = %w{a b c d e f g h i j k l}
Then m (=a.size) = 12, so:
n = int((-1+sqrt(97))/2) = 4
and the desired array would be:
[['a','d'],['e','g'],['h','i'],['j','j']]
Once n has been computed, constructing the array of pairs is straightforward.

Splitting an array by performing an arithmetic function in ruby

I have an array in Ruby like [3,4,5] and I want to create sub-arrays by diving or multiplying. For example, I want to multiply each number in the array by 2, 3, and 4, returning [[6,9,12],[8,12,16],[10,15,20]]
After that, what's the best way to count the total number of units? In this example, it would be 9, while array.count would return 3.
Thanks
The simplest way I could think of was:
[3,4,5].map { |v|
[3,4,5].map { |w|
w * v
}
}
I'm sure there is a more elegant way.
As for the count you can use flatten to turn it into a single array containing all the elements.
[[9, 12, 15], [12, 16, 20], [15, 20, 25]].flatten
=> [9, 12, 15, 12, 16, 20, 15, 20, 25]
You might find it convenient to use matrix operations for this, particularly if it is one step among several involving matrices, vectors, and/or scalars.
Code
require 'matrix'
def doit(arr1, arr2)
(Matrix.column_vector(arr2) * Matrix.row_vector(arr1)).to_a
end
def nbr_elements(arr1, arr2) arr1.size * arr2.size end
Examples
arr1 = [3,4,5]
arr2 = [3,4,5]
doit(arr1, arr2)
#=> [[ 9, 12, 15],
# [12, 16, 20],
# [15, 20, 25]]
nbr_elements(arr1, arr2)
#=> 9
doit([1,2,3], [4,5,6,7])
#=> [[4, 8, 12],
# [5, 10, 15],
# [6, 12, 18],
# [7, 14, 21]]
nbr_elements([1,2,3], [4,5,6,7])
#=> 12
Alternative
If you don't want to use matrix operations, you could do it like this:
arr2.map { |e| [e].product(arr1).map { |e,f| e*f } }
Here's an example:
arr1 = [1,2,3]
arr2 = [4,5,6,7]
arr2.map { |e| [e].product(arr1).map { |e,f| e*f } }
#=> [[4, 8, 12],
# [5, 10, 15],
# [6, 12, 18],
# [7, 14, 21]]

Array select to get true and false arrays?

I know I can get this easily:
array = [45, 89, 23, 11, 102, 95]
lower_than_50 = array.select{ |n| n<50}
greater_than_50 = array.select{ |n| !n<50}
But is there a method (or an elegant manner) to get this by only running select once?
[lower_than_50, greater_than_50] = array.split_boolean{ |n| n<50}
over, under_or_equal = [45, 89, 23, 11, 102, 95].partition{|x| x>50 }
Or simply:
result = array.partition{|x| x>50 }
p result #=> [[89, 102, 95], [45, 23, 11]]
if you rather want the result as one array with two sub-arrays.
Edit: As a bonus, here is how you would to it if you have more than two alternatives and want to split the numbers:
my_custom_grouping = -> x do
case x
when 1..50 then :small
when 51..100 then :large
else :unclassified
end
end
p [-1,2,40,70,120].group_by(&my_custom_grouping) #=> {:unclassified=>[-1, 120], :small=>[2, 40], :large=>[70]}
The answer above is spot on!
Here is a general solution for more than two partitions (for example: <20, <50, >=50):
arr = [45, 89, 23, 11, 102, 95]
arr.group_by { |i| i < 20 ? 'a' : i < 50 ? 'b' : 'c' }.sort.map(&:last)
=> [[11], [45, 23], [89, 102, 95]]
This can be very useful if you're grouping by chunks (or any mathematically computable index such as modulo):
arr.group_by { |i| i / 50 }.sort.map(&:last)
=> [[45, 23, 11], [89, 95], [102]]

Every Other 2 Items in Array

I need a ruby formula to create an array of integers. The array must be every other 2 numbers as follows.
[2, 3, 6, 7, 10, 11, 14, 15, 18, 19...]
I have read a lot about how I can do every other number or multiples, but I am not sure of the best way to achieve what I need.
Here's an approach that works on any array.
def every_other_two arr
arr.select.with_index do |_, idx|
idx % 4 > 1
end
end
every_other_two((0...20).to_a) # => [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
# it works on any array
every_other_two %w{one two three four five six} # => ["three", "four"]
array = []
#Change 100000 to whatever is your upper limit
100000.times do |i|
array << i if i%4 > 1
end
This code works for any start number to any end limit
i = 3
j = 19
x =[]
(i...j).each do |y|
x << y if (y-i)%4<2
end
puts x
this should work
For fun, using lazy enumerables (requires Ruby 2.0 or gem enumerable-lazy):
(2..Float::INFINITY).step(4).lazy.map(&:to_i).flat_map { |x| [x, x+1] }.first(8)
#=> => [2, 3, 6, 7, 10, 11, 14, 15]
here's a solution that works with infinite streams:
enum = Enumerator.new do |y|
(2...1/0.0).each_slice(4) do |slice|
slice[0 .. 1].each { |n| y.yield(n) }
end
end
enum.first(10) #=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
enum.each do |n|
puts n
end
Single Liner:
(0..20).to_a.reduce([0,[]]){|(count,arr),ele| arr << ele if count%4 > 1;
[count+1,arr] }.last
Explanation:
Starts the reduce look with 0,[] in count,arr vars
Add current element to array if condition satisfied. Block returns increment and arr for the next iteration.
I agree though that it is not so much of a single liner though and a bit complex looking.
Here's a slightly more general version of Sergio's fine answer
module Enumerable
def every_other(slice=1)
mod = slice*2
res = select.with_index { |_, i| i % mod >= slice }
block_given? ? res.map{|x| yield(x)} : res
end
end
irb> (0...20).every_other
=> [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
irb> (0...20).every_other(2)
=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
irb> (0...20).every_other(3)
=> [3, 4, 5, 9, 10, 11, 15, 16, 17]
irb> (0...20).every_other(5) {|v| v*10 }
=> [50, 60, 70, 80, 90, 150, 160, 170, 180, 190]

Resources