Reputation: 2661
A user is given two inputs, A
and B
(collections), and a number, X
, and must create an output. The output starts with the first value from A
, then takes X
units from B
, then the second value of A
, and continuing like that until both A
and B
have been exhausted. You can assume 1 < X < sizeof(B)
. However, if either A
or B
runs out, you should loop back to the beginning of the short collection, and continue until the other runs out. The looping back should continue until both collections have hit their end.
What is an elegant way to do this in Ruby? I have some half-broken code and can't seem to find a good way to do this. No, it's not a homework problem. I have strange hobbies.
Here's some sample tests of the behavior I want:
# SomeModule.copy(A, B, X)
SomeModule.copy(%w(a1 a2 a3), %w(b1 b2 b3), 1) == %w(a1 b1 a2 b2 a3 b3))
SomeModule.copy(%w(a1), %w(b1), 1) == %w(a1 b1))
SomeModule.copy(%w(a1), %w(b1 b2 b3), 1) == %w(a1 b1 a1 b2 a1 b3))
SomeModule.copy(%w(a1 a2), %w(b1 b2 b3 b4 b5), 2) == %w(a1 b1 b2 a2 b3 b4 a1 b5 b1))
SomeModule.copy(%w(a1 a2), %w(b1 b2 b3 b4 b5), 2) == %w(a1 b1 b2 a2 b3 b4 a1 b5 b1))
SomeModule.copy(%w(a1 a2 a3 a4), %w(b1 b2 b3 b4 b5), 3) == %w(a1 b1 b2 b3 a2 b4 b5 b1 a3 b2 b3 b4 a4 b5 b1 b2))
Upvotes: 0
Views: 103
Reputation: 1256
def copy(array1, array2, slice_length)
iterations = [array1.length,
(array2.length.to_f / slice_length.to_f).round
].max
result = []
enum1 = array1.cycle
enum2 = array2.cycle
iterations.times do
result << enum1.next
slice_length.times { result << enum2.next }
end
result
end
require 'minitest/autorun'
class TestCopy < Minitest::Test
def test_copy
assert_equal(%w(a1 b1 a2 b2 a3 b3), copy(%w(a1 a2 a3), %w(b1 b2 b3), 1))
assert_equal(%w(a1 b1), copy(%w(a1), %w(b1), 1))
assert_equal(%w(a1 b1 a1 b2 a1 b3), copy(%w(a1), %w(b1 b2 b3), 1))
assert_equal(%w(a1 b1 b2 a2 b3 b4 a1 b5 b1), copy(%w(a1 a2), %w(b1 b2 b3 b4 b5), 2))
assert_equal(%w(a1 b1 b2 a2 b3 b4 a1 b5 b1), copy(%w(a1 a2), %w(b1 b2 b3 b4 b5), 2))
assert_equal(%w(a1 b1 b2 b3 a2 b4 b5 b1 a3 b2 b3 b4 a4 b5 b1 b2), copy(%w(a1 a2 a3 a4), %w(b1 b2 b3 b4 b5), 3))
end
end
Upvotes: 0
Reputation: 2661
Here's what finally jumped out of my brain at 4.30am:
class InfiniteArray
private
attr_reader :collection
attr_accessor :index, :wrapped_count
public
def initialize(collection)
@collection = collection
reset
end
def next
value = collection[index]
if index < collection.length - 1
self.index = index + 1
else
self.index = 0
self.wrapped_count = wrapped_count + 1
end
value
end
def wrapped?
wrapped_count > 0
end
def reset
@index = 0
@wrapped_count = 0
end
end
class IntervalCopier
private
attr_reader :source_collection, :destination_collection, :interval
public
def initialize(source_collection, destination_collection, interval)
@source_collection = InfiniteArray.new(source_collection)
@destination_collection = InfiniteArray.new(destination_collection)
@interval = interval
end
def copy
final = []
until source_collection.wrapped? && destination_collection.wrapped? do
final << source_collection.next
interval.times { final << destination_collection.next }
end
[source_collection, destination_collection].each(&:reset)
final
end
end
Upvotes: 0
Reputation: 80041
Enumerable
has an each_slice
method that'll let you iterate over an enumerable object N items at a time. Using it, you can do something like this:
a1 = [1, 2, 3, 4, 5, 6]
a2 = [10, 11, 12, 20, 21, 22, 30, 31, 32, 40, 41, 42, 50, 51, 52, 60]
a1.each_slice(1).zip(a2.each_slice(3)).flatten
# => [1, 10, 11, 12, 2, 20, 21, 22, 3, 30, 31, 32, 4, 40, 41, 42, 5, 50, 51, 52, 6, 60]
Upvotes: 1
Reputation: 6834
Here is something, the idea is to iterate every array until both of them reach their max index:
def custom_combine(a,b,x)
max_index_a = a.count - 1
max_index_b = b.count - 1
a_run_out = false
b_run_out = false
output = []
a.cycle.each_with_index do |elem_a, index_a|
output << elem_a
# base on a index get x elements from b
x.times do |i|
index_b = ((index_a * x) + i) % (max_index_b + 1)
output << b[index_b]
b_run_out = true if index_b == max_index_b
end
a_run_out = true if index_a == max_index_a
break if a_run_out && b_run_out
end
output
end
a = %w(a1 a2 a3 a4)
b = %w(b1 b2 b3 b4 b5)
x = 3
custom_combine(a,b,x)
# => ["a1", "b1", "b2", "b3", "a2", "b4", "b5", "b1", "a3", "b2", "b3", "b4", "a4", "b5", "b1", "b2"]
Upvotes: 1