2013-10-10 11 views
10

Voglio unire due elenchi in python, con gli elenchi di diverse lunghezze, in modo che gli elementi della lista più breve siano equamente distribuiti all'interno dell'elenco finale possibile. io voglio prendere [1, 2, 3, 4] e ['a','b'] e unirli per ottenere un elenco simile a [1, 'a', 2, 3, 'b', 4]. Deve essere in grado di funzionare con elenchi che non sono multipli esatti, quindi potrebbe richiedere [1, 2, 3, 4, 5] e ['a', 'b', 'c'] e produrre [1, 'a', 2, 'b', 3, 'c', 4, 5] o simili. Ha bisogno di preservare l'ordine di entrambe le liste.Come interleave elegantemente due elenchi di lunghezza non uniforme in python?

Posso vedere come farlo con un metodo a forza bruta prolisso ma poiché Python sembra avere una vasta gamma di strumenti eccellenti per fare ogni sorta di cose intelligenti di cui non so (ancora) mi sono chiesto se c'è qualcosa di più elegante che posso usare?

NB: sto usando Python 3.3.

+0

Perché il '' b'' arriva tra 3 e 4? – kennytm

+0

Perché li spazia in modo uniforme all'interno dell'elenco. Prenderò anche [[a, 1, 2, 'b', 3, 4] ', ma la rotazione sembra più bella ai miei occhi. –

+0

http://docs.python.org/2/library/functions.html#slice potrebbe aiutare. Vedi anche il modulo 'itertools'. Ora la parte importante qui è l'algoritmo, non gli strumenti stessi. –

risposta

5

se a è la lista più lunga e b è la più corta

from itertools import groupby 

len_ab = len(a) + len(b) 
groups = groupby(((a[len(a)*i//len_ab], b[len(b)*i//len_ab]) for i in range(len_ab)), 
       key=lambda x:x[0]) 
[j[i] for k,g in groups for i,j in enumerate(g)] 

esempio

>>> a = range(8) 
>>> b = list("abc") 
>>> len_ab = len(a) + len(b) 
>>> groups = groupby(((a[len(a)*i//len_ab], b[len(b)*i//len_ab]) for i in range(len_ab)), key=lambda x:x[0]) 
>>> [j[i] for k,g in groups for i,j in enumerate(g)] 
[0, 'a', 1, 2, 'b', 3, 4, 5, 'c', 6, 7] 

È possibile utilizzare questo trucco per assicurarsi a è più lungo di b

b, a = sorted((a, b), key=len) 
+0

Questa è stata la soluzione alla fine, anche se ho modificato la riga finale a '[j [i] per _, g in gruppi per i, j in enumerate (g)]' in modo da sopprimere un avvertimento su un inutilizzato variabile. –

8

Prendendo in prestito pesantemente da una soluzione Jon Clements', si potrebbe scrivere una funzione che prende un numero arbitrario di sequenze e restituisce sequenze fuse di elementi uniformemente distanziate:

import itertools as IT 

def evenly_spaced(*iterables): 
    """ 
    >>> evenly_spaced(range(10), list('abc')) 
    [0, 1, 'a', 2, 3, 4, 'b', 5, 6, 7, 'c', 8, 9] 
    """ 
    return [item[1] for item in 
      sorted(IT.chain.from_iterable(
      zip(IT.count(start=1.0/(len(seq) + 1), 
         step=1.0/(len(seq) + 1)), seq) 
      for seq in iterables))] 

iterables = [ 
    ['X']*2, 
    range(1, 11), 
    ['a']*3 
    ] 

print(evenly_spaced(*iterables)) 

rendimenti

[1, 2, 'a', 3, 'X', 4, 5, 'a', 6, 7, 'X', 8, 'a', 9, 10] 
10

Questo è fondamentalmente uguale a Bresenham's line algorithm. È possibile calcolare le posizioni "pixel" e utilizzarle come indici negli elenchi.

Il punto in cui l'attività è diversa è che si desidera visualizzare ogni elemento solo una volta. Dovresti modificare l'algoritmo o post-elaborare gli indici, aggiungendo gli elementi dagli elenchi solo la prima volta che appaiono. C'è una leggera ambiguità, tuttavia: quando entrambi gli indici di pixel/elenco cambiano allo stesso tempo, dovrai scegliere quale includere prima. Ciò corrisponde alle due diverse opzioni per l'interleaving delle liste menzionate nella domanda e un commento.

+1

In effetti lo è. Che bel pezzo di pensiero laterale. –

6

Con il presupposto che a è la sequenza da inserire in:

from itertools import izip, count 
from operator import itemgetter 
import heapq 

a = [1, 2, 3, 4] 
b = ['a', 'b'] 

fst = enumerate(a) 
snd = izip(count(0, len(a) // len(b)), b) 
print map(itemgetter(1), heapq.merge(fst, snd)) 
# [1, 'a', 2, 3, 'b', 4] 
+0

@JackAidley ugh - ovviamente in Python 3.x - un secondo;) –

+0

intelligente. Per python3 è probabilmente più ordinato usare una list comprehension di 'list (map (...))' –

4

Se modifichiamo @ risposta di Jon come questo

from itertools import count 
import heapq 

[x[1] for x in heapq.merge(izip(count(0, len(b)), a), izip(count(0, len(a)), b))] 

non importa quale delle a/b è più lunga

1

Se vogliamo per fare questo senza itertools:

def interleave(l1, l2, default=None): 
    max_l = max(len(l1), len(l2)) 
    data = map(lambda x: x + [default] * (max_l - len(x)), [l1,l2]) 
    return [data[i%2][i/2] for i in xrange(2*max_l)] 

Ah, mis sed la parte ugualmente distanziata. Questo è stato, per qualche motivo, contrassegnato come duplicato con una domanda che non richiedeva una spaziatura uniforme in presenza di diverse lunghezze di lista.

+0

Grazie per aver risposto a una domanda e benvenuto in SO. Per il tuo riferimento futuro, il tuo commento modificato probabilmente non è necessario (potresti averlo postato quando hai effettuato la modifica lì) o potresti indicare che hai modificato il post, ad esempio "** Modifica ** Ho perso ..." –

-1
# Given 
a = [1, 2, 3, 4] 
b = ['a','b'] 
c = [1, 2, 3, 4, 5] 
d = ['a', 'b', 'c'] 

È possibile alternare attraverso l'iterabile più lunga da:

import itertools as it 

list(i for i in it.chain(*it.zip_longest(a, b)) if i is not None) 
# [1, 'a', 2, 'b', 3, 4] 

list(i for i in it.chain(*it.zip_longest(c, d)) if i is not None) 
# [1, 'a', 2, 'b', 3, 'c', 4, 5] 

Considera anche l'installazione di more_itertools, fornito con interleave_longest e interleave.

import more_itertools 

list(more_itertools.interleave_longest(a, b)) 
# [1, 'a', 2, 'b', 3, 4] 

list(more_itertools.interleave_longest(c, d)) 
# [1, 'a', 2, 'b', 3, 'c', 4, 5] 
+0

Ciò produce spaziatura uguale attraverso la lista, cioè prova 'a = [1,2,3,4,5,6]' e 'b = ['a', 'b', 'c']' –

+0

Questa soluzione si rivolge il titolo, "Come interlacciare elegantemente due liste di lunghezza non uniforme in python?". Mentre le prime uscite non sono personalizzate, ad es. '[1, 'a', 2, 3, 'b', 4]', sono intuitivi e comunque accettabili secondo il commento dell'OP "Prenderò' ['a, 1, 2,' b ', 3, 4] 'pure ..." – pylang

+0

Non così, come da prima frase della domanda: "Voglio unire due liste in python, con gli elenchi di diverse lunghezze, in modo che gli elementi della lista più corta ** sono quanto più distanziati all'interno dell'elenco finale possibile **. " –

Problemi correlati