【问题标题】:Interleave different length lists, elimating duplicates, and preserve order交错不同长度的列表,消除重复,并保持顺序
【发布时间】:2012-12-23 20:39:00
【问题描述】:

我有两个列表,比如说:

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']

请注意,元素可以根据相等性进行比较,但不是有序的(它们是复杂的字符串)。 元素不能通过比较来排序,但它们有一个基于它们在原始列表中出现的顺序。

如果出现矛盾(两个输入列表中的顺序不同),任何包含所有元素的输出都是有效的。当然,如果解决方案在保留大部分订单方面显示出“常识”,则可以加分。

再一次(正如一些 cmets 仍在争论的那样),列表通常不会在公共元素的顺序方面相互矛盾。如果他们这样做了,算法需要优雅地处理该错误。

我从一个版本开始,它使用 .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了,这显然会破灭泡沫。

有没有人更好地了解如何遍历这些列表以组合元素?

如果我可以一次完成三个列表,则可以获得奖励积分。

【问题讨论】:

  • 我不认为您要计算的列表通常可以保证存在。如果keys1 = ['A', 'B', 'D']; keys2 = ['D', 'C', 'B']怎么办?
  • 算法应该如何解决这种情况:keys1 = ['A', '%', '*']keys1 = ['A', '@', '?']
  • @RyanThompson 有解决方案,即['A', 'B', 'D', 'C', 'B'] 和['A', 'D', 'C', 'B', 'D'],但是如何选择返回哪一个呢?一个元素是否允许在输出序列中重复?
  • 我想这就是重点。该问题给出了一个示例,其中通过间距和按顺序使用字母字符使所需答案变得明显,但随后说元素是无序的。因此,给出的示例并没有完全说明在一般情况下所需的结果。
  • 再想一想,我想知道 OP 是否没有有效地寻求最短常见超字符串问题的解决方案?

标签: python list iterator


【解决方案1】:

我会使用一个 Set (cf. python doc),我会一个接一个地填充两个列表的元素。

完成后从集合中列出一个列表。

请注意,您的问题存在矛盾/悖论:您希望保留无法比较的元素的顺序(只有相等,因为正如您所说,“它们是复杂的字符串”)。

编辑:OP 正确地注意到集合不保留插入顺序

【讨论】:

  • 从示例中我猜匹配之间的元素应该从第一个列表中的元素开始插入。
  • 一个集合是无序的。他想在这里保留两个列表的顺序。并且没有矛盾:可以在不比较列表元素的情况下保留列表中的顺序。
  • 您所指的顺序是列表的顺序,而不是项目值的顺序(因为它们不是有序可比的)。这个问题是有偏见的,因为它表示具有隐含字母顺序的两个列表,根据 OP 不适用(因为这些是复杂的字符串......) A + iB ? :-)
  • +1 用于获取元素顺序和可排序元素之间的差异。 (集合为-1)
  • 感谢您指出集合不会保留插入顺序。
【解决方案2】:

您需要的基本上是任何合并实用程序所做的:它尝试合并两个序列,同时保持每个序列的相对顺序。您可以使用 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']

【讨论】:

  • +1 用于非字母顺序的 J ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' , 'J', 'K', 'I'],这就是我在评论中想要表达的意思。
  • 并且通过(纯粹:-))好奇它是如何工作的让我们说:['K','A','O','S']和['S',' O','A','K','I','N','G'] :-)
  • @StephaneRolland:在这种情况下,你会得到一些重复,例如 KAOSOAKING。
  • @StephaneRolland 对制作文件比较工具、源代码控制软件、增量补丁等很有用。我自己好像没用过。
  • 在尝试将 2 个列表与反向项目合并时失败。 merge_sequences([1,2,3], [3,2,1]) 将在 [1,2,3,2,1] 结束。有什么建议吗?
【解决方案3】:

我怀疑您可能要求解决shortest common supersequence 问题,我认为在任意数量的输入序列的一般情况下,该问题是 NP 难的。我不知道有任何库可以解决此问题,因此您可能必须手动实现。获得工作代码的最快方法可能是使用 difflib 获取 interjay 的答案,然后使用reduce 在任意数量的列表上运行它(确保将空列表指定为reduce 的第三个参数)。

【讨论】:

  • 是的,wiki 定义似乎适合我的问题。感谢您指出正确的术语以及对两个以上输入序列使用 reduce 的说明。
【解决方案4】:

通过仅使用列表,您可以通过几个简单的for 循环和.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. list1 中的元素,位于两个公共元素之间
  3. list2 中的元素,位于两个公共元素之间
  4. 新的通用元素

例如:

