it-swarm.com.de

Kombination zweier sortierter Listen in Python

Ich habe zwei Objektlisten. Jede Liste ist bereits nach einer Eigenschaft des Objekts mit dem Datetime-Typ sortiert. Ich möchte die beiden Listen zu einer sortierten Liste zusammenfassen. Ist der beste Weg nur eine Sortierung oder gibt es einen intelligenteren Weg, dies in Python zu tun?

64
Bjorn Tipling

Die Leute scheinen zu kompliziert zu sein. Kombinieren Sie einfach die beiden Listen und sortieren Sie sie:

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

..oder kürzer (und ohne l1 zu ändern):

>>> sorted(l1 + l2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

..einfach! Außerdem werden nur zwei integrierte Funktionen verwendet. Wenn die Listen von angemessener Größe sind, sollte dies jedoch schneller sein, als das Sortieren/Zusammenführen in einer Schleife. Noch wichtiger ist, dass das Obige viel weniger Code ist und sehr gut lesbar ist.

Wenn Ihre Listen groß sind (mehr als ein paar Hunderttausend, würde ich vermuten), ist es möglicherweise schneller, eine alternative/benutzerdefinierte Sortiermethode zu verwenden, aber es sind wahrscheinlich andere Optimierungen erforderlich, die zuerst vorgenommen werden müssen (z. B. nicht das Speichern von Millionen von datetime-Objekten)

Mit der timeit.Timer().repeat() (die die Funktionen 1000000-mal wiederholt), habe ich sie locker gegen ghoseb's solution verglichen und sorted(l1+l2) ist wesentlich schneller:

merge_sorted_lists nahm ..

[9.7439379692077637, 9.8844599723815918, 9.552299976348877]

sorted(l1+l2) nahm ..

[2.860386848449707, 2.7589840888977051, 2.7682540416717529]
103
dbr

gibt es einen intelligenteren Weg, dies in Python zu tun

Dies wurde nicht erwähnt, also gehe ich weiter - es gibt eine merge stdlib-Funktion im Heapq-Modul von Python 2.6+. Wenn Sie nur Dinge erledigen möchten, ist dies möglicherweise eine bessere Idee. Wenn Sie Ihre eigene implementieren möchten, ist das Zusammenführen von Merge-Sort natürlich der richtige Weg.

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

Hier ist die Dokumentation .

93
sykora

Lange Geschichte kurz, es sei denn len(l1 + l2) ~ 1000000 use:

L = l1 + l2
L.sort()

merge vs. sort comparison

Die Beschreibung der Abbildung und des Quellcodes finden Sie hier

Die Abbildung wurde mit dem folgenden Befehl generiert:

$ python make-figures.py --nsublists 2 --maxn=0x100000 -s merge_funcs.merge_26 -s merge_funcs.sort_builtin
49
jfs

Das verschmilzt einfach. Behandeln Sie jede Liste wie einen Stapel, und drücken Sie den kleineren der beiden Stapelköpfe kontinuierlich aus, um das Element der Ergebnisliste hinzuzufügen, bis einer der Stapel leer ist. Fügen Sie dann alle verbleibenden Elemente zur Ergebnisliste hinzu.

25
Barry Kelly

Es gibt einen kleinen Fehler in ghosebs Lösung, der es zu O (n ** 2) anstatt zu O (n) macht.
Das Problem ist, dass dies ausgeführt wird:

item = l1.pop(0)

Bei verknüpften Listen oder Deques wäre dies eine O(1) -Operation, die die Komplexität nicht beeinflussen würde. Da Python-Listen jedoch als Vektoren implementiert werden, werden die restlichen Elemente von l1 um ein Leerzeichen, eine O(n) Operation. Da dies bei jedem Durchlauf der Liste durchgeführt wird, wird aus einem O(n) - Algorithmus ein O (n ** 2) - Algorithmus. Dies kann korrigiert werden, indem eine Methode verwendet wird, die die Quellenlisten nicht ändert, sondern nur die aktuelle Position verfolgt.

Ich habe das Benchmarking eines korrigierten Algorithmus gegenüber einem einfachen sortierten (l1 + l2) ausprobiert, wie von dbr vorgeschlagen.

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

Ich habe diese mit Listen getestet, die mit generiert wurden

l1 = sorted([random.random() for i in range(NITEMS)])
l2 = sorted([random.random() for i in range(NITEMS)])

Für verschiedene Listengrößen bekomme ich die folgenden Zeitangaben (100 mal):

# items:  1000   10000 100000 1000000
merge  :  0.079  0.798 9.763  109.044 
sort   :  0.020  0.217 5.948  106.882

Es sieht also so aus, als wäre dbr richtig. Die Verwendung von sort () ist vorzuziehen, es sei denn, Sie erwarten sehr große Listen, auch wenn die Komplexität des Algorithmus schlechter ist. Die Gewinnschwelle liegt bei rund einer Million Elementen in jeder Quellenliste (insgesamt 2 Millionen).

Ein Vorteil des Merge-Ansatzes besteht jedoch darin, dass es einfach ist, als Generator umzuschreiben, wodurch wesentlich weniger Speicherplatz benötigt wird (keine Zwischenliste erforderlich).

[Edit] Ich habe dies mit einer Situation wiederholt, die näher an der Frage liegt - unter Verwendung einer Liste von Objekten, die ein Feld "date" enthalten, das ein datetime-Objekt ist . Der obige Algorithmus wurde zum Vergleichen geändert stattdessen gegen .date und die Sortiermethode wurde geändert in:

return sorted(l1 + l2, key=operator.attrgetter('date'))

Das ändert die Sache ein bisschen. Da der Vergleich teurer ist, wird die Anzahl, die wir durchführen, im Verhältnis zur Geschwindigkeit der Implementierung mit konstanter Zeit wichtiger. Dies bedeutet, dass beim Zusammenführen verloren gegangene Felder wieder hergestellt werden und die sort () -Methode stattdessen bei 100.000 Elementen überschritten wird. Ein Vergleich mit einem noch komplexeren Objekt (z. B. große Zeichenfolgen oder Listen) würde dieses Gleichgewicht wahrscheinlich noch mehr verschieben.

# 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]: Hinweis: Ich habe eigentlich nur 10 Wiederholungen für 1.000.000 Artikel gemacht und entsprechend skaliert, da es ziemlich langsam war.

14
Brian

Dies ist ein einfaches Zusammenführen von zwei sortierten Listen. Sehen Sie sich den folgenden Beispielcode an, der zwei sortierte Listen von Ganzzahlen zusammenführt.

#!/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)

Dies sollte gut mit datetime-Objekten funktionieren. Hoffe das hilft.

4
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

Die Ausgabe:

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

Ich wette, das ist schneller als jeder der ausgefallenen Pure-Python-Merge-Algorithmen, selbst für große Daten. heapq.merge von Python 2.6 ist eine ganze andere Geschichte.

4
akaihola

Pythons Sortierungsimplementierung "Timsort" ist speziell für Listen optimiert, die geordnete Abschnitte enthalten. Plus, es ist in C geschrieben. 

http://bugs.python.org/file4451/timsort.txt
http://en.wikipedia.org/wiki/Timsort

Wie bereits erwähnt wurde, kann die Vergleichsfunktion mehrmals um einen konstanten Faktor aufgerufen werden (in vielen Fällen jedoch möglicherweise öfter in kürzerer Zeit!).

Ich würde mich jedoch nie darauf verlassen. - Daniel Nadasi

Ich glaube, die Python-Entwickler haben sich verpflichtet, Timsort zu halten, oder zumindest eine Sorte zu halten, die in diesem Fall O(n) ist.

Verallgemeinerte Sortierung (d. H. Das Auslassen von Radix-Sortierungen aus Domänen mit begrenzten Werten)
kann nicht in weniger als O (n log n) auf einem seriellen Computer ausgeführt werden. - Barry Kelly

Richtig, das Sortieren im allgemeinen Fall kann nicht schneller sein. Da O() jedoch eine obere Grenze ist, widerspricht Timsort O (n log n) bei beliebigen Eingaben nicht, dass O(n) sortiert (L1) + sortiert (L2) ist.

3
FutureNerd

Eine Implementierung des Zusammenführungsschritts in Merge Sort, die beide Listen durchläuft:

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]

