Sortierte Arrays in Python zusammenführen

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

Ich versuche, sortierte Arrays in Python zusammenzuführen. Wie kann ich das verbessern? Ehrlich gesagt sieht es so aus, als hätte ich das in C und nicht in Python geschrieben

Antworten

7 MaartenFabré Aug 31 2020 at 17:07

verschieden

  • merge_lenist unbenutzt
  • die zusätzlichen Klammern um die einfachen Prüfungen sind unnötig
  • l1_ptr <= len_list1-1kann klarer gemacht werden alsl1_ptr < len_list1
  • Es ist nicht sinnvoll , den Variablennamen l1_ptrzu verwenden, um ein paar Zeichen zu sparen, während es schwieriger wird, anhand des Namens zu erraten, was er tut

Das direkte Arbeiten mit den Indizes ist in der Tat nicht wirklich pythonisch. Sie können dies generischer gestalten, indem Sie iterand verwenden nextund für alle Iterables arbeiten.

tippen

Eingabeinformationen hinzufügen:

import typing

T = typing.TypeVar("T")


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

Dies ist eine zusätzliche Erklärung für den Benutzer dieser Funktion (und seiner IDE).

Dokumentzeichenfolge

Fügen Sie eine Erklärung hinzu, was die Methode tut, vom Aufrufer erwartet und zurückgibt.

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 `<=`).
    ...
    """

Iterator

Anstatt den Index zu verfolgen, können Sie iterund verwenden next. Sie müssen die Elemente nicht einmal einer Liste hinzufügen, Sie können yieldsie, sodass der Aufrufer der Methode entscheiden kann, auf welche Weise er diese verwenden möchte.

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)

Dann muss nur noch der Iterator fortgesetzt werden, der nicht fertig ist

    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

testen

Sie können das Verhalten testen, beginnend mit den einfachsten Fällen:

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

absteigend

Sobald Sie diese Tests eingerichtet haben, können Sie das Verhalten leicht erweitern, um auch absteigende Iterables zu verwenden:

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

Ich habe das ascendingSchlüsselwort als Nur-Schlüsselwort-Argument hinzugefügt, um Verwirrung zu vermeiden und die Abwärtskompatibilität zu wahren

Einer seiner Tests:

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

Verwenden Sie einen 4-Leerzeichen-Einzug .

Subtrahieren Sie nicht wiederholt 1 von demselben unveränderlichen Wert.

Vereinfachen Sie die Vergleichsbedingung: Verwenden Sie einfach else.

Nutzen Sie list.extend() .

Lassen Sie die Abschlussbedingungen fallen: Sie werden nicht wirklich benötigt. Code like zs.extend(xs[xi:])funktioniert gut, auch wenn er xidie Listengrenzen überschreitet.

Kürzen Sie die Variablennamen, um das Codegewicht zu verringern und die Lesbarkeit zu verbessern. Hier gibt es keinen Bedeutungsverlust, denn alle Kurznamen sind ziemlich konventionell und in einer generischen Funktion wie dieser sinnvoll.

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

Letzte Nacht habe ich eine iteratorbasierte Lösung geschrieben, aber irgendwie vergessen, dass sie next()ein praktisches defaultArgument unterstützt: Der Code war umständlich und Maarten Fabré hat eine schönere Implementierung vorgenommen. Aber wenn Sie bereit sind, more_itertools.peekable() zu verwenden , können Sie eine einfache, lesbare Implementierung erreichen. Danke an superb-rain für eine Idee in den Kommentaren, die mir geholfen hat, weiter zu vereinfachen.

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)