l1 = ["A", "B", "C",      "E"]
l2 = ["A",           "D", "E"]
  1. 以前的公共元素A 将在合并列表中排在第一位。
  2. 之前公共元素 A 和新公共元素 E 之间来自 l1 的元素将插入到 A 之后。
  3. 来自l2 的元素在之前的公共elmeent A 和新的公共elmeent E 之间将插入到来自l1 的元素之后。
  4. 新的公共元素E 将是最后一个元素。
  5. 如果找到更多常见元素,则返回第 1 步。

    [“A”、“B”、“C”、“D”、“E”]

【讨论】:

  • 这些解决方案都不保留列表的顺序。第二种解决方案很接近,但可以更改第二个列表中元素的顺序。
  • 正如 interjay 所说:使用集合,不保留顺序。第二种解决方案不交错,而只是追加。
  • 修复了它,并删除了set 选项。
【解决方案5】:

我最近在实现一项功能时偶然发现了一个类似的问题。我试图首先明确定义问题陈述。如果我理解正确,这里是问题陈述

问题陈述

编写一个函数merge_lists,它将一个列表列表与重叠项目合并,同时保留项目的顺序。

约束

  1. 如果项目 A 在它们一起出现的所有列表中位于项目 B 之前,则项目 A 在最终列表中也必须位于项目 B 之前

  2. 如果项目 A 和项目 B 在不同的列表中互换顺序,即在某些列表中 A 在 B 之前,而在另一些列表中 B 在 A 之前,那么最终列表中 A 和 B 的顺序应该与它们的顺序相同在它们一起出现的第一个列表中排序。也就是说,如果在 l1 中 A 在 B 之前,在 l2 中 B 在 A 之前,那么在最终列表中 A 应该在 B 之前

  3. 如果项目 A 和项目 B 不在任何列表中同时出现,则它们的顺序必须由每个项目首先出现的列表位置决定。即如果item A在l1和l3,item B在l2和l6,那么最终list中的顺序一定是A then B

测试用例 1:

输入:

l1 = [“类型和尺寸”、“方向”、“材料”、“位置”、“正面打印类型”、“背面打印类型”]

l2 = [“类型和尺寸”,“材料”,“位置”,“正面打印类型”,“正面打印尺寸”,“背面打印类型”,“背面打印尺寸”]

l3 = [“方向”、“材料”、“位置”、“颜色”、“正面打印类型”]

merge_lists([l1,l2,l3])

输出:

['Type and Size', 'Orientation', 'Material', 'Locations', 'Color', 'Front Print Type', 'Front Print Size', 'Back Print Type', 'Back Print Size']

测试用例 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 ']

解决方案

我找到了一个合理的解决方案,可以正确解决我拥有的所有数据。 (对于其他一些数据集可能是错误的。将留给其他人评论)。这是解决方案

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)

