Como classificar duas listas (que se referem) exatamente da mesma maneira

139

Digamos que eu tenha duas listas:

list1 = [3, 2, 4, 1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

Se eu executar list1.sort(), ele será ordenado, [1,1,2,3,4]mas existe uma maneira de list2sincronizar também (para que eu possa dizer que o item 4pertence 'three')? Portanto, o resultado esperado seria:

list1 = [1, 1, 2, 3, 4]
list2 = ['one', 'one2', 'two', 'three', 'four']

Meu problema é que tenho um programa bastante complexo que funciona bem com listas, mas preciso começar a referenciar alguns dados. Sei que esta é uma situação perfeita para dicionários, mas estou tentando evitar dicionários no meu processamento porque preciso classificar os valores das chaves (se devo usar dicionários, sei como usá-los).

Basicamente, a natureza desse programa é que os dados são apresentados em uma ordem aleatória (como acima), preciso classificá-los, processá-los e enviar os resultados (a ordem não importa, mas os usuários precisam saber qual resultado pertence a qual chave). Pensei em colocá-lo em um dicionário primeiro e, em seguida, em uma lista de classificação, mas eu não teria como diferenciar itens com o mesmo valor se a ordem não for mantida (pode ter um impacto ao comunicar os resultados aos usuários). Então, idealmente, depois de obter as listas, prefiro descobrir uma maneira de classificar as duas listas. Isso é possível?

Erro 404
fonte
Devo salientar que suas variáveis ​​na lista2 não apontam para as entradas na lista1. Por exemplo, se alterar um valor como list1 [0] = 9 e olhar para a lista2, a lista2 [0] ainda será 3. Com números inteiros em python, ele não usa a referência / ponteiro, ele copia o valor. Você teria sido melhor se vai list2 = list1 [:]
Robert King

Respostas:

242

Uma abordagem clássica para esse problema é usar o idioma "decorar, classificar, não decorar", que é especialmente simples usando a função interna do python zip:

>>> list1 = [3,2,4,1, 1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> list1, list2 = zip(*sorted(zip(list1, list2)))
>>> list1
(1, 1, 2, 3, 4)
>>> list2 
('one', 'one2', 'two', 'three', 'four')

Obviamente, essas não são mais listas, mas são facilmente remediadas, se isso importa:

>>> list1, list2 = (list(t) for t in zip(*sorted(zip(list1, list2))))
>>> list1
[1, 1, 2, 3, 4]
>>> list2
['one', 'one2', 'two', 'three', 'four']

Vale a pena notar que o exposto acima pode sacrificar a velocidade pela concisão; a versão local, que ocupa três linhas, é um pouco mais rápida na minha máquina para pequenas listas:

>>> %timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 3.3 us per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best of 3: 2.84 us per loop

Por outro lado, para listas maiores, a versão em uma linha pode ser mais rápida:

>>> %timeit zip(*sorted(zip(list1, list2)))
100 loops, best of 3: 8.09 ms per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100 loops, best of 3: 8.51 ms per loop

Como aponta o Quantum7, a sugestão do JSF ainda é um pouco mais rápida, mas provavelmente só será um pouco mais rápida, porque o Python usa o mesmo idioma DSU internamente para todos os tipos de chaves. Está acontecendo um pouco mais perto do metal nu. (Isso mostra o quão otimizadas zipsão as rotinas!)

Eu acho que a zipabordagem baseada é mais flexível e um pouco mais legível, então eu prefiro.

remetente
fonte
6
o que o asterisco na terceira linha representa?
21412 Jeffrey
8
Para elaborar o acima, o *operador faz argumento desembalar ,
senderle
1
O paradigma de índice / mapa ordenado sugerido por JF Sebastian é cerca de 10% mais rápido que qualquer solução zip para mim (usando listas de 10000 entradas aleatórias):% timeit index = range (len (l1)); index.sort (chave = l1 .__ getitem__); mapa (l1 .__ getitem__, índice); mapear (L2 .__ getitem__, índice) 100 loops, melhor de 3: 8,04 ms por laço (vs 9,17 ms, 9,07 ms para timits de senderle)
Quantum7
1
O primeiro e o segundo zip na lista1, lista2 = zip (* classificado (zip (lista1, lista2))) fazem coisas tão diferentes. O * faz toda a diferença.
Ashu
1
@ashu, em certo sentido, sim! Mas, em outro sentido, eles são dificilmente diferentes. zip(*x)tem a propriedade interessante de que é o seu próprio inverso: l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == lretorna True. É efetivamente um operador de transposição. zip()por si só é o mesmo operador, mas assume que você desempacotou a sequência de entrada manualmente.
Senderle
30

Você pode classificar índices usando valores como chaves:

indexes = range(len(list1))
indexes.sort(key=list1.__getitem__)

Para obter listas classificadas com índices classificados:

sorted_list1 = map(list1.__getitem__, indexes)
sorted_list2 = map(list2.__getitem__, indexes)

No seu caso, você não deveria ter list1, list2mas uma única lista de pares:

data = [(3, 'three'), (2, 'two'), (4, 'four'), (1, 'one'), (1, 'one2')]

É fácil de criar; é fácil classificar em Python:

data.sort() # sort using a pair as a key

Classifique apenas pelo primeiro valor:

data.sort(key=lambda pair: pair[0])
jfs
fonte
O legal disso é que eu posso manter os índices por perto e classificar outras coisas mais tarde, caso a lista1 seja uma coordenada importante que afeta várias outras matrizes.
EL_DON 5/0318
3
índices = lista (intervalo (len (list1))) para python 3
DonQuiKong 2/18/18
@DonQuiKong você também precisa list() em torno de map()se você gostaria de usar esse código em Python 3.
jfs
Ou, em vez de sorted_list1 = list(map(list1.__getitem__, indexes))um poderia fazer sorted_list1 = [list1[i] for i in indexes].
Nathan
20

Eu tenho usado a resposta dada por remetente por um longo tempo até que eu descobri np.argsort. Aqui está como isso funciona.

# idx works on np.array and not lists.
list1 = np.array([3,2,4,1])
list2 = np.array(["three","two","four","one"])
idx   = np.argsort(list1)

list1 = np.array(list1)[idx]
list2 = np.array(list2)[idx]

Acho essa solução mais intuitiva e funciona muito bem. O desempenho:

def sorting(l1, l2):
    # l1 and l2 has to be numpy arrays
    idx = np.argsort(l1)
    return l1[idx], l2[idx]

# list1 and list2 are np.arrays here...
%timeit sorting(list1, list2)
100000 loops, best of 3: 3.53 us per loop

# This works best when the lists are NOT np.array
%timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 2.41 us per loop

# 0.01us better for np.array (I think this is negligible)
%timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best for 3 loops: 1.96 us per loop

Embora np.argsortnão seja o mais rápido, acho mais fácil de usar.

Daniel Thaagaard Andreasen
fonte
1
Eu recebo um erro ao executar o seu exemplo: TypeError: only integer arrays with one element can be converted to an index(Python 2.7.6, numpy 1.8.2). Para corrigi-lo, list1 e list2 devem ser declaradas como matrizes numpy.
217 BenB
Obrigado. Não é isso que escrevo no comentário na função? Enfim, acho bobo que np.argsortnão tente converter para um np.arrayinternamente.
Daniel Thaagaard Andreasen
Eu estava me referindo ao primeiro trecho de código, uma vez que não é executado como escrito :)
BenB
Eu o corrigi convertendo as listas quando elas são atribuídas a matrizes numpy. Obrigado pelo comentário :)
Daniel Thaagaard Andreasen
Agora eles estão convertidos para matrizes Numpy duas vezes;)
BenB
13

