Объединение двух отсортированных списков в Python


У меня есть два списка объектов. Каждый список уже отсортирован по свойству объекта типа datetime. Я хочу объединить два списка в один упорядоченный список. Это лучший способ просто сделать вид или есть более умный способ сделать это в Python?

20 62

20 ответов:

люди, кажется, слишком усложняют это.. Просто объедините два списка, а затем отсортируйте их:

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

..или короче (и без изменения l1):

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

..полегче! Кроме того, он использует только две встроенные функции, поэтому, предполагая, что списки имеют разумный размер, это должно быть быстрее, чем реализация сортировки/слияния в цикле. Что еще более важно, вышеописанный код намного меньше и очень читаем.

если ваши списки большие (более нескольких сто тысяч, я бы предположил), Может быть быстрее использовать альтернативный / пользовательский метод сортировки, но, вероятно, сначала будут сделаны другие оптимизации (например, не хранить миллионы datetime объекты)

С помощью timeit.Timer().repeat() (который повторяет функции 1000000 раз), я свободно сравнивал его с ghoseb это!--20--> решение, и sorted(l1+l2) существенно быстрее:

..

[9.7439379692077637, 9.8844599723815918, 9.552299976348877]

sorted(l1+l2) взял..

[2.860386848449707, 2.7589840888977051, 2.7682540416717529]

есть ли более умный способ сделать это в Python

это не было упомянуто, так что я пойду вперед-есть функция слияния stdlib в модуле heapq python 2.6+. Если все, что вы хотите сделать, это сделать вещи, это может быть лучшей идеей. Конечно, если вы хотите реализовать свой собственный, слияние merge-sort-это путь.

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

здесь документация.

короче говоря, если len(l1 + l2) ~ 1000000 использование:

L = l1 + l2
L.sort()

merge vs. sort comparison

описание рисунка и исходного кода можно найти здесь.

рисунок был сгенерирован следующей командой:

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

Это просто слияние. Обрабатывайте каждый список, как если бы он был стеком, и непрерывно открывайте меньшую из двух головок стека, добавляя элемент в список результатов, пока один из стеков не станет пустым. Затем добавьте все остальные элементы в результирующий список.

есть небольшой недостаток в ghoseb это!--10--> решение, что делает его O(n**2), а не O (n).
Проблема в том, что это выполняет:

item = l1.pop(0)

со связанными списками или deques это будет операция O(1), поэтому не повлияет на сложность, но поскольку списки python реализованы как векторы, это копирует остальные элементы l1 на один пробел, операцию O(n). Поскольку это делается каждый проход через список, он превращает алгоритм O(n) в o (n**2) один. Это можно исправить с помощью метода, который не изменяет исходные списки, а просто отслеживает текущую позицию.

Я опробовал бенчмаркинг исправленного алгоритма против простой сортировки(l1+l2), как предложено 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

Я проверил их со списками, созданными с помощью

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

для различных размеров списка, я получаю следующие тайминги (повторять 100 раз):

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

так что на самом деле, это выглядит как dbr правильно, просто использование sorted () предпочтительнее, если вы не ожидаете очень больших списков, хотя у него есть худшая алгоритмическая сложность. Точка безубыточности составляет около миллиона элементов в каждом исходном списке (всего 2 миллиона).

одно из преимуществ подхода слияния заключается в том, что тривиально переписывать как генератор, который будет использовать существенно меньше памяти (нет необходимости в промежуточном списке).

[Edit] Я повторил это с ситуацией ближе к вопросу-с помощью списка объектов, содержащих поле"date " который является объектом datetime. Приведенный выше алгоритм был изменен для сравнения с .date вместо этого, и метод сортировки был изменен на:

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

это действительно немного меняет дело. Более дорогое сравнение означает, что число, которое мы выполняем, становится более важным по отношению к постоянной скорости реализации. Это означает, что слияние составляет потерянную землю, превосходя метод sort () в Вместо этого 100 000 предметов. Сравнение на основе еще более сложного объекта (например, больших строк или списков), вероятно, еще больше сдвинет этот баланс.

# 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]: Примечание: я на самом деле сделал только 10 повторов для 1 000 000 элементов и масштабируется соответственно, как это было довольно медленно.

Это простое слияние двух отсортированных списков. Взгляните на пример кода ниже, который объединяет два отсортированных списка целых чисел.

#!/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 __name__ == '__main__':
    print merge_sorted_lists(l1, l2)

Это должно отлично работать с объектами datetime. Надеюсь, это поможет.

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

вывод:

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

Я уверен, что это быстрее, чем любой из причудливых алгоритмов слияния pure-Python, даже для больших данных. В Python 2.6 это heapq.merge это совсем другая история.

реализация сортировки Python "timsort" специально оптимизирована для списков, содержащих упорядоченные разделы. Кроме того, это написано в C.

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

Как уже упоминалось, он может вызывать функцию сравнения больше раз с помощью некоторого постоянного фактора (но, возможно, во многих случаях назовите его больше раз за более короткий период!).

Я бы однако не полагайтесь на это. - Даниэль Надаси

Я считаю, что разработчики Python стремятся сохранить timsort или, по крайней мере, сохранить вид, который в этом случае является O(n).

обобщенная сортировка (т. е. оставляя отдельно сортировку по корню из доменов с ограниченным значением)
не может быть сделано менее чем за O (N log n) на последовательной машине. - Барри Келли

право, сортировка в общем случае не может быть быстрее. Но так как O () является верхняя граница, timsort сложность o(n журнал N) на произвольный входной сигнал не противоречит ее о(н) с учетом отсортированный(Л1) + сортировка(Л2).

рекурсивная реализация ниже. Средняя производительность составляет 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

или генератор с повышенной сложностью пространства:

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 списка в большой и сортировать) будет O(N*log (N)) сложность. С другой стороны, если вы реализуете слияние вручную (я не знаю ни о каком готовом коде в Python libs для этого, но я не эксперт) сложность будет O(N), что явно быстрее. Идея хорошо описана в посте Барри Келли.

используйте шаг "слияние" сортировки слияния, он выполняется в O(n) времени.

С Википедия (псевдо-код):

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

Если вы хотите сделать это более совместимым с изучением того, что происходит в итерации, попробуйте это

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)

использовали шаг слияния сортировки слияния. Но я использовал генераторы. сложность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)
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

реализация шага слияния в Сортировке слиянием, которая повторяется в обоих списках:

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]

Я все еще изучаю алгоритмы, пожалуйста, дайте мне знать, если код может быть улучшен в любом аспекте, ваша обратная связь ценится, спасибо!

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)

отсортирует список на месте. Определите свою собственную функцию для сравнения двух объектов и передайте эту функцию во встроенную функцию сортировки.

Не используйте сортировку пузырьков, она имеет ужасную производительность.

Это мое решение в линейного времени без редактирования l1 и 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:]

этот код имеет временную сложность O (n) и может объединять списки любого типа данных, учитывая количественную функцию в качестве параметра func. Он создает новый объединенный список и не изменяет ни один из списков, переданных в качестве аргументов.

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

надеюсь, что это помогает. Довольно просто и прямо вперед:

l1 = [1, 3, 4, 7]

l2 = [0, 2, 5, 6, 8, 9]

l3 = l1 + l2

l3.сортировка ()

print (l3)

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]