Перебирайте разные списки длин, исключая дубликаты и сохраняя порядок в Python

У меня есть два списка, скажем:

keys1 = ['A', 'B', 'C', 'D', 'E',           'H', 'I']
keys2 = ['A', 'B',           'E', 'F', 'G', 'H',      'J', 'K']

Как создать объединенный список без дубликатов, которые сохраняют порядок обоих списков, вставляя отсутствующие элементы там, где они принадлежат? Вот так:

merged = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K']

Обратите внимание, что элементы можно сравнивать с равенством, но не упорядоченными (это сложные строки). Обновление: Элементы нельзя упорядочить, сравнивая их, но они имеют порядок, основанный на их появлении в исходных списках.

Update: In case of contradiction (different order in both input lists), any output containing all elements is valid. Of course with bonus points if the solution shows 'common sense' in preserving most of the order.

Update: Again (as some comments still argue about it), the lists normally don't contradict each other in terms of the order of the common elements. In case they do, the algorithm needs to handle that error gracefully.

Я начал с версии, которая выполняет итерации по спискам с помощью .next (), чтобы продвигать только список, содержащий непревзойденные элементы, но .next() просто не знает, когда остановиться.

merged = []
L = iter(keys1)
H = iter(keys2)
l = L.next()
h = H.next()

for i in range(max(len(keys1, keys2))):
  if l == h:
    if l not in merged:
      merged.append(l)
    l = L.next()
    h = H.next()

  elif l not in keys2:
    if l not in merged:
      merged.append(l)
    l = L.next()

  elif h not in keys1:
    if h not in merged:
      merged.append(h)
    h = H.next()

  else: # just in case the input is badly ordered
    if l not in merged:
      merged.append(l)
    l = L.next()
    if h not in merged:
      merged.append(h)
    h = H.next()   

print merged

Это явно не работает, поскольку .next() вызовет исключение для кратчайшего списка. Теперь я могу обновить свой код, чтобы поймать это исключение каждый раз, когда я вызываю .next (). Но код уже довольно не-pythonic, и это, очевидно, всплеск пузыря.

Есть ли у кого-нибудь лучшее представление о том, как перебирать эти списки для объединения элементов?

Бонусные очки, если я могу сделать это за три списка за один раз.

