【问题标题】:How to get rid of sub-tuples in this list?如何摆脱此列表中的子元组?
【发布时间】:2023-04-03 14:42:01
【问题描述】:
list_of_tuple = [(0,2), (0,6), (4,6), (6,7), (8,9)]

由于(0,2)(4,6) 都在(0,6) 的索引内,所以我想删除它们。结果列表将是:

list_of_tuple = [(0,6), (6,7), (8,9)]

看来我需要以某种方式对这个列表元组进行排序,以便更容易删除。但是如何对元组列表进行排序?

给定两个数组索引元组,[m,n][a,b],如果:

m >=a & n<=b

然后[m,n] 包含在[a,b] 中,然后从列表中删除[m,n]

【问题讨论】:

  • 您用来删除它们的确切标准是什么?似乎您将它们视为间隔,对吗?那么,如果任何间隔包含在另一个间隔中,它应该被删除吗?你可以很容易地在 O(n^2) 时间内使用 range 做到这一点。
  • 是的。但是怎么做呢?我需要先排序吗?

标签: python list


【解决方案1】:

似乎是滥用reduce() 和Python 的逻辑运算符的机会!解决方案假定列表按照 OP 的示例进行排序,主要在每个元组的第二个元素上排序,其次是在第一个元素上:

from functools import reduce

list_of_sorted_tuples = [(0, 2), (0, 6), (4, 6), (6, 7), (8, 9)]

def contains(a, b):
    return a[0] >= b[0] and a[1] <= b[1] and [b] or b[0] >= a[0] and b[1] <= a[1] and [a] or [a, b]

reduced_list = reduce(lambda x, y: x[:-1] + contains(x[-1], y) if x else [y], list_of_sorted_tuples, [])

print(reduced_list)

输出

> python3 test.py
[(0, 6), (6, 7), (8, 9)]
>

