J'ai deux listes d'objets. Chaque liste est déjà triée par une propriété de l'objet du type date-heure. Je voudrais combiner les deux listes en une liste triée. Est-ce que la meilleure façon de faire une sorte ou y at-il une façon plus intelligente de le faire en Python?
Les gens semblent trop compliquer les choses. Combinez simplement les deux listes, puis triez-les:
>>> l1 = [1, 3, 4, 7]
>>> l2 = [0, 2, 5, 6, 8, 9]
>>> l1.extend(l2)
>>> sorted(l1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
..ou plus court (et sans modifier l1
):
>>> sorted(l1 + l2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
..facile! De plus, il utilise uniquement deux fonctions intégrées. Par conséquent, si les listes ont une taille raisonnable, il devrait être plus rapide que de mettre en œuvre le tri/la fusion en boucle. Plus important encore, ce qui précède est beaucoup moins codé et très lisible.
Si vos listes sont volumineuses (plus de quelques centaines de milliers, je suppose), il serait peut-être plus rapide d'utiliser une méthode de tri alternative/personnalisée, mais d'autres optimisations sont probablement à effectuer en premier (par exemple, ne pas stocker des millions d'objets datetime
).
À l'aide de timeit.Timer().repeat()
(qui répète les fonctions 1 000 fois), je l'ai comparé de manière lâche à la solution ghoseb et sorted(l1+l2)
est nettement plus rapide:
merge_sorted_lists
a pris ..
[9.7439379692077637, 9.8844599723815918, 9.552299976348877]
sorted(l1+l2)
a pris ..
[2.860386848449707, 2.7589840888977051, 2.7682540416717529]
existe-t-il un moyen plus intelligent de le faire en Python?
Cela n’a pas été mentionné, je vais donc aller de l’avant - il existe une fonction merge stdlib dans le module heapq de python 2.6+. Si tout ce que vous cherchez à faire est de faire avancer les choses, cela pourrait être une meilleure idée. Bien sûr, si vous voulez implémenter le vôtre, la fusion de tri par fusion est la voie à suivre.
>>> list1 = [1, 5, 8, 10, 50]
>>> list2 = [3, 4, 29, 41, 45, 49]
>>> from heapq import merge
>>> list(merge(list1, list2))
[1, 3, 4, 5, 8, 10, 29, 41, 45, 49, 50]
Voici la documentation .
Histoire courte, sauf si len(l1 + l2) ~ 1000000
utilise:
L = l1 + l2
L.sort()
La description de la figure et le code source peuvent être trouvés ici .
La figure a été générée par la commande suivante:
$ python make-figures.py --nsublists 2 --maxn=0x100000 -s merge_funcs.merge_26 -s merge_funcs.sort_builtin
C'est simplement la fusion. Traitez chaque liste comme s’il s’agissait d’une pile et ouvrez continuellement la plus petite des deux têtes de pile en ajoutant l’élément à la liste des résultats, jusqu’à ce que l’une des piles soit vide. Ajoutez ensuite tous les éléments restants à la liste résultante.
Il y a un léger défaut dans ghoseb's solution, ce qui le rend O (n ** 2), plutôt que O (n).
Le problème est que cela fonctionne:
item = l1.pop(0)
Avec des listes liées ou deques, ce serait une opération O(1), donc cela n’affecterait pas la complexité, mais puisque les listes python sont implémentées en tant que vecteurs, ceci copie le reste des éléments de l1, un espace restant, une O(n) opération. Comme cela est fait à chaque passage dans la liste, il transforme un algorithme O(n) en un algorithme O (n ** 2). Cela peut être corrigé en utilisant une méthode qui ne modifie pas les listes de sources, mais garde simplement trace de la position actuelle.
J'ai essayé de comparer un algorithme corrigé à un simple trié (l1 + l2) comme suggéré par dbr
def merge(l1,l2):
if not l1: return list(l2)
if not l2: return list(l1)
# l2 will contain last element.
if l1[-1] > l2[-1]:
l1,l2 = l2,l1
it = iter(l2)
y = it.next()
result = []
for x in l1:
while y < x:
result.append(y)
y = it.next()
result.append(x)
result.append(y)
result.extend(it)
return result
J'ai testé ces listes avec des listes générées avec
l1 = sorted([random.random() for i in range(NITEMS)])
l2 = sorted([random.random() for i in range(NITEMS)])
Pour différentes tailles de liste, j'obtiens les timings suivants (répétés 100 fois):
# items: 1000 10000 100000 1000000
merge : 0.079 0.798 9.763 109.044
sort : 0.020 0.217 5.948 106.882
Donc, en fait, il semble que dbr soit correct, il est préférable d’utiliser Trier () à moins que vous ne vous attendiez à des listes très volumineuses, bien que la complexité algorithmique soit pire. Le seuil de rentabilité est d'environ un million d'articles dans chaque liste de sources (2 millions au total).
L’un des avantages de l’approche de fusion réside dans le fait qu’il est trivial de réécrire en tant que générateur, ce qui nécessitera beaucoup moins de mémoire (pas besoin de liste intermédiaire).
[Edit] J'ai réessayé ceci avec une situation plus proche de la question - en utilisant une liste d'objets contenant un champ "date
" qui est un objet datetime . L'algorithme ci-dessus a été modifié pour comparer contre .date
à la place, et la méthode de tri a été modifiée en:
return sorted(l1 + l2, key=operator.attrgetter('date'))
Cela change un peu les choses. La comparaison étant plus chère signifie que le nombre que nous effectuons devient plus important, par rapport à la vitesse constante de la mise en œuvre. Cela signifie que la fusion constitue un terrain perdu, dépassant la méthode sort () à 100 000 éléments. Une comparaison basée sur un objet encore plus complexe (grandes chaînes ou listes, par exemple) modifierait probablement davantage cet équilibre.
# items: 1000 10000 100000 1000000[1]
merge : 0.161 2.034 23.370 253.68
sort : 0.111 1.523 25.223 313.20
[1]: Note: En fait, je n'ai fait que 10 répétitions pour 1 000 000 éléments et j'ai été agrandi en conséquence, car c'était assez lent.
C'est une simple fusion de deux listes triées. Examinez l'exemple de code ci-dessous qui fusionne deux listes d'entiers triées.
#!/usr/bin/env python
## merge.py -- Merge two sorted lists -*- Python -*-
## Time-stamp: "2009-01-21 14:02:57 ghoseb"
l1 = [1, 3, 4, 7]
l2 = [0, 2, 5, 6, 8, 9]
def merge_sorted_lists(l1, l2):
"""Merge sort two sorted lists
Arguments:
- `l1`: First sorted list
- `l2`: Second sorted list
"""
sorted_list = []
# Copy both the args to make sure the original lists are not
# modified
l1 = l1[:]
l2 = l2[:]
while (l1 and l2):
if (l1[0] <= l2[0]): # Compare both heads
item = l1.pop(0) # Pop from the head
sorted_list.append(item)
else:
item = l2.pop(0)
sorted_list.append(item)
# Add the remaining of the lists
sorted_list.extend(l1 if l1 else l2)
return sorted_list
if __== '__main__':
print merge_sorted_lists(l1, l2)
Cela devrait fonctionner correctement avec les objets datetime. J'espère que cela t'aides.
from datetime import datetime
from itertools import chain
from operator import attrgetter
class DT:
def __init__(self, dt):
self.dt = dt
list1 = [DT(datetime(2008, 12, 5, 2)),
DT(datetime(2009, 1, 1, 13)),
DT(datetime(2009, 1, 3, 5))]
list2 = [DT(datetime(2008, 12, 31, 23)),
DT(datetime(2009, 1, 2, 12)),
DT(datetime(2009, 1, 4, 15))]
list3 = sorted(chain(list1, list2), key=attrgetter('dt'))
for item in list3:
print item.dt
Le résultat:
2008-12-05 02:00:00
2008-12-31 23:00:00
2009-01-01 13:00:00
2009-01-02 12:00:00
2009-01-03 05:00:00
2009-01-04 15:00:00
Je parie que cela est plus rapide que n'importe quel algorithme de fusion en pur Python, même pour les grandes données Le heapq.merge
de Python 2.6 est une toute autre histoire.
L'implémentation de tri "timsort" de Python est spécialement optimisée pour les listes contenant des sections ordonnées. De plus, c'est écrit en C.
http://bugs.python.org/file4451/timsort.txt
http://fr.wikipedia.org/wiki/Timsort
Comme les gens l'ont mentionné, la fonction de comparaison peut être appelée plusieurs fois de façon constante (mais peut-être plus de fois dans une période plus courte dans de nombreux cas!).
Je ne compterais jamais sur cela, cependant. - Daniel Nadasi
Je crois que les développeurs Python sont déterminés à conserver timsort, ou du moins à conserver une sorte qui est O(n) dans ce cas.
Tri généralisé (c'est-à-dire en séparant les types de base des domaines de valeurs limitées)
ne peut pas être effectué en moins de O (n log n) sur un ordinateur série. - Barry Kelly
Oui, le tri dans le cas général ne peut pas être plus rapide que cela. Mais puisque O() est une limite supérieure, timsort étant O (n log n) sur une entrée arbitraire ne contredit pas son existence O(n) donné trié (L1) + (L2).
Une implémentation de l'étape de fusion dans le tri par fusion qui parcourt les deux listes:
def merge_lists(L1, L2):
"""
L1, L2: sorted lists of numbers, one of them could be empty.
returns a merged and sorted list of L1 and L2.
"""
# When one of them is an empty list, returns the other list
if not L1:
return L2
Elif not L2:
return L1
result = []
i = 0
j = 0
for k in range(len(L1) + len(L2)):
if L1[i] <= L2[j]:
result.append(L1[i])
if i < len(L1) - 1:
i += 1
else:
result += L2[j:] # When the last element in L1 is reached,
break # append the rest of L2 to result.
else:
result.append(L2[j])
if j < len(L2) - 1:
j += 1
else:
result += L1[i:] # When the last element in L2 is reached,
break # append the rest of L1 to result.
return result
L1 = [1, 3, 5]
L2 = [2, 4, 6, 8]
merge_lists(L1, L2) # Should return [1, 2, 3, 4, 5, 6, 8]
merge_lists([], L1) # Should return [1, 3, 5]
J'apprends toujours sur les algorithmes, s'il vous plaît, laissez-moi savoir si le code pourrait être amélioré dans n'importe quel aspect, votre retour est apprécié, merci!
def merge_sort(a,b):
pa = 0
pb = 0
result = []
while pa < len(a) and pb < len(b):
if a[pa] <= b[pb]:
result.append(a[pa])
pa += 1
else:
result.append(b[pb])
pb += 1
remained = a[pa:] + b[pb:]
result.extend(remained)
return result
L'implémentation récursive est ci-dessous. La performance moyenne est O (n).
def merge_sorted_lists(A, B, sorted_list = None):
if sorted_list == None:
sorted_list = []
slice_index = 0
for element in A:
if element <= B[0]:
sorted_list.append(element)
slice_index += 1
else:
return merge_sorted_lists(B, A[slice_index:], sorted_list)
return sorted_list + B
ou générateur avec une complexité d'espace améliorée:
def merge_sorted_lists_as_generator(A, B):
slice_index = 0
for element in A:
if element <= B[0]:
slice_index += 1
yield element
else:
for sorted_element in merge_sorted_lists_as_generator(B, A[slice_index:]):
yield sorted_element
return
for element in B:
yield element
Si vous voulez le faire d'une manière plus cohérente avec l'apprentissage de l'itération, essayez ceci.
def merge_arrays(a, b):
l= []
while len(a) > 0 and len(b)>0:
if a[0] < b[0]: l.append(a.pop(0))
else:l.append(b.pop(0))
l.extend(a+b)
print( l )
import random
n=int(input("Enter size of table 1")); #size of list 1
m=int(input("Enter size of table 2")); # size of list 2
tb1=[random.randrange(1,101,1) for _ in range(n)] # filling the list with random
tb2=[random.randrange(1,101,1) for _ in range(m)] # numbers between 1 and 100
tb1.sort(); #sort the list 1
tb2.sort(); # sort the list 2
fus=[]; # creat an empty list
print(tb1); # print the list 1
print('------------------------------------');
print(tb2); # print the list 2
print('------------------------------------');
i=0;j=0; # varialbles to cross the list
while(i<n and j<m):
if(tb1[i]<tb2[j]):
fus.append(tb1[i]);
i+=1;
else:
fus.append(tb2[j]);
j+=1;
if(i<n):
fus+=tb1[i:n];
if(j<m):
fus+=tb2[j:m];
print(fus);
# this code is used to merge two sorted lists in one sorted list (FUS) without
#sorting the (FUS)
Avoir utilisé l'étape de fusion du tri par fusion. Mais j'ai utilisé générateurs . Complexité temporelle O(n)
def merge(lst1,lst2):
len1=len(lst1)
len2=len(lst2)
i,j=0,0
while(i<len1 and j<len2):
if(lst1[i]<lst2[j]):
yield lst1[i]
i+=1
else:
yield lst2[j]
j+=1
if(i==len1):
while(j<len2):
yield lst2[j]
j+=1
Elif(j==len2):
while(i<len1):
yield lst1[i]
i+=1
l1=[1,3,5,7]
l2=[2,4,6,8,9]
mergelst=(val for val in merge(l1,l2))
print(*mergelst)
Eh bien, l’approche naïve (combiner 2 listes en une grande et trier) sera une complexité de O (N * log (N)). D'un autre côté, si vous implémentez la fusion manuellement (je ne connais aucun code prêt dans les bibliothèques python pour cela, mais je ne suis pas un expert), la complexité sera O (N), ce qui est clairement plus rapide . L'idée est très bien décrite dans le message de Barry Kelly.
Utilisez l'étape 'fusion' du tri par fusion, elle s'exécute dans O(n) heure.
De wikipedia (pseudo-code):
function merge(left,right)
var list result
while length(left) > 0 and length(right) > 0
if first(left) ≤ first(right)
append first(left) to result
left = rest(left)
else
append first(right) to result
right = rest(right)
end while
while length(left) > 0
append left to result
while length(right) > 0
append right to result
return result
C'est ma solution en temps linéaire sans édition l1 et l2:
def merge(l1, l2):
m, m2 = len(l1), len(l2)
newList = []
l, r = 0, 0
while l < m and r < m2:
if l1[l] < l2[r]:
newList.append(l1[l])
l += 1
else:
newList.append(l2[r])
r += 1
return newList + l1[l:] + l2[r:]
Ce code a une complexité temporelle O(n) et peut fusionner des listes de tout type de données, avec une fonction de quantification comme paramètre func
. Il produit une nouvelle liste fusionnée et ne modifie aucune des listes transmises en tant qu'arguments.
def merge_sorted_lists(listA,listB,func):
merged = list()
iA = 0
iB = 0
while True:
hasA = iA < len(listA)
hasB = iB < len(listB)
if not hasA and not hasB:
break
valA = None if not hasA else listA[iA]
valB = None if not hasB else listB[iB]
a = None if not hasA else func(valA)
b = None if not hasB else func(valB)
if (not hasB or a<b) and hasA:
merged.append(valA)
iA += 1
Elif hasB:
merged.append(valB)
iB += 1
return merged
Mon point de vue sur ce problème:
a = [2, 5, 7]
b = [1, 3, 6]
[i for p in Zip(a,b) for i in (p if p[0] <= p[1] else (p[1],p[0]))]
# Output: [1, 2, 3, 5, 6, 7]
def compareDate(obj1, obj2):
if obj1.getDate() < obj2.getDate():
return -1
Elif obj1.getDate() > obj2.getDate():
return 1
else:
return 0
list = list1 + list2
list.sort(compareDate)
Va trier la liste en place. Définissez votre propre fonction pour comparer deux objets et transmettez-la à la fonction de tri intégrée.
N'utilisez PAS le tri à bulles, il a une performance horrible.