2010-08-27 5 views
7

Si je veux entrelacer un ensemble de tableaux en Ruby, et chaque tableau a été la même longueur, on peut le faire que:Comment imbriquer des tableaux de longueur différente dans Ruby

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

Cependant, comment pouvons-nous résoudre ce problème si les tableaux peuvent être de tailles différentes?

Nous pourrions faire quelque chose comme:

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 

Mais est-il une meilleure façon « Ruby », en utilisant peut-être zip ou transposer ou un tel?

Répondre

7

Si les tableaux sources n'ont pas nil seulement eux, vous devez étendre le premier tableau avec nil s, ZIP pad automatiquement les autres avec nil. Cela signifie également que vous aurez à utiliser compact pour nettoyer les entrées supplémentaires sur ce qui est je l'espère plus efficace que les boucles explicites

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 

Voici une version un peu plus compliquée qui fonctionne si les tableaux font contiennent 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 
5

Votre implémentation me semble bonne. Vous pouvez réaliser cela en utilisant #zip en remplissant les tableaux avec une valeur de poubelle, les compresser, puis aplatir et enlever les ordures. Mais c'est trop compliqué IMO. Ce que vous avez ici est propre et explicite, il a juste besoin d'être rubyfied.

Édition: Correction de la restriction.

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] 

Modifier: Pour le plaisir

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

Merci, n'a pas considéré args.map (&: taille). En fait, je n'avais pas vu cette approche auparavant. Le paramètre max_length.times est aussi plus propre que mon for-loop. – ChrisInEdmonton

+0

Et j'avais pensé à rembourrer les réseaux plus courts avec des nils, les entrelacer, puis compacter les nils. C'est génial si et seulement si vous pouvez être sûr que vos tableaux sources n'ont pas de Nils en eux. :) – ChrisInEdmonton

6

Voici une approche plus simple. Il profite de l'ordre que vous passez les tableaux à 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([], []) 
# => [] 

attention: cela supprime tous nil « s:

interleave([11], [41, 42, 43, 44, nil]) 
# => [11, 41, 42, 43, 44] 
Questions connexes