【问题标题】:Delete an entry from an nested list if two entries at the same index are identical [duplicate]如果同一索引处的两个条目相同,则从嵌套列表中删除一个条目[重复]
【发布时间】:2020-02-05 22:19:08
【问题描述】:

我有一个嵌套列表,如下所示:

l = [['h', 'i', 'l'],
     ['m', 'b', 'x'],
     ['u', 'o', 'l'],
     ['f', 'j', 'x']]

如您所见,索引 2 处的条目对于子列表 1 和 3 以及子列表 0 和 2 是相同的。

如何删除在特定索引处具有相同条目的子列表?

预期结果:

nl = [['h', 'i', 'l'],
      ['m', 'b', 'x']]

我尝试使用set() 函数,但没有成功。

有人可以帮忙吗?

【问题讨论】:

  • 如果输入是这样的会发生什么 l = [['h', 'i', 'l'], ['m', 'i', 'x'], ['u ', 'o', 'l'], ['f', 'j', 'x'] ] 'i' 在第二个列表中重复
  • 嗨@pavanskipo,我基本上只是想比较索引2处的条目。
  • 啊,好吧,正在考虑算法垂直获取列表,例如 ['h', 'm', 'u', 'f'], ['i', 'b', 'o' , 'j'], ['l', 'x', 'l', 'x'] 然后删除重复项

标签: python list


【解决方案1】:

这里是单行解决方案:

[l[i] for i in sorted({e[2]: i for i, e in zip(range(len(l) - 1, -1, -1), l[::-1])}.values())]

输出:

[['h', 'i', 'l'], 
 ['m', 'b', 'x']]

这是一个 for 循环解决方案:

uniq_value = set()
final_list = []
for e in l:
    if e[2] not in uniq_value:
        uniq_value.add(e[2])
        final_list.append(e)

final_list

输出:

[['h', 'i', 'l'], 
 ['m', 'b', 'x']]

我一直在对@FilipMłynarski、@Exa 和我的解决方案进行基准测试:

from simple_benchmark import BenchmarkBuilder
from random import choice
from string import ascii_letters
import numpy as np

b = BenchmarkBuilder()

def _filip(x):
    stack = [set() for _ in x[0]]
    for row in x:
        if all(i not in seen for i, seen in zip(row, stack)):
            for idx, i in enumerate(row):
                stack[idx].add(i)
            yield row

@b.add_function()            
def filip(l):
    return list(_filip(l))

@b.add_function()
def exa(l):
    t = []
    for sl in l:
        for st in t:
            for (v, o) in zip(sl, st):
                if v == o:
                    break
            else:
                continue
            break
        else:
            t.append(sl)
    return t

@b.add_function()
def kederrac(l):
    uniq_value = set()
    final_list = []
    for e in l:
        if e[2] not in uniq_value:
            uniq_value.add(e[2])
            final_list.append(e)

    return final_list

@b.add_function()
def exa_fix_index(l):
    t = []
    for sl in l:
        for st in t:
            if sl[2] == st[2]:
                break
            else:
                continue
            break
        else:
            t.append(sl)
    return t

@b.add_function()
def mathfux(array, idx=2):
    array = np.array(array)
    ith_column = array[:, idx]
    u, good_indices = np.unique(ith_column, return_index=True)
    new_array = array[good_indices]
    new_array = new_array.tolist() #if you need to convert back to list
    return new_array

@b.add_arguments('Size of list of lists')
def argument_provider():
    for exp in range(2, 7):
        size = 10**exp
        list_of_lists = [[choice(ascii_letters) for _ in range(3)] for _ in range(size)]
        yield size,  list_of_lists

r = b.run()
r.plot()

输出:

【讨论】:

  • 我在答案中添加了一个特定索引的示例,为了保持一致性,请随意对您的函数进行基准测试。
  • @Exa 我刚刚添加了您的修复索引解决方案
【解决方案2】:

仅仅使用set() 是行不通的,因为您要删除具有至少一个重复元素的条目,不是整个列表。我们可以创建一个函数来跟踪我们希望在未来避免的可见元素(这里是集合列表stack),同时迭代提供的列表,到具有所有值的yield元素独一无二。

l = [
    ['h', 'i', 'l'],
    ['m', 'b', 'x'],
    ['u', 'o', 'l'],
    ['f', 'j', 'x'],
]

def fix(x):
    stack = [set() for _ in x[0]]
    for row in x:
        if all(i not in seen for i, seen in zip(row, stack)):
            for idx, i in enumerate(row):
                stack[idx].add(i)
            yield row

print(list(fix(l)))  # -> [['h', 'i', 'l'], ['m', 'b', 'x']]

【讨论】:

  • 嗨@Filip,感谢您的回答。所以您的解决方案还会删除索引 0 和 1 处相同的元素?
  • 是的,没错
【解决方案3】:

在我看来,numpy 是在您的情况下最简单的方法:

def delete_by_index(array, idx):
    array = np.array(array)
    ith_column = array[:, idx]
    u, good_indices = np.unique(ith_column, return_index=True)
    new_array = array[good_indices]
    new_array = new_array.tolist() #if you need to convert back to list
    return new_array

print(delete_by_index(l, 2)) #prints expected result

我在那里做了两件重要的事情:

  • 提取ith_column,即['l', 'x', 'l', 'x'],用于i=2
  • 使用np.unique 方法返回(排序的)唯一项目['l', 'x'] 及其索引[0, 1] 根据these docs

效率如何,numpy 的使用应该比使用 for 循环更快,因为它的所有操作都执行用 C 编写的循环。

【讨论】:

    【解决方案4】:

    虽然 Filip 的答案看起来很酷,但它有点太复杂了 - 正如我们所发现的那样 - 相当缓慢。只需跟踪所有唯一列表并在每次迭代时扫描它们以确定我们当前的列表是否有效(具有统一的唯一元素),就可以了。

    def fix(l):
        t = []
        for sl in l:
            for st in t:
                for (v, o) in zip(sl, st):
                    if v == o:
                        break
                else:
                    continue
                break
            else:
                t.append(sl)
        return t
    

    这在~2.2946 执行,而 Filip 根据timeit.timeit 命中~6.1039(终端中的复制粘贴解决方案):

    >>> timeit.timeit(lambda: fix(l)) # mine
    2.3752248109999528
    >>> timeit.timeit(lambda: list(fix(l))) # Filip
    6.073348348000309
    

    对于具体的指标,一些调整给了我们:

    def fix(l, i):
        t = []
        for sl in l:
            for st in t:
                if sl[i] == st[i]:
                    break
                else:
                    continue
                break
            else:
                t.append(sl)
        return t
    

    基准第三指数:

    >>> timeit.timeit(lambda: fix(l, 2))
    0.9275596050001695
    

    正如预期的那样,当您知道要摆脱什么时,速度会快得多。

    为避免在处理大量数据时出现不必要的内存开销,请在 t.append(sl) 上添加 yield sl 并删除 return 语句(在所有示例中)。

    【讨论】:

    • 嗨@Exa,感谢您的回答。所以您的解决方案还会删除索引 0 和 1 处相同的元素?
    • @Exa 如果您声称您的答案比 Filip 的更快,建议提供有关您如何得出结论的详细信息,即您的答案比他的 timeit 分析或您所做的任何事情都快这个结论。
    • Heya @gython,是的,这将删除所有后续列表,其元素等于最后一次看到的列表的任何索引,在任何索引处(如果您想要更具体的内容,请告诉我)。并为不包括时间证明而道歉,现在就这样做了。
    • @Exa 我一直在将您的解决方案与 Filip 和我的解决方案进行对比,您可以查看我的答案
    • @kederrac 奇怪的是 timeit 和 benchmark 之间的结果不一致。此外,您的函数与我和 Filip 的函数不同,因为它只检查一个索引。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-19
    • 2022-01-17
    • 2022-06-16
    • 1970-01-01
    • 2020-06-29
    相关资源
    最近更新 更多