22
@RyanThompson, ты так прав. И есть даже худшее, где keys1 = ['A', 'B', 'D']; и keys2 = ['B', 'D', 'A']. В смысле математических доказательств абсурдно говорить, что mergeList может сохранить порядок ключей1 и keys2, поскольку оба противоположны.
добавлено автор Stephane Rolland, источник
как алгоритм должен решить этот случай: keys1 = ['A', '%', '*'] и keys1 = ['A', '@', '?']
добавлено автор Theodros Zelleke, источник
Я не думаю, что список, который вы хотите вычислить, гарантированно существует в целом. Что делать, если keys1 = ['A', 'B', 'D']; keys2 = ['D', 'C', 'B'] ?
добавлено автор Ryan Thompson, источник
Думая еще немного, я задаюсь вопросом, не ищет ли OP эффективного решения самой краткой общей проблемы суперструн?
добавлено автор Ryan Thompson, источник
Думаю, в этом все дело. Этот вопрос дает пример, когда желаемый ответ становится очевидным по интервалу и использованию алфавитных символов в порядке, но затем говорит, что элементы неупорядочены. Таким образом, приведенный пример не дает полного определения того, каков желаемый результат в общем случае.
добавлено автор Ryan Thompson, источник
@RyanThompson Существуют решения, а именно: [A, B, D, C, , но как выбрать, какой из них вернуть? И является ли элемент разрешенным повторением в выходной последовательности?
добавлено автор Khaur, источник
@Ryan Thompson: Элементы ARE упорядочены во входных списках. Они просто не могут быть заказаны по каким-либо вычислительным критериям.
добавлено автор Chaos_99, источник
Я обновил вопрос. @TheodrosZelleke: вывод в вашем случае должен быть либо ['A', '%', '*', '@', '?'] , либо ['A', '@ ','? ','% ',' * '] . Мне все равно, что.
добавлено автор Chaos_99, источник

5 ответы

Вам нужно в основном то, что делает любая утилита слияния: он пытается объединить две последовательности, сохраняя относительный порядок каждой последовательности. Вы можете использовать Python difflib модуль, чтобы разделить две последовательности и объединить их :

from difflib import SequenceMatcher

def merge_sequences(seq1,seq2):
    sm=SequenceMatcher(a=seq1,b=seq2)
    res = []
    for (op, start1, end1, start2, end2) in sm.get_opcodes():
        if op == 'equal' or op=='delete':
            #This range appears in both sequences, or only in the first one.
            res += seq1[start1:end1]
        elif op == 'insert':
            #This range appears in only the second sequence.
            res += seq2[start2:end2]
        elif op == 'replace':
            #There are different ranges in each sequence - add both.
            res += seq1[start1:end1]
            res += seq2[start2:end2]
    return res

Пример:

>>> keys1 = ['A', 'B', 'C', 'D', 'E',           'H', 'I']
>>> keys2 = ['A', 'B',           'E', 'F', 'G', 'H',      'J', 'K']
>>> merge_sequences(keys1, keys2)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K']

Обратите внимание, что ответ, который вы ожидаете, не обязательно является единственным возможным. Например, если мы изменим порядок последовательностей здесь, мы получим еще один ответ, который справедлив:

>>> merge_sequences(keys2, keys1)
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'I']
16
добавлено
И любопытством (явным :-)), как это работает, давайте скажем: ['K', 'A', 'O', 'S'] и ['S', 'O', 'A', ' КОРОЛЬ'] :-)
добавлено автор Stephane Rolland, источник
и в каких других обстоятельствах вы использовали эти модули? Я чувствую, что это может быть полезно, но мне ничего не приходило в голову.
добавлено автор Stephane Rolland, источник
+1 для J в не-алфавитном порядке ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K' , 'I'], вот что я пытался сказать в своем комментарии.
добавлено автор Stephane Rolland, источник
это не удается при попытке объединить 2 списка с перевернутыми элементами. merge_sequences ([1,2,3], [3,2,1]) закончится в [1,2,3,2,1] . какие-либо предложения?
добавлено автор aschmid00, источник
@StephaneRolland: В этом случае вы получите некоторое дублирование, например KAOSOAKING.
добавлено автор interjay, источник
@StephaneRolland Это может быть полезно для создания инструментов сравнения файлов, программного обеспечения для управления версиями, инкрементного исправления и т. Д. Я не думаю, что сам использовал его.
добавлено автор interjay, источник
@ aschmid00 Если вы не хотите дублировать (я этого не делаю), удалите дубликаты из списка в конце, сохраняя порядок: python при сохранении порядка "> stackoverflow.com/questions/480214/…
добавлено автор Sam Watkins, источник
Просто проверил. Работает как шарм. И достаточно быстро в моем случае (около 1000 элементов в списке в 3 списках). Ты лучший!
добавлено автор Chaos_99, источник
Мне очень нравится. Благодаря! @Stephane Rolland: Вы заметили только нестандартный J, потому что эти буквы имеют естественный порядок. С случайными строками «J», «K» и «I» имеют точно такое же свойство: они следуют за «K». Таким образом, разрешение очень важно.
добавлено автор Chaos_99, источник

Я бы использовал набор (cf.python doc) , который я бы заполнил с элементами двух списков, один за другим.

И сделайте список из набора, когда это будет сделано.

Обратите внимание, что в вашем вопросе есть противоречие/парадокс: вы хотите сохранить порядок для элементов, которые нельзя сравнивать (только равенство, потому что «они сложные строки», как вы сказали).

EDIT: the OP is right noticing that sets don't preserve order of insertion.

