查找成对元素的索引

时间:2017-04-26 09:15:12

标签: python indexing tuples pairwise

给定目标('b', 'a')和输入:

x0 = ('b', 'a', 'z', 'z')
x1 = ('b', 'a', 'z', 'z')
x2 = ('z', 'z', 'a', 'a')
x3 = ('z', 'b', 'a', 'a')

目的是找到连续('b', 'a')元素的位置并获得输出:

>>> find_ba(x0)
0
>>> find_ba(x1)
0
>>> find_ba(x2)
None
>>> find_ba(x3)
1

使用pairwise食谱:

from itertools import tee
def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

我可以这样做以获得所需的输出:

def find_ba(x, target=('b', 'a')):
    try:
        return next(i for i, pair in enumerate(pairwise(x)) if pair == target)
    except StopIteration:
        return None

但这需要我循环遍历所有字符对,直到找到第一个实例。 有没有办法在不循环所有字符的情况下找到成对元素的索引?

在评论中回答@MatthiasFripp的问题:

  

您的元素是列表或类型(如图所示)还是生成器(例如从文件句柄中读取)?

x *是字符串的所有元组。所以他们可以通过索引访问。但如果答案/解决方案可以用于元组和生成器,那就太棒了!

  

您能说出您需要搜索的列表数量以及它们的持续时间吗?这有助于建议搜索策略。

元组的长度不固定。它们的尺寸可以是> 2.

14 个答案:

答案 0 :(得分:13)

最快的一般搜索算法将具有O(n)平均性能(称为线性搜索),这意味着除了处理每个元素之外,您没有其他选择(除了可能是常数因子)。

鉴于你的问题:

  

有没有办法在不循环所有字符的情况下找到成对元素的索引?

只有查看每个第二项才有可能(它仍然是O(n)):

from itertools import count

def find_ab(tup):
    for idx in count(start=1, step=2):
        try:
            if tup[idx] == 'b':
                if tup[idx+1] == 'a':
                    return idx
            elif tup[idx] == 'a':
                if tup[idx-1] == 'b':
                    return idx-1
        except IndexError:
            break

在最糟糕的情况下,它仍会比较所有项目,但对于不是'b''a'的每个奇数索引项目,它会跳过一项。

这有点像作弊,所以让我解释一下为什么常见的替代品在你的情况下是不可能的:

二进制搜索

二进制搜索只需要比较log(n)项,但它需要对序列进行排序。您的示例未进行排序,因此对它们进行排序将需要O(n*log(n))次操作 - 这不仅会处理每个项目一次,而是会多次处理其中一些项目。并不是说我知道一种合理的方法来对相邻元素进行排序。

存储桶搜索(或哈希表)

你有元组所以创建一个哈希表(一个dict)是没有意义的,因为为了创建那个结构,你需要处理每个元素。

但是,如果您计划对这些对进行多次搜索,则可以创建一次字典(O(n)),然后在O(1)中进行多次搜索:

d = {}
for idx, pair in enumerate(pairwise(x0)):
    if pair not in d:    # keep only the first index for each pair
        d[pair] = idx

>>> d.get(('b', 'a'), None)
0

然而,如果您只想搜索一个对,那么这种方法要慢得多,因为您失去了“短路行为”(一旦找到匹配就停止)并且您处理所有创建字典时的元素。

其他方法

除了一般方法:

  • O(n)线性搜索
  • O(log(n))二进制搜索(对于排序数据)
  • O(1)查找(针对可浏览的查找或其他需要在某些“存储桶”中搜索的搜索问题)

您通常可以利用有关数据的任何结构或知识来减少需要处理的项目数量。问题主要在于(可能)没有已经存在的数据结构,而且自制的实现通常比天真的“处理所有元素”方法慢几个数量级。但是,如果您有关于序列的任何元信息,那么您可以利用它。

最后的评论

成对的配方实际上相当不错,但您也可以使用iteration_utilities.successive 1 。最后我检查它比食谱大约快1.5到2倍。即使您没有更改方法并接受在最坏的情况下您需要处理所有(或几乎所有)元素,可能更快!

可能已生成该数据。也许在创作过程中实际“搜索”元素是值得的。这样你根本不需要对数据进行额外的传递。或者您可以在创建数据集时创建dict(之后允许O(1)查找)。如果有某种方式可以提取信息,有时候查看生成/下载/获取数据集的过程是个好主意。

现在,在写完所有这些文字后,我需要说清楚:

你的方法非常好。即使它需要在最坏的情况下处理所有元素,它也会针对手头的问题使用完美的拟合(pairwise - 配方),即使对于长输入,它实际上也应该非常快。对于包含100万'z'的元组,我的计算机上只需要200毫秒。因此,您可以每秒处理数百万个元素(即使是像我这样的老式和慢速计算机)。对于大数据而言,这可能不够快,但是纯python并不是处理大数据的好语言(通常你需要编写C扩展,使用Cython或一些NumPy,Pandas或衍生方法)。此外,生成器上的next函数是惰性的(假设您在python2而不是itertools.izip上使用zip),因此您只需处理每个元组,直到找到匹配项为止。

就个人而言,我只会使用您原来的方法。或者,如果我必须找到几对,那么我只需创建我之前提到的字典(甚至可以序列化它)并在其中进行查找。

赏金理由明确要求“可信和/或官方消息来源”。对Fortunatly“搜索算法”进行了深入研究,因此您可以在算法的基础教科书中找到每种提到的方法的解释。例如:

python wiki中还有一个关于python类型时间复杂性的小概述:"TimeComplexity"。对于查找,您必须选中“获取项目”或“在”中。

1 披露:我是该第三方图书馆的作者。

答案 1 :(得分:2)

虽然它在您的案例中工作但没有太多令人印象深刻。

我们只是提取样本中匹配项目的索引并检查它是否是连续的。

def consecutive_index(src,sample):
    result = None
    il = [src.index(a) for a in sample if a in src]
    if len(il) == len(sample) and len(range(il[0],il[-1]))==1:
        result = il[0]
    return result



x0 = ('b', 'a', 'z', 'z')
x1 = ('b', 'a', 'z', 'z')
x2 = ('z', 'z', 'a', 'a')
x3 = ('z', 'b', 'a', 'a')
sample = ('b', 'a')

##TEST your given combinations.
print consecutive_index(x0,sample) #expected 0
print consecutive_index(x1,sample) #expected 0
print consecutive_index(x2,sample) #expected None
print consecutive_index(x3,sample) #expected 1

答案 2 :(得分:1)

也许比如使用正则表达式?您可以在下面找到两个功能。 findPair 将返回与您的示例完全相同的值。 findPairs 将查找所有非重叠事件并在列表中返回其起始位置。

import re

# Function looks for all non-overlapping occurrences of pair (b, a) 
# and returns a list containing their starting positions
def findPairs(x, b, a):
    x = str().join(x)
    y = str().join([str(b), str(a)])
    try:
        return [x.regs[0][0] for x in list(re.finditer(y, x))]
    except AttributeError:
        return None

# Function looks for first occurrence of the pair (b, a) 
# and returns starting position if there was a match 
# or None when the match was not found
def findPair(x, b, a):
    x = str().join(x)
    y = str().join([str(b), str(a)])
    try:
        return re.search(y, x).regs[0][0]
    except AttributeError:
        return None


if __name__ == "__main__":
    # first occurrence
    x0 = ('b', 'a', 'z', 'z')
    x1 = ('b', 'a', 'z', 'z')
    x2 = ('z', 'z', 'a', 'a')
    x3 = ('z', 'b', 'a', 'a')

    outx0 = findPair(x0, 'b', 'a')  # 0
    outx1 = findPair(x1, 'b', 'a')  # 0
    outx2 = findPair(x2, 'b', 'a')  # None
    outx3 = findPair(x3, 'b', 'a')  # 1

    # multiple occurrences:
    x4 = ('z', 'b', 'a', 'a', 'z', 'b', 'a', 'a')
    outx4 = findPairs(x4, 'b', 'a')  # [1, 5]

修改

如果你不想/不喜欢正则表达式,并且你只对第一次出现感兴趣,你可以简单地使用方法find()并定义查找对的函数:

def findPairNoRe(x, b, a):
    y = str().join([str(b), str(a)])
    res = str().join(x).find(y)
    if res == -1:
        return None
    else:
        return res

答案 3 :(得分:1)

有更短的公式,但没有办法完全避免循环。但是,您可以通过multiprocessing加快速度(请参阅结束)。首先,这里有一些搜索方法(所有O(n)),各种速度和简单的混合。

如果值位于元组或列表中,则使用相当简单,快速的代码:

def find_ba(tup, target):
    last_check = len(tup)-len(target)
    for i, c in enumerate(tup):
        # note: the test below only uses c 95% of the time, 
        # which makes it pretty fast
        if c == target[0] and i <= last_check and tup[i:i+len(target)] == target:
            return i
    return None

不是那么简单,但更快,受到@MSeifert的启发,但针对更长的目标进行了优化:

