Menggabungkan array yang diurutkan dengan Python

Aug 31 2020

def merge_arrays(list1, list2):
  len_list1 = len(list1); len_list2 = len(list2)
  merge_len = len_list1 + len_list2
  merge_list = []

  l1_ptr = 0
  l2_ptr = 0
  # import pdb; pdb.set_trace()
  while(l1_ptr <= len_list1-1 and l2_ptr <= len_list2-1):

    if (list1[l1_ptr] <= list2[l2_ptr]):
      merge_list.append(list1[l1_ptr])
      l1_ptr += 1
    
    elif (list1[l1_ptr] > list2[l2_ptr]):
      merge_list.append(list2[l2_ptr])
      l2_ptr += 1
      
  if l1_ptr > len_list1-1: #list1 exhausted
    for item in list2[l2_ptr:]:
      merge_list.append(item)
  else:
    for item in list1[l1_ptr:]:
      merge_list.append(item)

  return merge_list

Saya mencoba menggabungkan array yang diurutkan dengan python. Bagaimana saya bisa meningkatkan ini? Sejujurnya sepertinya saya telah menulis ini dalam C dan bukan dengan Python

Jawaban

7 MaartenFabré Aug 31 2020 at 17:07

berbagai

  • merge_lentidak terpakai
  • tanda kurung tambahan di sekitar pemeriksaan sederhana tidak diperlukan
  • l1_ptr <= len_list1-1dapat dibuat lebih jelas sebagail1_ptr < len_list1
  • menggunakan nama variabel l1_ptruntuk menyimpan beberapa karakter sambil membuatnya lebih sulit untuk menebak dari nama apa fungsinya tidak berguna

Bekerja dengan indeks secara langsung memang tidak benar-benar pythonic. Anda dapat membuatnya lebih umum, menggunakan iterand next, dan berfungsi untuk semua iterable.

mengetik

tambahkan informasi pengetikan:

import typing

T = typing.TypeVar("T")


def merge_sorted_iterables(
    iterable1: typing.Iterable[T], iterable2: typing.Iterable[T]
) -> typing.Iterable[T]:

Ini adalah penjelasan tambahan untuk pengguna fungsi ini (dan IDE-nya).

doktrin

Tambahkan beberapa penjelasan tentang apa yang dilakukan metode, harapkan dari pemanggil, dan kembali.

def merge_sorted_iterables(
    iterable1: typing.Iterable[T], iterable2: typing.Iterable[T]
) -> typing.Iterable[T]:
    """Merge 2 sorted iterables.
    
    The items in the iterables need to be comparable (and support `<=`).
    ...
    """

pembuat ulang

Alih-alih melacak indeks, Anda dapat menggunakan iterdan next. Anda bahkan tidak perlu menambahkan item ke daftar, Anda dapat yieldmelakukannya, sehingga pemanggil metode dapat memutuskan dengan cara apa dia ingin menggunakan ini.

done = object()

iterator1 = iter(iterable1)
iterator2 = iter(iterable2)

item1 = next(iterator1, done)
item2 = next(iterator2, done)
while item1 is not done and item2 is not done:
    if item1 <= item2:
        yield item1
        item1 = next(iterator1, done)
    else:
        yield item2
        item2 = next(iterator2, done)

Maka yang perlu dilakukan hanyalah melanjutkan iterator yang belum selesai

    if item1 is not done:
        yield item1
        yield from iterator1
    if item2 is not done:
        yield item2
        yield from iterator2

import typing

T = typing.TypeVar("T")


def merge_sorted_iterables(
    iterable1: typing.Iterable[T], iterable2: typing.Iterable[T]
) -> typing.Iterable[T]:
    """Merge 2 sorted iterables.
    
    The items in the iterables need to be comparable (and support `<=`).
    ...
    """
    done = object()
    
    iterator1 = iter(iterable1)
    iterator2 = iter(iterable2)
    
    item1 = next(iterator1, done)
    item2 = next(iterator2, done)
    
    while item1 is not done and item2 is not done:
        if item1 <= item2:
            yield item1
            item1 = next(iterator1, done)
        else:
            yield item2
            item2 = next(iterator2, done)

    if item1 is not done:
        yield item1
        yield from iterator1
    if item2 is not done:
        yield item2
        yield from iterator2

pengujian

Anda dapat menguji perilaku, dimulai dengan kasus paling sederhana:

import pytest

def test_empty():
    expected = []
    result = list(merge_sorted_iterables([], []))
    assert result == expected

def test_single():
    expected = [0, 1, 2]
    result = list(merge_sorted_iterables([], range(3)))
    assert expected == result
    result = list(merge_sorted_iterables(range(3), [],))
    assert expected == result