3
добавлено
вы правы, мой добавленный код просто неверен. Я удаляю эту часть.
добавлено автор Stephane Rolland, источник
thx, указав мне, что наборы не будут сохранять порядок вставки.
добавлено автор Stephane Rolland, источник
Заказ, о котором вы говорите, - это порядок списка, а не порядок его стоимости (поскольку они не сопоставимы по порядку). Вопрос биаизм, потому что он представляет собой два списка с неявным алфавитным порядком, который не применим в соответствии с OP (потому что это сложные строки ...) A + iB? :-)
добавлено автор Stephane Rolland, источник
+1 для указания противоречия
добавлено автор mgilson, источник
Набор неупорядочен. Он хочет сохранить порядок этих двух списков. И нет никакого противоречия: можно сохранить порядок в списке, не сравнивая элементы списка.
добавлено автор interjay, источник
Из примера я предполагаю, что элементы между совпадениями должны быть вставлены, начиная с первых в списке.
добавлено автор Khaur, источник
Привет, Стефан. Ваш обновленный ответ теперь вычисляет общие элементы обоих списков. Не то, что я просил.
добавлено автор Chaos_99, источник
+1 для получения разницы между порядком элементов и упорядочиваемыми элементами. (-1 для множества)
добавлено автор Chaos_99, источник

Я подозреваю, что вы можете попросить решение кратчайшей общей проблемы supersequence , которое, я считаю, NP-жесткий в общем случае произвольного количества входных последовательностей. Я не знаю о каких-либо библиотеках для решения этой проблемы, поэтому вам, возможно, придется реализовать один за другим. Вероятно, самый быстрый способ получить рабочий код - взять ответ interjay с помощью difflib, а затем использовать уменьшить , чтобы запустить его на произвольном количестве списков (не забудьте указать пустой список в качестве третьего аргумента ).

2
добавлено
Да, определение wiki кажется правильным для моей проблемы. Спасибо, что указали правильный термин и примечание об использовании сокращения для более чем двух входных последовательностей.
добавлено автор Chaos_99, источник

Используя только списки, вы можете добиться этого с помощью нескольких простых циклов для и .copy() :

def mergeLists(list1, list2):
    # Exit if list2 is empty
    if not len(list2):
        return list1
    # Copy the content of list2 into merged list
    merged = list2.copy()

    # Create a list for storing temporary elements
    elements = []
    # Create a variable for storing previous element found in both lists
    previous = None

    # Loop through the elements of list1
    for e in list1:
        # Append the element to "elements" list if it's not in list2
        if e not in merged:
            elements.append(e)

        # If it is in list2 (is a common element)
        else:

            # Loop through the stored elements
            for x in elements:
                # Insert all the stored elements after the previous common element
                merged.insert(previous and merged.index(previous) + 1 or 0, x)
            # Save new common element to previous
            previous = e
            # Empty temporary elements
            del elements[:]

    # If no more common elements were found but there are elements still stored
    if len(elements)
        # Insert them after the previous match
        for e in elements:
            merged.insert(previous and merged.index(previous) + 1 or 0, e)
    # Return the merged list
    return merged

In [1]: keys1 = ["A", "B",      "D",      "F", "G", "H"]
In [2]: keys2 = ["A",      "C", "D", "E", "F",      "H"]
In [3]: mergeLists(keys1, keys2)
Out[3]: ["A", "B", "C", "D", "E", "F", "G", "H"]

Английский язык не является моим первым языком, и это довольно сложно объяснить, но если вы заботитесь об объяснении, вот что он делает:

  • Существует локальный список, называемый elements , который может хранить временные элементы.
  • Существует локальная переменная с именем previous , которая хранит предыдущий элемент, который был в обоих списках.
  • Когда он найдет элемент, который НЕ находится в list2 , но находится в list1 , он добавит этот элемент в список elements и продолжит цикл.
  • Когда он попадает в элемент, который находится в обоих списках, он проходит через список elements , добавляя все элементы после previous в list2 .
  • Новое совпадение затем сохраняется в previous и elements сбрасывается на [] , и цикл продолжается.
  • Начало списков и конец списков подсчитывается как общий элемент, если первый или последний элемент не является общим элементом в обоих списках.

Таким образом, он всегда будет следовать этому формату:

  1. Предыдущий общий элемент
  2. Элементы из списка1, между двумя общими элементами
  3. Элементы в списке2, между двумя общими элементами
  4. Новый общий элемент

Так, например:

l1 = ["A", "B", "C",      "E"]
l2 = ["A",           "D", "E"]
  1. The revious common element A will be first in the merged list.
  2. Elements from l1 between the previous common element A and the new common element E will be inserted right after A.
  3. Elements from l2 between the previous common elmeent A and the new common elmeent E will be inserted right after the elements from l1.
  4. The new common element E will be last element.
  5. Back to step 1 if more common elements found.

    ["A", "B", "C", "D", "E"]

1
добавлено
Ни одно из этих решений не сохраняет порядок списков. Второе решение близко, но может изменить порядок элементов во втором списке.
добавлено автор interjay, источник
Исправлено и удалил параметр set .
добавлено автор user1632861, источник
Как сказал interjay: с наборами порядок не сохраняется. Второе решение не чередуется, а только добавляет.
добавлено автор Chaos_99, источник

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

Постановка задачи

Напишите функцию merge_lists, которая объединит список списков с перекрывающимися элементами, сохраняя при этом порядок элементов.

Ограничения

  1. Если элемент A находится перед элементом B во всех списках, где они встречаются вместе, то элемент A должен предшествовать пункту B в конечном списке также

  2. Если порядок обмена A и пункт B в разных списках, то есть в некоторых списках A предшествует B, а в некоторых других B предшествует A, тогда порядок A и B в конечном списке должен быть таким же, как и их порядок в первом списке, где они встречаются вместе. То есть, если A предшествует B в l1 и B предшествует A в l2, тогда A должен предшествовать B в конечном списке

  3. Если элементы A и элемент B не встречаются вместе в любом списке, их порядок должен определяться положением списка, в котором каждый из них встречается первым. То есть, если элемент A находится в l1 и l3, элемент B находится в l2 и l6, тогда порядок в конечном списке должен быть A, а затем B

Тестовый кейс 1:

Входные данные:

l1 = [«Тип и размер», «Ориентация», «Материал», «Местоположения», «Тип передней печати», «Обратный тип печати»]

l2 = [«Тип и размер», «Материал», «Местоположения», «Тип передней печати», «Размер передней печати», «Обратный тип печати», «Назад размер печати»]

l3 = ["Ориентация", "Материал", "Местоположения", "Цвет", "Тип передней печати"]

merge_lists ([l1, l2, l3])

Вывод:

[«Тип и размер», «Ориентация», «Материал», «Местоположения», «Цвет», «Тип передней печати», «Размер передней печати», «Обратный тип печати», «Назад размер печати»]

Тестовый пример 2:

Входные данные:

l1 = ["T", "V", "U", "B", "C", "I", "N"]

l2 = ["Y", "V", "U", "G", "B", "I"]

l3 = ["X", "T", "V", "M", "B", "C", "I"]

l4 = ["U", "P", "G"]

merge_lists ([l1, l2, l3, l4])

Вывод:

['Y', 'X', 'T', 'V', 'U', 'M', 'P', 'G', 'B', 'C', 'I', 'N']

Тестовый кейс 3:

Входные данные:

l1 = ["T", "V", "U", "B", "C", "I", "N"]

l2 = ["Y", "U", "V", "G", "B", "I"]

l3 = ["X", "T", "V", "M", "I", "C", "B"]

l4 = ["U", "P", "G"]

merge_lists ([l1, l2, l3, l4])

Вывод:

['Y', 'X', 'T', 'V', 'U', 'M', 'P', 'G', 'B', 'C', 'I', 'N']

Решение

I arrived at a reasonable Решение which solved it correctly for all the data I had. (It might be wrong for some other data set. Will leave it for others to comment that). Here is the Решение

def remove_duplicates(l):
    return list(set(l))

def flatten(list_of_lists):
    return [item for sublist in list_of_lists for item in sublist]

def difference(list1, list2):
    result = []
    for item in list1:
        if item not in list2:
            result.append(item)
    return result

def preceding_items_list(l, item):
    if item not in l:
        return []
    return l[:l.index(item)]

