web-dev-qa-db-ja.com

Pythonで2つのソートされたリストを組み合わせる

オブジェクトのリストは2つあります。各リストは、日時型のオブジェクトのプロパティによって既に並べ替えられています。 2つのリストを1つのソート済みリストに結合したいと思います。ソートを行うだけの最良の方法ですか、それともPythonでこれを行うよりスマートな方法がありますか?

66
Bjorn Tipling

人々はこれを複雑にしすぎているようです。

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

..簡単!さらに、2つの組み込み関数のみを使用しているため、リストが適切なサイズであると仮定すると、ループでの並べ替え/マージの実装よりも高速になります。さらに重要なことに、上記のコードははるかに少なく、非常に読みやすくなっています。

リストが大きい場合(数十万を超えると思います)、代替/カスタムのソート方法を使用する方が速いかもしれませんが、他の最適化が最初に行われる可能性があります(たとえば、数百万のdatetimeオブジェクト)

timeit.Timer().repeat()(関数を1000000回繰り返す)を使用して、 ghoseb's ソリューションに対して緩やかにベンチマークを行い、sorted(l1+l2)を大幅に高速化しました。

_merge_sorted_lists_がかかりました。

_[9.7439379692077637, 9.8844599723815918, 9.552299976348877]
_

sorted(l1+l2) take ..

_[2.860386848449707, 2.7589840888977051, 2.7682540416717529]
_
109
dbr

pythonでこれを行うよりスマートな方法はありますか

これは言及されていないので、先に進みます-python 2.6+のheapqモジュールに merge stdlib function があります。もちろん、自分で実装したい場合は、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]

ドキュメント です。

99
sykora

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

これは単にマージされます。各リストをスタックのように扱い、2つのスタックヘッドのうち小さい方を連続的にポップし、スタックの1つが空になるまで、結果リストにアイテムを追加します。次に、残りのすべてのアイテムを結果リストに追加します。

25
Barry Kelly

ghoseb's ソリューションにわずかな欠陥があり、O(n)ではなくO(n ** 2)になります。
問題は、これが実行していることです:

item = l1.pop(0)

リンクされたリストまたは両端キューでは、これはO(1)操作であるため、複雑さには影響しませんが、pythonリストはベクトルとして実装されるため、 l1の残りの要素を1スペース左にコピーし、O(n)操作。これはリストを通過するたびに行われるため、O(n) O(n ** 2)アルゴリズムへの変換これは、ソースリストを変更せず、現在の位置を追跡するだけの方法を使用して修正できます。

dbr で示唆されているように、単純なソート済み(l1 + l2)と修正済みアルゴリズムのベンチマークを試しました。

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()を使用することをお勧めしますが、アルゴリズムの複雑さは劣ります。損益分岐点は、各ソースリストの約100万アイテム(合計200万アイテム)にあります。

ただし、マージアプローチの利点の1つは、ジェネレーターとして書き換えるのが簡単であり、実質的に少ないメモリを使用することです(中間リストは不要です)。

[編集]フィールド「date」を含むオブジェクトのリストを使用して、質問により近い状況でこれを再試行しました。これは日時オブジェクトです。上記のアルゴリズムは、代わりに.dateと比較するように変更され、ソート方法は次のように変更されました。

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

これにより状況は少し変わります。比較のコストが高いということは、実装の一定時間の速度と比較して、実行する数がより重要になることを意味します。これは、マージによって失われた地面が埋められ、代わりに100,000アイテムでsort()メソッドを超えることを意味します。さらに複雑なオブジェクト(たとえば、大きな文字列またはリスト)に基づいて比較すると、このバランスがさらにシフトする可能性があります。

# 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]:注:実際には、1,000,000個のアイテムに対して10回だけリピートしましたが、かなり遅いため、それに応じてスケールアップしました。

15
Brian

これは、2つのソートされたリストの単純なマージです。整数の2つのソートされたリストをマージする以下のサンプルコードをご覧ください。

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

これは、日時オブジェクトで正常に機能するはずです。お役に立てれば。

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

出力:

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はまったく別の話です。

4
akaihola

Pythonのソート実装「timsort」は、順序付けられたセクションを含むリスト用に特に最適化されています。さらに、Cで書かれています。

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

人々が述べたように、それは一定の要因によって比較関数をより多く呼び出すかもしれません(しかし、多くの場合、より短い期間でより多くの時間を呼び出すかもしれません!)。

ただし、これに依存することはありません。 –ダニエル・ナダシ

Python開発者はティムソートを維持するか、少なくともこの場合はO(n)のソートを維持することを約束します。

一般化されたソート(つまり、基数ソートを制限された値ドメインから分離する)
シリアルマシンではO(n log n)未満では実行できません。 –バリーケリー

右、一般的な場合の並べ替えはそれより速くすることはできません。しかしO()は上限であるため、任意の入力でtimsortがO(n log n)であることは、O(n)ソート済み(L1)+ソート済み(L2)。

3
FutureNerd

両方のリストを反復するMerge Sortのマージステップの実装:

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]

私はまだアルゴリズムについて学んでいますが、コードが何らかの面で改善される可能性がある場合、私に知らせてください、あなたのフィードバックは感謝します、ありがとう!

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

再帰的な実装は以下です。平均パフォーマンスは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
Pavel Paulau

反復で何が起こっているかを学習することとより一貫した方法でそれをしたい場合は、これを試してください

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

マージソートのマージステップを使用しました。しかし、私はgeneratorsを使用しました。 時間の複雑さ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

さて、単純なアプローチ(2つのリストを大きなリストに結合して並べ替える)はO(N * log(N))の複雑さです。一方、マージを手動で実装する場合(python libsの準備ができているコードについては知りませんが、エキスパートではありません)、複雑さはO(N)になります) 、これは明らかに高速です。この考えは、Barry Kellyの投稿でよく説明されています。

1
Drakosha

マージソートの「マージ」ステップを使用します。O(n)時間で実行されます。

wikipedia (擬似コード)から:

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

これは、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:]
0
ndsvw

このコードは時間の複雑さ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
0
Daniel

この問題に関する私の見解:

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)

リストを所定の場所に並べ替えます。 2つのオブジェクトを比較するための独自の関数を定義し、その関数を組み込みのソート関数に渡します。

バブルソートは使用しないでください。パフォーマンスがひどいです。

0
Josh Smeaton