Skip to content

Instantly share code, notes, and snippets.

@ahoward
Created August 20, 2010 04:55
Show Gist options
  • Save ahoward/539638 to your computer and use it in GitHub Desktop.
Save ahoward/539638 to your computer and use it in GitHub Desktop.
class Array
# Array.zipper: like Array#zip, but produces and full and even distributed
# array for 1,2, or N arrays. eg:
#
# a = %w( a b c d e f g h i )
# b = %w( 0 1 2 )
# c = %w( A B C D E F )
#
# zippered = Array.zipper(a, b, c)
#
# p zippered #=> ["a", "b", "c", "A", "B", "0", "d", "e", "f", "C", "D", "1", "g", "h", "i", "E", "F", "2"]
# p a => a.zippered #=> {["a", "b", "c", "d", "e", "f", "g", "h", "i"]=>[[0, 1, 2], [6, 7, 8], [12, 13, 14]]}
# p b => b.zippered #=> {["0", "1", "2"]=>[[5], [11], [17]]}
# p c => c.zippered #=> {["A", "B", "C", "D", "E", "F"]=>[[3, 4], [9, 10], [15, 16]]}
#
def Array.zipper(*arrays)
# determine the total new array size, bless the component arrays with some
# super powers, and sort biggest component first
#
size = 0
arrays.map! do |array|
array.extend(Zippered)
array.offset = 0
array.zippered = []
size += array.size
array
end
arrays.sort!{|a, b| b.size <=> a.size}
# the zipperd array will be total size of all components
#
zippered = Array.new(size)
# our indexes. structs just make the syntax nice later
#
dst = Struct.new(:offset, :length).new(0, nil)
src = Struct.new(:offset, :length).new(0, nil)
# pull from each component array slice wise to obtain a nice even
# distribution in the zippered output array
#
while((dst.offset < size))
arrays.each do |array|
offset = array.offset
length = [array.size / arrays.size, 1].max
if(offset + (2 * length) > array.size)
remaining = array.size - offset
length = remaining
end
src.offset = array.offset
src.length = length.ceil
dst.length = length.floor
zippered[*dst] = array[*src]
if $DEBUG
p :zippered => zippered
p :array => array
p :dst => dst.to_a
p :src => src.to_a
puts
end
indexes = Array.new(dst.length){|i| i + dst.offset}
array.zippered.push(indexes)
array.offset += length
dst.offset += length
end
end
return zippered
end
module Zippered
attr_accessor :offset
attr_accessor :zippered
end
end
if $0 == __FILE__
require 'test/unit'
class Array::Zippered::Test < Test::Unit::TestCase
def test_no_arrays
zippered = nil
assert_nothing_raised{ zippered = Array.zipper() }
assert zippered==[], zippered.inspect
end
def test_empty_arrays
zippered = nil
assert_nothing_raised{ zippered = Array.zipper([]) }
assert zippered==[], zippered.inspect
assert_nothing_raised{ zippered = Array.zipper([], []) }
assert zippered==[], zippered.inspect
end
def test_one_argument_arrays
zippered = nil
assert_nothing_raised{ zippered = Array.zipper([42]) }
assert zippered==[42], zippered.inspect
assert_nothing_raised{ zippered = Array.zipper([40], [2]) }
assert zippered==[40, 2], zippered.inspect
end
def test_two_argument_arrays
zippered = nil
assert_nothing_raised{ zippered = Array.zipper([40, 2]) }
assert zippered==[40, 2], zippered.inspect
assert_nothing_raised{ zippered = Array.zipper([40, 2], [40.0, 2.0]) }
assert zippered==[40, 40.0, 2, 2.0], zippered.inspect
end
def test_varying_arrays
zippered = nil
a = %w( a b c d e f g h i )
b = %w( 0 1 2 )
c = %w( A B C D E F )
assert_nothing_raised{ zippered = Array.zipper(a, b, c) }
assert zippered==["a", "b", "c", "A", "B", "0", "d", "e", "f", "C", "D", "1", "g", "h", "i", "E", "F", "2"],
zippered.inspect
assert a.zippered==[[0, 1, 2], [6, 7, 8], [12, 13, 14]],
a.zippered.inspect
assert b.zippered==[[5], [11], [17]],
b.zippered.inspect
assert c.zippered==[[3, 4], [9, 10], [15, 16]],
c.zippered.inspect
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment