Mesclando arrays ordenados em Python
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
Estou tentando mesclar matrizes classificadas em python. Como posso melhorar isso? Honestamente, parece que eu escrevi isso em C e não em Python
Respostas
vários
merge_lennão é usado- os parênteses extras em torno das verificações simples são desnecessários
l1_ptr <= len_list1-1pode ser mais claro comol1_ptr < len_list1- usar o nome da variável
l1_ptrpara salvar alguns caracteres enquanto torna mais difícil adivinhar pelo nome o que ela faz não é útil
Trabalhar com os índices diretamente não é realmente pitônico. Você pode tornar isso mais genérico, usando iterand nexte trabalhar para todos os iteráveis.
digitando
adicionar informações de digitação:
import typing
T = typing.TypeVar("T")
def merge_sorted_iterables(
iterable1: typing.Iterable[T], iterable2: typing.Iterable[T]
) -> typing.Iterable[T]:
Esta é uma explicação extra para o usuário desta função (e seu IDE).
docstring
Adicione alguma explicação sobre o que o método faz, espera do chamador e retorna.
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 `<=`).
...
"""
iterador
Em vez de acompanhar o índice, você pode usar itere next. Você nem precisa adicionar os itens a uma lista, você pode yieldfazê-los, para que o chamador do método possa decidir de que maneira ele deseja usar isso.
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)
Então tudo o que precisa ser feito é continuar o iterador que não terminou
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
testando
Você pode testar o comportamento, começando com os casos mais simples:
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"]))
descendente
Depois de fazer esses testes, você pode expandir facilmente o comportamento para também receber iteráveis descendentes:
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
Eu adicionei a palavra- ascendingchave como um argumento somente de palavra-chave para evitar confusão e manter a compatibilidade com versões anteriores
Um de seus testes:
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
Use um recuo de 4 espaços .
Não subtraia repetidamente 1 do mesmo valor imutável.
Simplifique a comparação condicional: basta usar else.
Aproveite list.extend() .
Elimine as condicionais de finalização: elas não são realmente necessárias. Código como zs.extend(xs[xi:])funcionará bem mesmo se xiexceder os limites da lista.
Encurte os nomes das variáveis para diminuir o peso do código e aumentar a legibilidade. Não há perda de significado aqui, porque todos os nomes curtos são bastante convencionais e fazem sentido em uma função genérica como esta.
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
Ontem à noite escrevi uma solução baseada em iterador, mas de alguma forma esqueci que next()suporta um defaultargumento útil: o código era estranho e Maarten Fabré fez uma implementação melhor. Mas se você estiver disposto a usar more_itertools.peekable() você pode obter uma implementação simples e legível. Obrigado a superb-rain por uma ideia nos comentários que me ajudou a simplificar ainda mais.
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)