【问题标题】:Finding clusters of numbers in a list在列表中查找数字簇
【发布时间】:2013-04-04 01:06:51
【问题描述】:

我正在为此苦苦挣扎,因为我确信十几个 for 循环不是解决这个问题的方法:

有一个排序的数字列表,如

numbers = [123, 124, 128, 160, 167, 213, 215, 230, 245, 255, 257, 400, 401, 402, 430]

我想创建一个带有数字列表的字典,其中数字的差异(彼此跟随)不超过 15。所以输出将是这样的:

clusters = {
    1 : [123, 124, 128],
    2 : [160, 167],
    3 : [213, 215, 230, 245, 255, 257],
    4 : [400, 401, 402],
    5 : [430]
}

我目前的解决方案有点难看(我必须在最后删除重复项......),我确信它可以以 python 的方式完成。

这就是我现在所做的:

clusters = {}  
dIndex = 0 
for i in range(len(numbers)-1) :
    if numbers[i+1] - numbers[i] <= 15 :
        if not clusters.has_key(dIndex) : clusters[dIndex] = []
        clusters[dIndex].append(numbers[i])
        clusters[dIndex].append(numbers[i+1])
    else : dIndex += 1

【问题讨论】:

标签: python list


【解决方案1】:

如果您的列表很小,则不是绝对必要的,但我可能会以“流处理”方式处理此问题:定义一个生成器,该生成器将您的输入可迭代,并产生分组为由

def grouper(iterable):
    prev = None
    group = []
    for item in iterable:
        if prev is None or item - prev <= 15:
            group.append(item)
        else:
            yield group
            group = [item]
        prev = item
    if group:
        yield group

numbers = [123, 124, 128, 160, 167, 213, 215, 230, 245, 255, 257, 400, 401, 402, 430]
dict(enumerate(grouper(numbers), 1))

打印:

{1: [123, 124, 128],
 2: [160, 167],
 3: [213, 215, 230, 245, 255, 257],
 4: [400, 401, 402],
 5: [430]}

作为奖励,您甚至可以将运行分组为可能无限的列表(当然,只要它们已排序)。您还可以将索引生成部分粘贴到生成器本身中(而不是使用enumerate)作为一个小的增强功能。

【讨论】:

  • 哇,真的很酷的功能!我就是为了那个。谢谢@tzaman
  • @wjandrea 不错,已修复!
【解决方案2】:
import itertools
import numpy as np

numbers = np.array([123, 124, 128, 160, 167, 213, 215, 230, 245, 255, 257, 400, 401, 402, 430])
nd = [0] + list(np.where(np.diff(numbers) > 15)[0] + 1) + [len(numbers)]

a, b = itertools.tee(nd)
next(b, None)
res = {}
for j, (f, b) in enumerate(itertools.izip(a, b)):
    res[j] = numbers[f:b]

如果你可以使用 itertools 和 numpy.为迭代器技巧改编 pairwise。需要+1 来移动索引,将0len(numbers) 添加到列表中以确保正确包含第一个和最后一个条目。

你显然可以在没有itertools 的情况下做到这一点,但我喜欢tee

【讨论】:

    【解决方案3】:

    您可以使用 numpy / pandas 在没有(显式)循环的情况下实现这一点:

    import pandas as pd    
    import numpy as np
    
    n = 15
    numbers = [123, 124, 128, 160, 167, 213, 215, 230, 245, 255, 257, 400, 401, 402, 430]
    nnumbers = np.array(numbers)
    clusters = pd.DataFrame({
        'numbers': numbers,
        'segment': np.cumsum([0] + list(1*(nnumbers[1:] - nnumbers[0:-1] > n))) + 1
    }).groupby('segment').agg({'numbers': set}).to_dict()['numbers']
    

    诀窍是移动数字列表并将差异与您的阈值 (15) 进行比较,以找到段之间的“中断”。当然,第一个元素不会是中断。然后使用 cumsum 函数获取分段并使用 set 函数进行分组(以防有重复项)。 希望这对您有所帮助,即使自发布此问题以来已经过去多年。

    【讨论】:

      【解决方案4】:

      使用生成器分离逻辑:(一个函数做一件事)

      numbers = [123, 124, 128, 160, 167, 213, 215, 230, 245, 255, 257, 400, 401, 402, 430]
      
      def cut_indices(numbers):
          # this function iterate over the indices that need to be 'cut'
          for i in xrange(len(numbers)-1):
              if numbers[i+1] - numbers[i] > 15:
                  yield i+1
      
      def splitter(numbers):
          # this function split the original list into sublists.
          px = 0
          for x in cut_indices(numbers):
              yield numbers[px:x]
              px = x
          yield numbers[px:]
      
      def cluster(numbers):
          # using the above result, to form a dict object.
          cluster_ids = xrange(1,len(numbers))
          return dict(zip(cluster_ids, splitter(numbers)))
      
      print cluster(numbers)
      

      以上代码给我

      {1: [123, 124, 128], 2: [160, 167], 3: [213, 215, 230, 245, 255, 257], 4: [400, 401, 402], 5: [430]}
      

      【讨论】:

        【解决方案5】:

        这是一个适用于列表或生成器的相对简单的解决方案。它懒惰地产生对(group_number, element),因此如果需要,您必须单独进行实际分组。 (或者也许你只需要组号。)

         from itertools import tee
        
         def group(xs, gap=15):
            # use `tee` to get two efficient iterators
            xs1, xs2 = tee(xs)
        
            # the first element is in group 0, also advance the second iterator
            group = 0
            yield (group, next(xs2))
        
            # after advancing xs2, this zip is pairs of consecutive elements
            for x, y in zip(xs1, xs2):
                # whenever the gap is too large, increment the group number
                if y - x > gap:
                    group += 1
                # and yield the second number in the pair
                yield group, y
        

        【讨论】:

          猜你喜欢
          • 2020-09-24
          • 2015-01-21
          • 2017-07-15
          • 2020-05-02
          • 2021-05-17
          • 1970-01-01
          • 2014-11-01
          • 2017-10-07
          相关资源
          最近更新 更多