如何在Python中获取值满足某些条件的列表的索引范围?

时间:2015-06-23 03:08:33

标签: python list

例如,a = [12,11,5,7,2,21,32,13,6,42,1,8,9,0,32,38],我想找到那个指数范围它中的值小于10.是否有一种简单的方法可以返回[[2,4],[8],[10,13]]的索引范围?

我写了一个函数,但觉得它很乏味:

def indexscope(dlist):
    newinterval = True
    scope = []
    for i in range(len(dlist)):
        if dlist[i] < 10:
            if newinterval:
                interval = [i]
                newinterval = False
            else:
                k = i
        else:
            if not newinterval:
                interval[1] = k + 1
                scope.append(interval)
                newinterval = True
    return scope

对于上面提到的例子,我可以使用indexscope(a)来获得我的结果。

但是如果我想获得另一个需要值&gt;的索引范围呢? 20下次?丑陋写了另一个功能? 是否有一种简单的方法可以将索引范围提升到一般水平?

更新

1.是的,正如你们中的一些人指出的那样,我需要的是指数范围。我稍后将处理每个索引范围内的元素。 让每个连续元素保持在相同范围非常重要。

因此,[[2,4],[8],[10,13]]和[[2,3,4],[8],[10,11,12,13]]的返回值可以都被接受了。

2.目前,@ TigerhawkT3的答案可以满足我在我的例子中的要求。 @ DTing的回答引起了我的进一步要求。但我仍然找到一种更简单的方式。

UPDATE2

3.我终于得到了两行答案,请参阅@ TigerhawkT3的答案以及互联网上的其他一些答案。现在我可以放弃功能定义,只需替换我需要的标准。谢谢大家帮助我的努力。

l = [idx for idx,value in enumerate(a) if value<10]
print [list(g) for _,g in groupby(l,key=lambda n,c=count():n-next(c))]

5 个答案:

答案 0 :(得分:2)

您可以让函数将函数作为参数用作构建区间的谓词:

def indexscope(dlist, predicate):
    scope = []
    start = end = -1
    for i, v in enumerate(dlist):
        if predicate(v):
            if start == -1:
                start = end = i
                continue
            if end + 1 == i:
                end = i
            else:
                scope.append([start] if start == end else [start, end])
                start = end = i
    if start != -1: 
        scope.append([start] if start == end else [start, end])
    return scope

a = [12,11,5,7,2,21,32,13,6,42,1,8,9,0,32,38]

def less_than_10(n):
    return n < 10

print(indexscope(a, less_than_10))
print(indexscope(a, lambda x: x > 20))


[[2, 4], [8], [10, 13]]
[[5, 6], [9], [14, 15]]

与scipy:

import numpy as np
import scipy.ndimage as nd

def passing_ranges(a, predicate):
    return nd.find_objects(nd.label(predicate(a))[0])

结果以slice个对象的形式返回,但这对您有利,因为您可以将它们用于原始的np数组:

small_a = [12,11,5,7,2,21,32,13,6,42,1,8,9,0,32,38]
small_np_array = np.array(small_a)

valid_ranges = passing_ranges(small_np_array, lambda n: n < 10)

for r in valid_ranges:
    print(r[0], small_np_array[r])

slice(2, 5, None) [5 7 2]
slice(8, 9, None) [6]
slice(10, 14, None) [1 8 9 0]

<强>基准

large_a = [12,11,5,7,2,21,32,13,6,42,1,8,9,0,32,38]*1000000
large_np_array = np.array(large_a)

%timeit passing_ranges(large_np_array, lambda x: x < 10)
1 loops, best of 3: 1.2 s per loop

%timeit indexscope(large_a, lambda n: n < 10)
1 loops, best of 3: 6.99 s per loop

这是你的答案,我甚至内联谓词来删除一个函数调用:

from itertools import groupby, count

def xibinke(a):
    l = [idx for idx,value in enumerate(a) if value<10]
    return [list(g) for _,g in groupby(l,key=lambda n,c=count():n-next(c))]

%timeit xibinke(large_a)
1 loops, best of 3: 14.6 s per loop

答案 1 :(得分:2)

a = [12,11,5,7,2,21,32,13,6,42,1,8,9,0,32,38]
indices = [idx for idx,val in enumerate(a) if val < 10]

这会创建list个索引:

[2, 3, 4, 8, 10, 11, 12, 13]

我建议保持这种方式以便于解析,但您也可以按如下方式将其转换为范围:

ranges = [[]]
for val in indices:
    if not ranges[-1] or ranges[-1][-1] == val-1:
        ranges[-1].append(val)
    else:
        ranges.append([val])

这会创建list范围:

[[2, 3, 4], [8], [10, 11, 12, 13]]

现在取出中间位置:

ranges = [[item[0],item[-1]] if len(item) > 1 else item for item in ranges]

结果:

[[2, 4], [8], [10, 13]]

答案 2 :(得分:1)

如果您准备使用numpy,要获取所有元素的索引,在numpy库中有一个名为numpy.nonzero()的简单函数,您必须传入要检查的条件。

示例 -

In [1]: import numpy as np

In [2]: n = np.array([11,23,4,5,1222,33,6,10])

In [6]: ni = np.nonzero(n < 10)
Out[6]: (array([2, 3, 6]),)

In [7]: ni[0]
Out[7]: array([2, 3, 6])

在此之后,返回你想要的索引的第一个元素。

如果您只是想要满足特定条件的值,那么numpy库中还有另一个非常简单的解决方案可以满足您的需求,使用的函数称为 - numpy.where,请注意这将给出值那些,而不是索引。例子 -

import np
a = [12,11,5,7,2,21,32,13,6,42,1,8,9,0,32,38]
npa = np.array(a)
np.where(a < 10)
>> array([5,7,2,6,1,8,9,0])

答案 3 :(得分:1)

获取无效索引的单行代码,并按案例生成范围

# get invalid indices
l = [idx for idx, value in enumerate(dlist) if value<10]

scope = []
for value in l:
    if scope and scope[-1][-1] == value-1:
        scope[-1] = scope[-1][0:1] + [value]
    else:
        scope.append([value])
# scope is exactly what you wanna

具有lambda功能的单行代码:

scope = []
l = [idx for idx, value in enumerate(dlist) if value<10]

# output like [[2, 4], [8], [10, 13]]:
map(lambda x: (len(scope[-1]) == 1 or scope[-1].pop(-1)) and scope[-1].append(
    x) if scope and scope[-1][-1] == x - 1 else scope.append([x]), l)

# output like [[2,3,4],[8],[10,11,12,13]]
map(lambda x: scope[-1].append(x) if scope and 
    scope[-1][-1] == x - 1 else scope.append([x]), l)

答案 4 :(得分:0)

参考@ TigerhawkT3的回答,并在互联网上搜索,我得到了这个答案:

from itertools import groupby,count
a = [12,11,5,7,2,21,32,13,6,42,1,8,9,0,32,38]
l = [idx for idx,value in enumerate(a) if value<10]
print [list(g) for _,g in groupby(l,key=lambda n,c=count():n-next(c))]

它给出了范围列表:

[[2, 3, 4], [8], [10, 11, 12, 13]]