Sortierte Arrays in Python zusammenführen
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
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
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)