def find_ba(tup, target):
    import itertools
    search = set(target)
    target_len = len(target)
    for i in count(start=1, step=target_len):
        try:
            if tup[i] in search:  # O(1) reverse lookup
                # search in this neighborhood
                c = tup[i]
                j = 0
                while True:
                    try:
                        # find next occurrence of c in the target
                        j = target[j:].index(c)
                    except ValueError:  # no more occurrences of c in target
                        break
                    # align tup and target and check for a match
                    if j >= i and tup[i-j:i-j+target_len] == target:
                        return i-j
        except IndexError:
            break
    return None

由于你已经很难构造字符元组,你可以改为构造字符串,然后让Python在本机C代码中进行优化:

def find_ba(x, target):
    # assuming x and target are both strings
    pos = x.find(target)
    return pos if pos >= 0 else None

(尽管如此,你可能最好在创建元组或字符串时进行搜索,如果可能的话。)

如果值在生成器中,那么这将起作用(与您已经非常相似)。这比创建长元组更有效,并且如果底层源很慢(例如,从磁盘读取项目),则搜索它们:

import itertools
def find_ba(lst, target):
    a, b = itertools.tee(lst)
    next(b)
    for i, pair in enumerate(zip(a, b)):
        if pair == target:
            return i
    return None

注意:在Python 2.7上,在Python 2.7上使用itertools.izip而不是zip。

加快这种速度的主要方法是使用multiprocessing库。如果要处理大量输入,可以使用multiprocessing.Pool.map以循环方式将每个输入发送给不同的工作人员。如果你只有几个输入并且每个输入很长,那么你可能想使用itertools.islice将它们分成长的块,然后将每个块发送到multiprocessing.Pool.map直到你得到一个命中;然后你就可以开始处理下一个输入。我无法从你的问题中看出哪种方法最有效。

答案 4 :(得分:0)

问题的答案是,不,没有任何方法可以在没有循环所有字符的情况下找到对。因为如果你不看一个角色,你就不知道它是否与你的一个角色相匹配。

您可以通过将迭代隐藏在语言或库例程中来隐藏迭代,但它必须存在。使其隐式可能会使代码更有效(例如,如果您将循环移出Python解释器并转换为预编译语言(如C))。或者,它可能不会。

隐藏东西的(低效,愚蠢!)示例可能是

def find_ba( x, target=('b','a'), separator = '|' ):
   t = separator.join(target)
   try:
        return  ( separator.join([ c for c in x]).index(t) ) / 2
   except ValueError:
        return None

(提供给Silly部的代码根据合同号SW / l10O / Il0O / 01L11000 / 22行走,并置于公共领域)。

答案 5 :(得分:0)

使用itertools可以使它变得懒惰,但仍然需要迭代:

import itertools
def check(x, target):
    for t in itertools.izip(x, itertools.islice(x, 1, len(x))):
        if t == target:
            return True
    return False
check(x0, ('b', 'a'))
True

编辑:在python3中使用zip

答案 6 :(得分:0)

正如nigel222指出的那样,(在最坏的情况下)没有办法避免迭代整个列表,因为你必须进行详尽的比较,以确保你想要的项目不包含在你的迭代中。

如果您要在各种可能的子序列上进行大量的这些查询,那么将它压入集合中是值得的,因为集合具有O(1)查找。

tableHeaderView

这样,通过... my_pairwise = set(pairwise(x)) found_subsequences = [subsequence for subsequence in collection_of_subsequences if subsequence in my_pairwise] 的O(n)迭代只发生一次,之后的每次查找都是O(1)。

答案 7 :(得分:0)

这不实用,但它可以解决你的问题

def look_up(needle, haystack):
    i = ''.join(haystack).find(''.join(needle))
    return i if i > -1 else None

假设我们有这个:

x0 = ('b', 'a', 'z', 'z')
x1 = ('b', 'a', 'z', 'z')
x2 = ('z', 'z', 'a', 'a')
x3 = ('z', 'b', 'a', 'a')
ba = ('b', 'a')

我们得到了这个:

print(look_up(ba, x0)) # Prints: 0
print(look_up(ba, x1)) # Prints: 0
print(look_up(ba, x2)) # Prints: None
print(look_up(ba, x3)) # Prints: 1

这里有多次出现:

def look_up_multiple(needle, haystack):
    needle_str = ''.join(needle)
    haystack_str = ''.join(haystack)
    indexes = []
    i = 0
    while i < len(haystack_str):
        i = haystack_str.find(needle_str, i)
        if i > -1:
            indexes.append(i)
        i += 2
    return indexes

让我们运行它:

x = ('b', 'a', 'z', 'z', 'b', 'a')
ba = ('b', 'a')

print(look_up_multiple(ba, x)) # Prints: [0, 4]

答案 8 :(得分:0)

您可以通过将列表转换为字符串来实现。

def findba(x,target):
    x1 = "".join(x) 
    target1 = "".join(target)
    if target1 in x1:
        return x1.index(target1)
    else:
        return None

ab = ('b','a')
x0 = ('b', 'a', 'z', 'z')
x1 = ('b', 'a', 'z', 'z')
x2 = ('z', 'z', 'a', 'a')
x3 = ('z', 'b', 'a', 'a')

print findba(x0,ab)
print findba(x1,ab)
print findba(x2,ab)
print findba(x3,ab)

答案 9 :(得分:0)

正如已经指出的那样,你无法避免遍历所有字符。您可以使其延迟并仅在输入元组上迭代一次,如下所示(假设Python 3):

from itertools import islice, tee

def find_ba(x):
    pairs = zip(*(islice(g, i, None) for i, g in enumerate(tee(x, 2))))
    return next(
        (i for i, pair in enumerate(pairs) if pair == ('b', 'a')),
        None)

答案 10 :(得分:0)

此解决方案使用列表的target方法查找index的第一个元素。然后它检查列表中的下一个项目是否与target的第二个项目匹配。如果不是,则查找下一次出现'b'并再次检查以下项目。洗涤重复冲洗。

这不会遍历所有对,但会查找预期对中的第一项,然后检查下一项。

def find_ba(x, target=('b','a')):
    try:
        ind = 0
        while ind < len(x):
            ind += x[ind:].index(target[0])
            if x[ind+1] == target[1]:
                return ind
            ind += 1
    except ValueError:
        return None

测试:

# 100 random letters
letters = ['f', 'y', 'h', 'u', 't', 'l', 'y', 'u', 'm', 'z', 'a', 'a',
           'i', 't', 'g', 'm', 'b', 'l', 'z', 'q', 'g', 'f', 'f', 'b', 
           'b', 'a', 'c', 'z', 'n', 'j', 'v', 'b', 'k', 'j', 'y', 'm', 
           'm', 'f', 'z', 'x', 'f', 'q', 'w', 'h', 'p', 'x', 't', 'n', 
           'm', 'd', 'z', 'q', 'v', 'h', 'b', 'f', 'q', 'd', 'b', 's', 
           'a', 't', 'j', 'm', 'h', 'r', 'd', 'n', 'e', 'k', 'y', 'z', 
           'd', 'e', 'x', 'h', 'r', 'z', 'b', 'n', 'q', 'v', 't', 'q', 
           'f', 'w', 'b', 'w', 'f', 'c', 'f', 'h', 'q', 'o', 'r', 'f', 
           'w', 'w', 'n', 'v']
find_ba(letters)  # 24

使用zip进行比较的方法:

def find_ba1(x):
    try:
        return [(i,j) for i,j in zip(x[:-1], x[1:])].index(('b', 'a'))
    except ValueError:
        return None

并进行一点速度测试:

%timeit find_ba(letters)
100000 loops, best of 3: 2.31 µs per loop

%timeit find_ba1(letters)
100000 loops, best of 3: 8.4 µs per loop

答案 11 :(得分:0)