Transformação Schwartziana . A classificação interna do Python é estável, portanto os dois 1s não causam problemas.

>>> l1 = [3, 2, 4, 1, 1]
>>> l2 = ['three', 'two', 'four', 'one', 'second one']
>>> zip(*sorted(zip(l1, l2)))
[(1, 1, 2, 3, 4), ('one', 'second one', 'two', 'three', 'four')]
Karl Knechtel
fonte
2
No entanto, se você achar que precisa fazer isso, considere fortemente ter as duas listas "paralelas" de dados, em vez de manter uma lista de duas tuplas (pares) ... ou talvez até mesmo criar uma classe .
19712 Karl Knechtel
3

A respeito:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

sortedRes = sorted(zip(list1, list2), key=lambda x: x[0]) # use 0 or 1 depending on what you want to sort
>>> [(1, 'one'), (1, 'one2'), (2, 'two'), (3, 'three'), (4, 'four')]
Artsiom Rudzenka
fonte
2

Você pode usar as funções zip()e sort()para fazer isso:

Python 2.6.5 (r265:79063, Jun 12 2010, 17:07:01)
[GCC 4.3.4 20090804 (release) 1] on cygwin
>>> list1 = [3,2,4,1,1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> zipped = zip(list1, list2)
>>> zipped.sort()
>>> slist1 = [i for (i, s) in zipped]
>>> slist1
[1, 1, 2, 3, 4]
>>> slist2 = [s for (i, s) in zipped]
>>> slist2
['one', 'one2', 'two', 'three', 'four']

Espero que isto ajude

Hunter McMillen
fonte
2

Você pode usar o argumento chave no método classificado (), a menos que tenha dois mesmos valores na lista2.

O código é fornecido abaixo:

sorted(list2, key = lambda x: list1[list2.index(x)]) 

Classifica list2 de acordo com os valores correspondentes na lista1, mas certifique-se de que, ao usá-lo, dois valores na lista2 não sejam avaliados porque a função list.index () fornece o primeiro valor

Saurav Yadav
fonte
classificado é um pouco lento em algumas condições, embora funcione.
tyan
2

Uma maneira é rastrear para onde cada índice vai, classificando a identidade [0,1,2, .. n]

Isso funciona para qualquer número de listas.

Em seguida, mova cada item para sua posição. Usar emendas é o melhor.

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

index = list(range(len(list1)))
print(index)
'[0, 1, 2, 3, 4]'

index.sort(key = list1.__getitem__)
print(index)
'[3, 4, 1, 0, 2]'

list1[:] = [list1[i] for i in index]
list2[:] = [list2[i] for i in index]

print(list1)
print(list2)
'[1, 1, 2, 3, 4]'
"['one', 'one2', 'two', 'three', 'four']"

Observe que poderíamos ter iterado as listas sem sequer classificá-las:

list1_iter = (list1[i] for i in index)
Robert King
fonte
1

Se você estiver usando numpy, poderá usar np.argsortpara obter os índices classificados e aplicá-los à lista. Isso funciona para qualquer número de lista que você deseja classificar.

import numpy as np

arr1 = np.array([4,3,1,32,21])
arr2 = arr1 * 10
sorted_idxs = np.argsort(arr1)

print(sorted_idxs)
>>> array([2, 1, 0, 4, 3])

print(arr1[sorted_idxs])
>>> array([ 1,  3,  4, 21, 32])

print(arr2[sorted_idxs])
>>> array([ 10,  30,  40, 210, 320])
Kurtis Streutker
fonte
0

uma solução algorítmica:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']


lis = [(list1[i], list2[i]) for i in range(len(list1))]
list1.sort()
list2 = [x[1] for i in range(len(list1)) for x in lis if x[0] == i]

Saídas: -> Velocidade de saída: 0.2s

>>>list1
>>>[1, 1, 2, 3, 4]
>>>list2
>>>['one', 'one2', 'two', 'three', 'four']
Jundullah
fonte
0

Outra abordagem para manter a ordem de uma lista de cadeias ao classificar em outra lista é a seguinte:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

# sort on list1 while retaining order of string list
sorted_list1 = [y for _,y in sorted(zip(list1,list2),key=lambda x: x[0])]
sorted_list2 = sorted(list1)

print(sorted_list1)
print(sorted_list2)

resultado

['one', 'one2', 'two', 'three', 'four']
[1, 1, 2, 3, 4]
brock
fonte
0

Gostaria de expandir a resposta do open jfs , que funcionou muito bem para o meu problema: classificar duas listas por uma terceira lista decorada :

Podemos criar nossa lista decorada de qualquer maneira, mas neste caso a criaremos a partir dos elementos de uma das duas listas originais que queremos classificar:

# say we have the following list and we want to sort both by the algorithms name 
# (if we were to sort by the string_list, it would sort by the numerical 
# value in the strings)
string_list = ["0.123 Algo. XYZ", "0.345 Algo. BCD", "0.987 Algo. ABC"]
dict_list = [{"dict_xyz": "XYZ"}, {"dict_bcd": "BCD"}, {"dict_abc": "ABC"}]

# thus we need to create the decorator list, which we can now use to sort
decorated = [text[6:] for text in string_list]  
# decorated list to sort
>>> decorated
['Algo. XYZ', 'Algo. BCD', 'Algo. ABC']

Agora podemos aplicar a solução do jfs para classificar nossas duas listas pela terceira

# create and sort the list of indices
sorted_indices = list(range(len(string_list)))
sorted_indices.sort(key=decorated.__getitem__)

# map sorted indices to the two, original lists
sorted_stringList = list(map(string_list.__getitem__, sorted_indices))
sorted_dictList = list(map(dict_list.__getitem__, sorted_indices))

# output
>>> sorted_stringList
['0.987 Algo. ABC', '0.345 Algo. BCD', '0.123 Algo. XYZ']
>>> sorted_dictList
[{'dict_abc': 'ABC'}, {'dict_bcd': 'BCD'}, {'dict_xyz': 'XYZ'}]

Edit: Ei pessoal, eu fiz um post sobre isso, confira se você quiser :) 🐍🐍🐍

frietz58
fonte
-1
newsource=[];newtarget=[]
for valueT in targetFiles:
    for valueS in sourceFiles:
            l1=len(valueS);l2=len(valueT);
            j=0
            while (j< l1):
                    if (str(valueT) == valueS[j:l1]) :
                            newsource.append(valueS)
                            newtarget.append(valueT)
                    j+=1
user10340258
fonte
2
um par de linhas de explicação seria útil
saiedmomen
@ saiedmomen Eu o publiquei em referência a stackoverflow.com/questions/53829160/… Aqui a string de destino é pesquisada sobre a string de origem.
user10340258