【讨论】:

    【解决方案2】:

    list_of_tuples 中删除所有超出指定元组范围的元组:

    list_of_tuple = [(0,2), (0,6), (4,6), (6,7), (8,9)]
    
    def rm(lst,tup):
        return [tup]+[t for t in lst if t[0] < tup[0] or t[1] > tup[1]]
    
    print(rm(list_of_tuple,(0,6)))
    

    输出:

    [(0, 6), (6, 7), (8, 9)]
    

    【讨论】:

    • 我发现这两种解决方案都有问题。第一个不比较 anything,它只是将元组扔到列表中任意选择的元组的任一侧。第二个使用理解做同样的事情。两者都忽略了从列表中删除元组时 OP 的标准公式。
    • 安,OP声明如果存在另一个元组(a,b),则删除元组(m,n),例如m &gt;= a and n &lt;= b。有些人将此解释为适用于随机列表,但由于 OP 的列表已经以某种方式排序,我选择将其解释为给定的 - 一个未经证实的假设。
    • 不,您只查看元组的一个元素,但都必须考虑。此外,您的答案是唯一将列表元素作为参数的答案 - 也许您正在了解我们其他人所缺少的东西。
    【解决方案3】:

    这是一个非常简单的解决方案,但它是 O(n2):

    intervals = [(0, 2), (0, 6), (4, 6), (6, 7), (8, 9)]  # list_of_tuple
    result = [
        t for t in intervals
        if not any(t != u and t[0] >= u[0] and t[1] <= u[1] for u in intervals)
        ]
    

    它过滤掉不等于但包含在任何其他间隔中的间隔。

    【讨论】:

    • 它实际上比声称的要好。如果元组相等,它也会删除重复项。 @wjandrea
    • @JeffH 它不会删除重复项。例如,如果您添加另一个(0, 6),它将包含在结果中。如果要删除重复项,只需先将intervals 转换为集合即可。
    • @awarrier99 不,你不能依赖于不可变类型的身份,因为它们可以被实习。例如,我刚刚在 CPython 3.7 上运行 (0, 6) is (0, 6) 并得到了 True
    • 是的。你是对的。我的测试用例与其他解决方案交叉授粉。我想知道为什么它似乎在拉重复。 :)
    • @awarrier99 我曾经看到一个很好的答案,但现在找不到。如果我没记错的话,在 REPL 上,单行编译在一起,不可变对象存储为同一个对象,但单独的行不是。
    【解决方案4】:

    您可以尝试这样的方法来检查(半开)区间的两端是否包含在另一个区间内:

    list_of_tuple = [(0,2), (0,6), (4,6), (6,7), (8,9)]
    reduced_list = []
    for t in list_of_tuple:
        add = True
        for o in list_of_tuple:
            if t is not o:
                r = range(*o)
                if t[0] in r and (t[1] - 1) in r:
                    add = False
    
        if add:
            reduced_list.append(t)
    
    print(reduced_list) # [(0, 6), (6, 7), (8, 9)]
    

    注意:这假定您的元组是半开区间,即 [0, 6) 其中 0 包含但 6 是排除,类似于 range 将如何处理 startstop 参数。对于完全闭区间的情况,必须进行一些小的更改:

    range(*o) -> range(o[0], o[1] + 1)

    if t[0] in r and (t[1] - 1) in r: -> if t[0] in r and t[1] in r:

    【讨论】:

      【解决方案5】:

      这是实现可以在 O(n log(n)) 内完成的解决方案的第一步:

      def non_cont(lot):
          s = sorted(lot, key = lambda t: (t[0], -t[1]))
          i = 1
          while i < len(s):
              if s[i][0] >= s[i - 1][0] and s[i][1] <= s[i - 1][1]:
                  del s[i]
              else:
                  i += 1
          return s
      

      这个想法是,在使用特殊键功能进行排序后,包含在某个其他元素中的每个元素将直接位于包含它的元素之后。然后,我们扫描列表,删除它们之前的元素所包含的元素。现在,扫描和删除循环本身的复杂度为 O(n^2)。上述解决方案是为了清楚起见,而不是其他任何事情。我们可以转到下一个实现:

      def non_cont_on(lot):
          s = sorted(lot, key = lambda t: (t[0], -t[1]))
          i = 1
          result = s[:1]
          for i in s:
              if not (i[0] >= result[-1][0] and i[1] <= result[-1][1]):
                  result.append(i)
          return result
      

      这里没有二次扫描和删除循环,只有构造结果的一个很好的线性过程。空间复杂度为 O(n)。可以在没有额外的非常量空间的情况下执行此算法,但我将省略它。

      这两种算法的副作用是对区间进行排序。

      【讨论】:

        【解决方案6】:

        如果您想保留有关包含结构的信息(通过哪个封闭间隔消耗原始集合的间隔),您可以构建“一级树”:

        def contained(tpl1, tpl2):
            return tpl1[0] >= tpl2[0] and tpl1[1] <= tpl2[1] 
        
        def interval_hierarchy(lst):
            if not lst:
                return
            root = lst.pop()
            children_dict = {root: []}
            while lst:
                t = lst.pop()
                curr_children = list(children_dict.keys())
                for k in curr_children:
                    if contained(k, t):
                        children_dict[t] = (children_dict[t] if t in children_dict else []) +\
                                           [k, *children_dict[k]]
                        children_dict.pop(k)
                    elif contained(t, k):
                        children_dict[k].append(t)
                        if t in children_dict:
                            children_dict[k] += children_dict[t]
                            children_dict.pop(t)
                    else:
                        if not t in children_dict:
                            children_dict[t] = []
            # return whatever information you might want to use
            return children_dict, list(children_dict.keys())
        

        【讨论】:

          【解决方案7】:

          您似乎正在尝试合并重叠的间隔。例如,(9,11), (10,12) 在下面的第二个例子中合并生成 (9,12)。

          在这种情况下,使用sorted 的简单排序将自动处理元组。

          方法:存储下一个要添加的区间。继续延长间隔的结尾,直到遇到一个值,其“开始”位于 (>=) 下一个要添加的值的“结束”之后。此时,存储的下一个间隔可以附加到结果中。在末尾追加以说明处理所有值。

          def merge_intervals(val_input):
              if not val_input:
                  return []
              vals_sorted = sorted(val_input)   # sorts by tuple values "natural ordering"
              result = []
              x0, x1 = vals_sorted[0]           # store next interval to be added as (x0, x1)
              for start, end in vals_sorted[1:]:
                  if start >= x1:               # reached next separate interval
                      result.append((x0, x1))
                      x0, x1 = (start, end)
                  elif end > x1:
                      x1 = end                  # extend length of next interval to be added
              result.append((x0, x1))
              return result
          
          print(merge_intervals([(0,2), (0,6), (4,6), (6,7), (8,9)]))
          print(merge_intervals([(1,2), (9,11), (10,12), (1,7)]))
          
          

          输出:

          [(0, 6), (6, 7), (8, 9)]
          [(1, 7), (9, 12)]
          

          【讨论】:

          • (9,11)(10,12) 的合并似乎不符合 OP 的 a[0] &gt;= b[0] and a[1] &lt;= b[1] 减少元组的标准。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-10-29
          • 2015-01-02
          • 1970-01-01
          • 1970-01-01
          • 2021-04-19
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多