web-dev-qa-db-fra.com

Combinaison de deux listes triées en Python

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?

64
Bjorn Tipling

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]
103
dbr

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 .

93
sykora

Histoire courte, sauf si len(l1 + l2) ~ 1000000 utilise:

L = l1 + l2
L.sort()

merge vs. sort comparison

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
49
jfs

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.

25
Barry Kelly

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.

14
Brian

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.

4
Baishampayan Ghose
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.

4
akaihola

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).

3
FutureNerd

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!

2
timokratia
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
2
krezaeim

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
2
Pavel Paulau

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 )
1
Leon
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)
1
Oussama Ďj Sbaa

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)
1
Atul

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.

1
Drakosha

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
1
Mongoose

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:]
0
Alpha

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
0
Daniel

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]
0
dbow
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.

0
Josh Smeaton