def merge_lists(list_of_lists):
    final_list = []
    item_predecessors = {}

    unique_items = remove_duplicates(flatten(list_of_lists))
    item_priorities = {}

    for item in unique_items:
        preceding_items = remove_duplicates(flatten([preceding_items_list(l, item) for l in list_of_lists]))
        for p_item in preceding_items:
            if p_item in item_predecessors and item in item_predecessors[p_item]:
                preceding_items.remove(p_item)
        item_predecessors[item] = preceding_items
    print "Item predecessors ", item_predecessors

    items_to_be_checked = difference(unique_items, item_priorities.keys())
    loop_ctr = -1
    while len(items_to_be_checked) > 0:
        loop_ctr += 1
        print "Starting loop {0}".format(loop_ctr)
        print "items to be checked ", items_to_be_checked
        for item in items_to_be_checked:
            predecessors = item_predecessors[item]
            if len(predecessors) == 0:
                item_priorities[item] = 0
            else:
                if all(pred in item_priorities for pred in predecessors):
                    item_priorities[item] = max([item_priorities[p] for p in predecessors]) + 1
        print "item_priorities at end of loop ", item_priorities
        items_to_be_checked = difference(unique_items, item_priorities.keys())
        print "items to be checked at end of loop ", items_to_be_checked
        print

    final_list = sorted(unique_items, key=lambda item: item_priorities[item])
    return final_list

Я также открыл исходный код как часть библиотеки с именем toolspy. Таким образом, вы можете просто сделать это

pip install toolspy

from toolspy import merge_lists
lls=[['a', 'x', 'g'], ['x', 'v', 'g'], ['b', 'a', 'c', 'x']]
merge_lists(lls)
0
добавлено
Python
Python
7 654 участник(ов)

Уютный чат для профессионалов, занимающихся поиском питоньих мудростей. Как не получить бан: https://t.me/ru_python/577926

Python beginners
Python beginners
4 449 участник(ов)

Вопросы про Python для чайников. Cпам и троллинг неприемлем. Не злоупотребляйте стикерами. Частозадаваемые вопросы: https://github.com/ru-python-beginners/faq/blob/master/README.md Статистика тут: https://grstats.me/chat/x4qym2k5uvfkr3al6at7

Верстка сайтов HTML/CSS/JS/PHP
Верстка сайтов HTML/CSS/JS/PHP
3 439 участник(ов)

Правила группы: напишите !rules в чате. Группа Вк: vk.com/web_structure Freelancer: @web_fl Веб Дизайн: @dev_design Маркетолог: @topmarkening Автор: @M_Boroda

CSS — русскоговорящее сообщество
CSS — русскоговорящее сообщество
1 502 участник(ов)

Сообщество любителей CSS Возникли проблемы с CSS? – пиши сюда, обсудим и предложим самое лучшее решение Работа: @css_ru_jobs Правила: https://teletype.in/@css_ru/r1EWtQ2w7 Приходите в наши чаты @javascript_ru и @frontend_ru Флуд: @css_flood

pro.python
pro.python
1 090 участник(ов)

Сообщество разработчиков под Python Создатель: @rodgelius

Чат — Типичный Верстальщик
Чат — Типичный Верстальщик
1 080 участник(ов)

Основной канал: @tpverstak Обратная связь: @annblok Все ссылки на соц.сети проекта: http://taplink.cc/tpverstak ПРАВИЛА ЧАТА — https://teletype.in/@annblok/BygPgC3E7

Rude Python
Rude Python
971 участник(ов)

Python без „девочек”, здесь матерятся и унижают Django. Not gay friendly. Правила: t.me/rudepython/114107 @rudepython | t.me/rudepython

rupython
rupython
509 участник(ов)

Группа создана с целью оперативного получения ответов на возникающие вопросы по разработке на яп python, смежные темы, а также человеческого общения. Приветствую!

Python-programming
Python-programming
266 участник(ов)

Чат группы вконтакте https://vk.com/python_community

Веб-Технологи: UI/UX, Вёрстка, Фронтенд
Веб-Технологи: UI/UX, Вёрстка, Фронтенд
167 участник(ов)

Всё про веб-дизайн и вёрстку. А также: HTML, CSS, флекс и бутстрапы, шаблонизаторы, препроцессоры, методологии, аглифаеры, улучшаторы и обфускаторы. Обсуждаем темы юзабилити, устраиваем А/В тесты лендингов, и проводим аудит.

DTP :: @DTPublish
DTP :: @DTPublish
147 участник(ов)

Обсуждаемые темы: полиграфия, препресс, верстка, дизайн, иллюстрации, скрипты, плагины. Канал - @DTPublishing

css_jobs
css_jobs
26 участник(ов)

Чат для вопросов по css и html: @css_ru Флуд: @css_flood Канал с вакансиями и резюме: @css_jobs_feed

css_флуд
css_флуд
10 участник(ов)