Я хочу перебрать два списка таким образом, чтобы я мог взять произвольное количество значений из одного списка и сохранить свою позицию в другом.

Я использовал индексы для хранения текущей позиции в каждом списке, а затем цикл while для их просмотра, но это определенно не очень питонно.

def alternate_iterate(a,b,cond=lambda x, y : x > y):
    pos_a = 0
    pos_b = 0
    retval = []

    while(True):

        if(pos_a == len(a) and pos_b == len(b)):
            break

        if(pos_a < len(a) and cond(a[pos_a],b[pos_b])):
            retval += [a[pos_a]]
            pos_a += 1
        elif(pos_b < len(b)):
            retval += [b[pos_b]]
            pos_b += 1

    return retval

#example usage
print(alternate_iterate(['abc','abcd','ab','abc','ab'],
                        ['xy','xyz','x','xyz'],
                        cond=lambda x,y: len(x) > len(y))

Это должно вывести ['abc','abdc','xy','xyz','ab','abc','ab','x','xyz'], где у вас нет идеального чередования 1: 1. Порядок элементов и тип элементов должны зависеть только от того, что cond определено как.

4
CRotelli 1 Июл 2019 в 18:11

4 ответа

Лучший ответ

Более Pythonic способ, как правило, вообще не использовать индексы, и предпочтительно не использовать исключения в качестве средства управления «намеченной» программной логикой. Вам также следует избегать лишних скобок.

Вот как вы можете сделать это с помощью итераторов:

def merge(a, b, cond=lambda x, y : x < y):
    Done           = []
    iterA, iterB   = iter(a), iter(b)
    valueA, valueB = next(iterA, Done), next(iterB, Done)
    result         = []
    while not(valueB is Done and valueA is Done):
        if valueB is Done or valueA is not Done and cond(valueA, valueB):
            result.append(valueA)
            valueA = next(iterA, Done)
        else:
            result.append(valueB)
            valueB = next(iterB, Done)
    return result

Это дает дополнительное преимущество, заключающееся в эффективной работе функции с любыми итеративными данными в качестве параметров.

Например:

print(merge(range(5, 10), range(7, 15)))

# [5, 6, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13, 14]

Это также облегчает создание итераторной версии функции для отложенного обхода:

def iMerge(a, b, cond=lambda x, y : x < y):
    Done           = []
    iterA, iterB   = iter(a), iter(b)
    valueA, valueB = next(iterA, Done), next(iterB, Done)
    while not(valueB is Done and valueA is Done):
        if valueB is Done or valueA is not Done and cond(valueA, valueB):
            yield valueA
            valueA = next(iterA ,Done)
        else:
            yield valueB
            valueB = next(iterB, Done)

РЕДАКТИРОВАТЬ Изменил None на Done, чтобы позволить функции поддерживать None в качестве допустимого значения в списках ввода.

3
Alain T. 2 Июл 2019 в 12:22

Добро пожаловать в Stackoverflow. Подводя итог, вы, кажется, хотите взять значение из одного списка или другого в зависимости от значения некоторого предиката. Ваша существующая логика, по-видимому, не учитывает возможность исчерпания одного из списков, и в этот момент я предполагаю, что вы захотите скопировать все оставшиеся значения из другого списка.

Вместо использования значений индекса для выбора последовательных элементов списка вы можете создать в списке итератор и использовать функцию next для получения следующего значения.

В этом случае ваша логика будет выглядеть примерно так:

def alternate_iterate(a_lst, b_lst, cond=lambda x, y: x > y):
    a_iter = iter(a_lst)
    b_iter = iter(b_lst)
    a = next(a_iter)
    b = next(b_iter)
    ret = []
    while True:
        if cond(a, b):
            ret.append(a)
            try:
                a = next(a_iter)
            except StopIteration:
                ret.append(b)
                for x in b_iter:
                    ret.append(x)
                return ret
        else:
            ret.append(b)
            try:
                b = next(b_iter)
            except StopIteration:
                ret.append(a)
                for x in a_iter:
                    ret.append(x)
                return ret


print(alternate_iterate(['abc','abcd','ab','abc','ab'],
                        ['xy','xyz','x','xyz'],
                        cond=lambda x,y: len(x) > len(y)))

Результат, который я получаю,

['abc', 'abcd', 'xy', 'xyz', 'ab', 'abc', 'ab', 'x', 'xyz']

Что может показаться тем, что вы ожидаете.

Как это часто бывает в таких примерах, вы пишете больше логики для обработки более редких угловых случаев (в данном случае один или другой список исчерпывается), чем для обработки «счастливого пути», где все идет как обычно.

1
holdenweb 1 Июл 2019 в 15:42

Эта версия использует только итераторы для достижения функциональности Lazilly (которая является Pythonic):

a = ['abc','abcd','ab','abc','ab']
b = ['xy','xyz','x','xyz']

cond=lambda x,y: len(x) > len(y)

def alternate_iterate(a, b, cond):
    a, b = iter(a), iter(b)

    def _return_rest():
        def _f(val, it):
            yield val
            yield from it
        return _f

    v1, v2 = next(a, _return_rest), next(b, _return_rest)

    while True:
        if v1 is _return_rest:
            yield from v1()(v2, b)
            break

        if v2 is _return_rest:
            yield from v2()(v1, a)
            break

        if cond(v1, v2):
            yield v1
            v1 =  next(a, _return_rest)
        else:
            yield v2
            v2 = next(b, _return_rest)

print(list(alternate_iterate(a, b, cond)))

Печать :

['abc', 'abcd', 'xy', 'xyz', 'ab', 'abc', 'ab', 'x', 'xyz']
0
Andrej Kesely 1 Июл 2019 в 16:13

Поместите свои списки в генераторы, и затем вы можете вызвать next для каждого из них, чтобы получить следующее значение. Этот ответ не является полным решением, просто чтобы показать, как генераторы могут создавать значения в любом порядке с помощью очень простого кода Pythonic:

agen = iter(a)
bgen = iter(b)
print next(agen) # 'a'
print next(bgen) # 1
print next(bgen) # 2
print next(agen) # 'b'

И так далее.

-4
Peter Westlake 1 Июл 2019 в 15:28