Ich lerne immer noch über Algorithmen. Bitte lassen Sie mich wissen, ob der Code in irgendeiner Hinsicht verbessert werden könnte. Ihr Feedback wird geschätzt, danke!

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

Die rekursive Implementierung ist unten. Die durchschnittliche Leistung beträgt 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

oder Generator mit verbesserter Platzkomplexität:

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

Wenn Sie es auf eine Art und Weise tun möchten, die besser mit dem Lernen in der Iteration übereinstimmt, versuchen Sie dies

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

Merge-Schritt der Merge-Sortierung verwendet. Aber ich habe Generatoren verwendet. Zeitkomplexität 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

Nun, der naive Ansatz (kombiniere 2 Listen zu großen und sortiere) wird O (N * log (N)) Komplexität sein. Wenn Sie dagegen die Zusammenführung manuell implementieren (ich kenne dafür keinen fertigen Code in Python-Bibliotheken, aber ich bin kein Experte), wird die Komplexität O (N) sein, was deutlich schneller ist. Die Idee wird von Barry Kelly gut beschrieben.

1
Drakosha

Verwenden Sie den "Merge" -Schritt der Merge-Sortierung, es läuft in der Zeit O(n).

Aus 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

Dies ist meine Lösung in linearer Zeit ohne Bearbeitung von l1 und 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

Dieser Code hat Zeitkomplexität O(n) und kann Listen beliebiger Datentypen zusammenführen, wenn eine Quantifizierungsfunktion als Parameter func angegeben wird. Es erzeugt eine neue zusammengeführte Liste und ändert keine der als Argumente übergebenen Listen.

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

Meine Einstellung zu diesem Problem:

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)

Sortiert die Liste an Ort und Stelle. Definieren Sie Ihre eigene Funktion zum Vergleichen von zwei Objekten und übergeben Sie diese Funktion an die integrierte Sortierfunktion.

Verwenden Sie KEINE Bubble-Sorte, es hat eine schreckliche Leistung.

0
Josh Smeaton