27

我有两个列表,比方说:

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

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

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

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

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

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

如果我可以一口气完成三个列表,则可以加分。

4

7 回答 7

20

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

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

于 2013-01-09T18:23:17.660 回答
2

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

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

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

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

于 2013-01-09T16:07:33.623 回答
1

通过仅使用列表,您可以通过几个简单的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列表,将元素后面的所有元素附加previouslist2.
  • 然后将新匹配存储到previouselements重置为[]并继续循环。
  • 如果第一个或最后一个元素不是两个列表中的公共元素,则列表的开头和列表的结尾被计为公共元素。

这样,它将始终遵循以下格式:

  1. 上一个共同元素
  2. list1 中的元素,位于两个公共元素之间
  3. list2 中的元素,位于两个常见元素之间
  4. 新的共同元素

例如:

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

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

于 2013-01-09T18:55:57.490 回答
0

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

问题陈述

编写一个函数 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. 如果 Item A 和 Item B 在任何列表中都不一起出现,则它们的顺序必须由每个列表中最先出现的位置决定。也就是说,如果项目A在l1和l3中,项目B在l2和l6中,那么最终列表中的顺序必须是A然后B

测试用例 1:

输入:

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

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

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

合并列表([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”]

合并列表([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”]

合并列表([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)
于 2018-04-04T12:48:46.107 回答
0

这是我提出的一个 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));
        }
    }
}

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

于 2020-05-14T18:25:58.047 回答
0

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

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

两个节点的相对顺序m, nm', n'可以满足当且仅当(m < m' and n < n') or (m > m' and n > n')。在前面的示例(0, 1), (1, 0)中,不满足此条件,因此不可能满足两个列表中a和的相对顺序。b(1, 0), (2, 2)满足这个性质,因此可以保持 和 的a顺序c

基于这个条件,找到所有节点对之间的边O(n^2)。要找到最佳排列,请使用 Bron-Kerbosch 算法找到最大的最大(即 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]
于 2021-12-11T12:09:47.747 回答