【讨论】:

    【解决方案6】:

    这是我提出的一个 C# 解决方案——使用扩展方法——用于两个列表可能不包含相同类型元素的情况,因此它需要一个比较方法和一个选择器方法(返回一个对象给定源对象的目标类型)。在这种情况下,第一个列表(“me”)被修改为包含最终结果,但可以修改它以创建一个单独的列表。

    public static class ListExtensions
    {
        /// <summary>
        /// Merges two sorted lists containing potentially different types of objects, resulting in a single
        /// sorted list of objects of type T with no duplicates.
        /// </summary>
        public static void MergeOrderedList<TMe, TOther>(this List<TMe> me, IReadOnlyList<TOther> other, Func<TMe, TOther, int> compare = null, Func<TOther, TMe> selectT = null)
        {
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            if (compare == null)
            {
                if (typeof(TMe).GetInterfaces().Any(i => i == typeof(IComparable<TOther>)))
                {
                    compare = (a, b) => ((IComparable<TOther>)a).CompareTo(b);
                }
                else
                {
                    throw new ArgumentNullException(nameof(compare),
                        "A comparison method must be supplied if no default comparison exists.");
                }
            }
    
            if (selectT == null)
                if (typeof(TMe).IsAssignableFrom(typeof(TOther)))
                {
                    selectT = o => (TMe)(o as object);
                }
                else
                {
                    throw new ArgumentNullException(nameof(selectT),
                        $"A selection method must be supplied if the items in the other list cannot be assigned to the type of the items in \"{nameof(me)}\"");
                }
    
            if (me.Count == 0)
            {
                me.AddRange(other.Select(selectT));
                return;
            }
    
            for (int o = 0, m = 0; o < other.Count; o++)
            {
                var currentOther = other[o];
                while (compare(me[m], currentOther) < 0 && ++m < me.Count) {}
    
                if (m == me.Count)
                {
                    me.AddRange(other.Skip(o).Select(selectT));
                    break;
                }
    
                if (compare(me[m], currentOther) != 0)
                    me.Insert(m, selectT(currentOther));
            }
        }
    }
    

    注意:我确实为此编写了单元测试,所以它很可靠。

    【讨论】:

      【解决方案7】:

      假设问题是尽可能多的常见项目保持相同的相对顺序。

      考虑图的节点表示在相应列表中具有相同值的索引对m, n。例如。 [a, b, c] 和 [b, a, c] => [(0, 1), (1, 0), (2, 2)]

      两个节点m, nm', n'的相对顺序当且仅当(m &lt; m' and n &lt; n') or (m &gt; m' and n &gt; n')才能满足。在前面的示例中,(0, 1), (1, 0) 不满足此条件,因此不可能满足两个列表中 ab 的相对顺序。而(1, 0), (2, 2) 满足此属性,因此可以保留ac 的顺序。

      根据这个条件,找到所有节点对O(n^2)之间的边。要找到最佳排列,请使用 Bron–Kerbosch 算法找到最大的 clique(即 NP-complete O(3^(n/3)))。生成的相同值索引对可以用作锚点来生成合并列表。

      如果多项式解决方案可以接受近似排序,则以下方法使用联合查找(具有路径压缩和秩优化)来近似最大团并在O(n^2) 时间运行并占用O(n) 空间。

      from collections import defaultdict
      
      def find(cur, d):
          path = []
          while d[cur] != cur:
              path.append(cur)
              cur = d[cur]
          for n in path:
              d[n] = cur
          return cur
      
      def f(o, n):
          if o == n:
              return o
      
          first_list = list(reversed(o))
          second_list = list(reversed(n))
          first_list_dict = {v: i for i, v in enumerate(first_list)}
          common = []
          for i, v in enumerate(second_list):
              if v in first_list_dict:
                  common.append((first_list_dict[v], i))
          if not common:
              o.extend(n)
              return o
          if len(common) == len(first_list):
              return list({v: None for l in (o, n) for v in l})
          if len(common) == len(second_list):
              return o
      
      
          d = {p:p for p in common}
          rank = defaultdict(int)
          for i, p1 in enumerate(common):
              for p2 in common[i+1:]:
                  if (p1[0] - p2[0]) * (p1[1] - p2[1]) > 0:
                      h1, h2 = find(p1, d), find(p2, d)
                      if rank[h1] > rank[h2]:
                          h1, h2 = h2, h1
                      elif rank[h1] == rank[h2]:
                          rank[h2] += 1
                      d[h1] = h2
      
          freq = defaultdict(list)
          for p in common:
              freq[find(p, d)].append(p)
          largest_clique = sorted(max(freq.values(), key=len))
      
          res = []
          seen = set()
          l_idx1, l_idx2 = 0, 0
          while largest_clique:
              idx1, idx2 = largest_clique.pop()
              new = first_list[l_idx1-1:idx1:-1]
              new.extend(second_list[l_idx2-1:idx2:-1])
              new.append(first_list[idx1])
              res.extend(v for v in new if v not in seen)
              seen.update(new)
              l_idx1, l_idx2 = idx1, idx2
      
          return res
      
      
      for o, n in (
              [[0, 1, 2, 3, 4, 5], [5, 0, 1, 3, 4]],
              [[0, 1, 2, 3, 4, 5], [7, 3, 1, 2, 4, 5]],
              [[0, 1, 2, 3, 4, 5], [3, 4, 5, 0, 1, 2]],
              [["A", "B", "C", "E"], ["A", "D", "E"]],
              [["A", "B", "D", "F", "G", "H"], ["A", "C", "D", "E", "F", "H"]],
              [[0, 1, 2, 3, 4], [5, 6, 7, 8]],
              ):
          print(f"{str(o): <30}, {str(n): <30} => {str(f(o, n)): >40}")
      

      给予:

      [0, 1, 2, 3, 4, 5]            , [5, 0, 1, 3, 4]                =>                       [0, 1, 2, 3, 4, 5]
      [0, 1, 2, 3, 4, 5]            , [7, 3, 1, 2, 4, 5]             =>                    [0, 7, 3, 1, 2, 4, 5]
      [0, 1, 2, 3, 4, 5]            , [3, 4, 5, 0, 1, 2]             =>                       [0, 1, 2, 3, 4, 5]
      ['A', 'B', 'C', 'E']          , ['A', 'D', 'E']                =>                ['A', 'B', 'C', 'D', 'E']
      ['A', 'B', 'D', 'F', 'G', 'H'], ['A', 'C', 'D', 'E', 'F', 'H'] => ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
      [0, 1, 2, 3, 4]               , [5, 6, 7, 8]                   =>              [0, 1, 2, 3, 4, 5, 6, 7, 8]
      

      【讨论】:

        猜你喜欢
        • 2010-12-05
        • 1970-01-01
        • 2010-10-03
        • 2018-11-21
        • 2013-11-21
        • 2017-02-09
        • 2021-05-02
        • 2012-07-21
        相关资源
        最近更新 更多