对数据的性质没有任何承诺(即假设它是随机的),搜索不能比O(n)更好。通过使用您尝试执行的操作的特定信息优化问题,您可以充分利用代字号减少操作次数(即减少一个因子),包括:目标的大小,重复字符目标(搜索&#39;&#39;&#39;&#39;&#39;我们可以查看其他所有角色并知道它必须是&#39; b&#39;通过对较小数据集的快速分析来匹配我们的序列,然后查看周围的字符)或我们可以获得的任何其他信息(同样,假设序列列表是未知数量)。例如,我研究的一件事是通过迭代目标的长度并确定它是否是我们正在搜索的字符之一来搜索目标。当然,问题在于不是搜索列表中的每个索引(我们现在触摸len(列表)/ len(目标)元素),我们现在对我们触摸的每个元素执行更多操作(换句话说,对于&# 39; b&#39;,&#39; a&#39;我们搜索每两个元素,但我们寻找两件事)。这在减少操作数方面没有任何作用,但是,它会显着减少从二级存储器加载的元素数量,假设您打算在相当大的序列中查找目标,这就是为什么你避免循环遍历每个元素。如果提高效率是您的唯一目标,还有许多方法可以使用多并行性来提高搜索效率。 (只要记住使用多处理而不是线程,如果你选择这条路由,因为python的线程模块只支持并发,而不是由于解释器瓶颈使线程的多并行性。)

作为结论并直接回答您提出的问题,是的,完全有可能找到成对元素的索引而不查看序列中的每个元素。但是,这样做需要首先查看手头问题的具体信息,然后将此信息应用于搜索。我认为最好的方法是首先分析数据进行搜索,然后执行最适合该输入的搜索方法。换句话说,如果有重复,你可以使用它,但如果没有重复,你可以回到另一个搜索。

答案 12 :(得分:0)

我试图对MSeifert的方法和我的方法进行基准测试。我的代码源自MSeifert的代码,但试图进一步发展,即跳到下一个目标词,而不是一次走两步。顺便说一句,我的通常更快,不需要任何包装。如果有人有任何问题或意见,请告诉我。谢谢。

05/09/2017编辑:
为了回应@Matthias Fripp的评论,我添加了10k和100k元素的测试元组。对于10k元素而言,我的速度仍然更快,但不是100k元素。因此,我的代码不是最佳的。我认为我的方法不是&#34;对&#34;回答@MSeifert指出,因为最初的问题是关于不搜索所有元素的方法。

import random # to generate data
# Set up data
x0 = ('b', 'a', 'z', 'z')
x1 = ('b', 'a', 'z', 'z')
x2 = ('z', 'z', 'a', 'a')
x3 = ('z', 'b', 'a', 'a')
x4 = tuple([random.choice(x3) for i in xrange(10000)])
x5 = tuple([random.choice(x3) for i in xrange(100000)])

# Set up functions
# My code
def findPairwise(x,target):
    currentX = x
    cumulatedIdx=0
    while(1):
        try:
            idx = currentX.index(target[0])
            try:
                if currentX[idx+1] == target[1]:
                    return(idx+cumulatedIdx)
            except:
                pass
        except:
            break
        currentX = currentX[idx+2:]
        cumulatedIdx += idx+2

# MSeifert's method
from itertools import count
def find_ab(tup,target):
    for idx in count(start=1, step=2):
        try:
            if tup[idx] == target[0]:
                if tup[idx+1] == target[1]:
                    return idx
            elif tup[idx] == target[1]:
                if tup[idx-1] == target[0]:
                    return idx-1
        except IndexError:
            break

结果

In [109]: %timeit findPairwise(x0,target)
The slowest run took 8.66 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 1.27 µs per loop

In [110]: %timeit find_ab(x0,target)
The slowest run took 5.49 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 2.04 µs per loop

In [111]: %timeit findPairwise(x1,target)
The slowest run took 4.75 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 1.46 µs per loop

In [112]: %timeit find_ab(x1,target)
The slowest run took 5.04 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 1.99 µs per loop

In [113]: %timeit findPairwise(x2,target)
The slowest run took 4.66 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 2.56 µs per loop

In [114]: %timeit find_ab(x2,target)
The slowest run took 5.89 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 4.25 µs per loop

In [115]: %timeit findPairwise(x3,target)
The slowest run took 8.59 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 1.28 µs per loop

In [116]: %timeit find_ab(x3,target)
The slowest run took 6.66 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 1.65 µs per loop

In [151]: %timeit findPairwise(x4,target)
The slowest run took 5.46 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 1.27 µs per loop

In [152]: %timeit find_ab(x4,target)
The slowest run took 6.21 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 1.92 µs per loop

In [153]: %timeit findPairwise(x5,target)
1000 loops, best of 3: 325 µs per loop

In [154]: %timeit find_ab(x5,target)
The slowest run took 4.35 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 3.45 µs per loop

答案 13 :(得分:0)

如果您在同一输入中反复搜索不同的目标,则可以通过创建所有唯一字符串位置的哈希来避免每次循环输入,如下面的代码。这需要在初始设置中通过每个输入进行一次循环,但随后搜索几乎是瞬时的(没有循环)。

# store first occurrence of each unique 2-char string (O(n))
x1_first = dict()
target_len = 2
for i in range(len(x1)):
    x1_first.setdefault(x1[i:i+target_len], i)

# find first occurrence of a particular string without looping (O(1))
print x1_first.get(('a', 'b'), None)

注意:这与@ MSeifert的答案非常相似,但展示了如何处理任意目标长度。如果您需要担心多个目标长度,则需要为每个长度创建单独的dicts,这对于存储来说效率很低。在这种情况下,您可能最好创建一个最长目标的排序列表(例如10个字符),然后使用二分法搜索它(请参阅bisect模块)。对于较短的子串,您需要扫描多个匹配并拔出最早的匹配。