def test_simple():
    expected = [0, 1, 2, 3, 4, 5]
    result = list(merge_sorted_iterables([0, 1, 2], [3, 4, 5]))
    assert result == expected
    result = list(merge_sorted_iterables([0, 2, 4], [1, 3, 5]))
    assert result == expected
    result = list(merge_sorted_iterables([3, 4, 5], [0, 1, 2],))
    assert result == expected

def test_string():
    expected = list("abcdef")

    result = list(merge_sorted_iterables("abc", "def"))
    assert result == expected
    result = list(merge_sorted_iterables("ace", "bdf"))
    assert result == expected
    result = list(merge_sorted_iterables("def", "abc",))
    assert result == expected

def test_iterable():
    
    expected = [0, 1, 2, 3, 4, 5]
    result = list(merge_sorted_iterables(iter([0, 1, 2]), iter([3, 4, 5])))
    assert result == expected
    result = list(merge_sorted_iterables(iter([0, 2, 4]), iter([1, 3, 5])))
    assert result == expected
    result = list(merge_sorted_iterables(iter([3, 4, 5]), iter([0, 1, 2]),))
    assert result == expected

def test_comparable():
    with pytest.raises(TypeError, match="not supported between instances of"):
        list(merge_sorted_iterables([0, 1, 2], ["a", "b", "c"]))

menurun

Setelah Anda memiliki tes ini, Anda dapat dengan mudah memperluas perilaku untuk juga mengambil iterables turun:

import operator

def merge_sorted_iterables(
    iterable1: typing.Iterable[T],
    iterable2: typing.Iterable[T],
    *,
    ascending: bool = True,
) -> typing.Iterable[T]:
    """Merge 2 sorted iterables.
    
    The items in the iterables need to be comparable.
    ...
    """
    done = object()

    iterator1 = iter(iterable1)
    iterator2 = iter(iterable2)

    item1 = next(iterator1, done)
    item2 = next(iterator2, done)

    comparison = operator.le if ascending else operator.ge

    while item1 is not done and item2 is not done:
        if comparison(item1, item2):
            yield item1
            item1 = next(iterator1, done)
        else:
            yield item2
            item2 = next(iterator2, done)

    if item1 is not done:
        yield item1
        yield from iterator1
    if item2 is not done:
        yield item2
        yield from iterator2

Saya menambahkan ascendingkata kunci sebagai argumen kata kunci saja untuk menghindari kebingungan dan menjaga kompatibilitas ke belakang

Salah satu tesnya:

def test_descending():
    expected = [5, 4, 3, 2, 1, 0]
    result = list(
        merge_sorted_iterables([2, 1, 0], [5, 4, 3], ascending=False)
    )
    assert result == expected
    result = list(
        merge_sorted_iterables([4, 2, 0], [5, 3, 1], ascending=False)
    )
    assert result == expected
    result = list(
        merge_sorted_iterables([5, 4, 3], [2, 1, 0], ascending=False)
    )
    assert result == expected
7 FMc Aug 31 2020 at 12:57

Gunakan indentasi 4 spasi .

Jangan berulang kali mengurangi 1 dari nilai yang sama yang tidak berubah.

Sederhanakan kondisi perbandingan: cukup gunakan else.

Manfaatkan list.extend() .

Lepaskan persyaratan penutup: sebenarnya tidak diperlukan. Kode seperti zs.extend(xs[xi:])akan berfungsi dengan baik bahkan jika ximelebihi batas daftar.

Persingkat nama variabel untuk meringankan bobot kode dan meningkatkan keterbacaan. Tidak ada kehilangan makna di sini, karena semua nama pendek cukup konvensional dan masuk akal dalam fungsi generik seperti ini.

def merge_arrays(xs, ys):
    # Setup.
    xmax = len(xs) - 1
    ymax = len(ys) - 1
    xi = 0
    yi = 0
    zs = []

    # Compare and merge.
    while xi <= xmax and yi <= ymax:
        if xs[xi] <= ys[yi]:
            zs.append(xs[xi])
            xi += 1
        else:
            zs.append(ys[yi])
            yi += 1

    # Merge any remainders and return.
    zs.extend(ys[yi:])
    zs.extend(xs[xi:])
    return zs

Tadi malam saya menulis solusi berbasis iterator tetapi entah bagaimana lupa yang next()mendukung defaultargumen praktis: kodenya canggung dan Maarten Fabré melakukan implementasi yang lebih baik. Tetapi jika Anda ingin menggunakan more_itertools.peekable() Anda dapat mencapai implementasi yang sederhana dan mudah dibaca. Terima kasih kepada hujan luar biasa untuk ide di komentar yang membantu saya menyederhanakan lebih lanjut.

from more_itertools import peekable

def merge(xs, ys):
    xit = peekable(xs)
    yit = peekable(ys)
    while xit and yit:
        it = xit if xit.peek() <= yit.peek() else yit
        yield next(it)
    yield from (xit or yit)