Как чередовать массивы разной длины в Ruby - PullRequest
7 голосов
/ 27 августа 2010

Если я хочу чередовать набор массивов в Ruby, и каждый массив имеет одинаковую длину, мы можем сделать это следующим образом:

a.zip(b).zip(c).flatten

Однако, как мы можем решить эту проблему, если массивы могутбыть разных размеров?

Мы могли бы сделать что-то вроде:

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

Но есть ли лучший способ "Ruby", возможно, с использованием zip или transpose или чего-то такого?

Ответы [ 3 ]

7 голосов
/ 28 августа 2010

Если исходные массивы не содержат nil, вам нужно только расширить первый массив с помощью nil s, zip автоматически дополнит остальные значения nil.Это также означает, что вы можете использовать compact для очистки дополнительных записей, что, как мы надеемся, более эффективно, чем явные циклы

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

Вот немного более сложная версия, которая работает, если массивы do содержат 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
7 голосов
/ 25 апреля 2013

Вот более простой подход.Используется порядок передачи массивов на 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([], [])
# => []

Будьте осторожны: это удаляет все nil:

interleave([11], [41, 42, 43, 44, nil])
# => [11, 41, 42, 43, 44]
5 голосов
/ 28 августа 2010

Ваша реализация выглядит хорошо для меня. Вы могли бы достичь этого, используя #zip, заполнив массивы некоторым значением мусора, сжав их, затем выровняв и убрав мусор. Но это слишком запутанная ИМО. То, что у вас здесь, чисто и самоочевидно, его просто нужно рубифицировать.

Редактировать : исправлена ​​ошибка.

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]

Редактировать : для развлечения

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]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...