【问题标题】:Finding index of pairwise elements查找成对元素的索引
【发布时间】:2017-09-23 14:25:14
【问题描述】:

给定目标 ('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

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


在 cmets 中回答 @MatthiasFripp 的问题:

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

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

你能说一下你需要搜索多少个列表以及它们有多长吗?这将有助于建议搜索策略。

元组的长度不是固定的。它们的大小可以 > 2。

【问题讨论】:

  • if ab in "".join(x0) ?
  • “不循环所有元素”是什么意思?当然,您必须至少查看每个元素一次才能确定元组不在列表中。 (您可能会争辩说查看每个第二个元素就足够了,但是您必须将其与元组中的 both 元素进行比较,不是吗?)
  • @alvas 我认为即使您使用 dict 在恒定时间内访问一对也是不可能的。你仍然需要循环一次来构造。
  • 你不需要 try-except 子句,return next((i for i, pair in enumerate(pairwise(x)) if pair == target), None)
  • @alvas 从概念上讲,您能否在问题中解释您希望如何在没有显式或隐式循环的情况下实现这一点?

标签: python indexing tuples pairwise


【解决方案1】:

最快的通用搜索算法将具有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' 的奇数索引项目跳过一个项目。

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

二分查找

二分查找只需要比较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

但是,如果您只想搜索 one 对,则该方法要慢得多,因为您会丢失“短路行为”(一旦找到匹配项就停止)并且您会处理所有创建字典时的元素。

其他方法

除了一般的方法:

  • O(n)线性搜索
  • O(log(n))二分查找(用于排序数据)
  • O(1) 查找(用于哈希查找或其他只需要在某些“桶”中搜索的搜索问题)

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

最后的评论

pairwise 的方法其实很不错,但你也可以使用iteration_utilities.successive1。最后我检查了一下,它比食谱快了大约 1.5 到 2 倍。即使您不改变方法并接受在最坏的情况下需要处理所有(或几乎所有)元素,它可能会更快!

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

现在,在写完所有这些文字之后,我需要说明显而易见的事情:

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

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


赏金原因明确需要“可靠和/或官方来源”。幸运的是,“搜索算法”得到了很好的研究,因此您可以在有关算法的基本教科书中找到每种上述方法的解释。例如:

python wiki:"TimeComplexity" 中还有一个关于 python 类型时间复杂度的小概述。对于查找,您必须检查“Get Item”或“in”。


1 披露:我是那个第 3 方库的作者。

【讨论】:

    【解决方案2】:

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

    2017 年 5 月 9 日编辑:
    为了回应@Matthias Fripp 的评论,我添加了 10k 和 100k 元素的测试元组。对于 10k 元素,我的仍然更快,但不是 100k 元素。因此,我的代码不是最优的。我认为我的方法也不是@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
    

    【讨论】:

    • 您是否测试过findPairwise() 在@alvas 指定的很长的x 元组中的表现?我相信您的方法在每一步都会完整复制 currentX 的其余部分,这应该具有类似 O(n^2) 性能的长 x 元组。
    • 它可能会更快(当您点击您的O(n**2) 最坏的情况时)但它仍然必须处理每个项目。迭代在 .index(target[0]) 中隐式完成,可能比 python 循环快,但它仍然存在。我的解决方案并不完全是为了速度,但问题不在于性能,它只是询问“如何避免循环所有字符”。然而,一个非常有创意的答案! (而且我没有对你投反对票)
    • @MSeifert 感谢您的 cmets。我想你是对的。 .index() 已搜索所有字符。我之前没想到。
    • @MatthiasFripp 感谢您的 cmets。我同意切片可能是减慢它的原因。我不清楚列表切片(浅拷贝?)的操作是否有 O(n^2)。
    • @Chih-HsuJackLin Slicing 对切片中的每个项目进行浅拷贝;使用短字符串,这几乎等同于复制项目本身。无论如何,获取列表的一部分是 O(n)。但是您在算法的每一步都创建了一个新切片,并且算法中的步数为 O(n)。所以复制的总量是O(n^2)。
    【解决方案3】:

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

    # 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 的答案之一非常相似,但显示了如何处理任意目标长度。如果您要担心多个目标长度,那么您需要为每个长度创建单独的字典,这对于存储来说效率很低。在这种情况下,您可能会更好地创建一个可能的最长目标(例如 10 个字符)的排序列表,然后使用二分法搜索它(参见 bisect 模块)。对于较短的子字符串,您需要扫描多个匹配项并取出最早的匹配项。

    【讨论】:

    • 你知道这只是我已经包含在我的答案中的方法的轻微变化吗?也使用x1_first.get 默认值,否则当“特定字符串”不在字典中时,您将得到KeyErrors,即x2(就像我在回答中所做的那样)。 :)
    • @MSeifert,对不起,我应该在发帖前仔细检查一下。当我意识到这些搜索可能会重复时,我才想到它,但我忘了你已经有这样的搜索了。我将把它留在这里,因为它展示了如何处理不同长度的目标,否则我会放弃它。
    【解决方案4】:

    对此有更短的公式,但没有办法完全避免循环。但是,您可以通过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 直到您获得成功;然后你可以开始处理下一个输入。我无法从您的问题中判断出哪种方法最有效。

    【讨论】:

      【解决方案5】:

      解决方案:

      构建成对的序列数组后,可以使用 numpy where 来定位序列。

      #np.roll(x1,-1) shifts the list leftwise one element. np.core.defchararray.add builds a paired sequence. 
      np.where(np.core.defchararray.add(x1,np.roll(x1,-1)) == 'ba')[0]
      

      测试

      for x in [x0,x1,x2,x3]:
          print (np.where(np.core.defchararray.add(x,np.roll(x,-1)) == 'ba'))[0]
      
      [0]
      [0]
      []
      [1]
      

      【讨论】:

      • 这种方法处理每个元素至少 6 次(roll 两次,因为它首先转换为数组然后滚动 - 然后 add 的两个参数都转换为字符数组,然后你做平等检查,最后一次在哪里)并且它不是短路。同样roll 具有包裹元素的丑陋副作用,因此如果第一个元素是a,最后一个元素是b,并且中间的每个元素都是z('a', 'z', 'z', 'b'),您可以获得索引。
      【解决方案6】:

      正如已经指出的那样,您无法避免循环遍历所有字符。您可以使它变得惰性,并且只在输入元组上迭代一次,如下所示(假设 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)
      

      【讨论】:

      • 切片x[1:] 不是惰性的,它要求x 是一个列表或元组。如果 x 是一个迭代器,它的值太多而无法一次全部放入内存中,它将无法工作。提问者使用的pairwise 配方更好。
      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

        此解决方案使用列表的index 方法查找target 的第一个元素。然后检查列表中的下一项是否与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
        

        【讨论】:

          【解决方案9】:

          您可以通过将列表转换为字符串来做到这一点。

          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)
          

          【讨论】:

            【解决方案10】:

            虽然它适用于您的情况,但并不令人印象深刻,请查看。

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

            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
            

            【讨论】:

            • 我没有投反对票,但tuple.index 方法只会返回第一次出现,因此如果第一次出现不匹配,您将找不到匹配项:consecutive_index(('a', 'b', 'z', 'b', 'a'), ('b', 'a')) 返回None (但在索引 4 处有一个匹配项)。这个问题没有提到元组中的任何“结构”,所以我不确定你的方法是否正确。如果a 不在src 中,if a in src 也会处理src 的所有元素,因此它不能完全解决“不循环所有字符”的部分问题。
            【解决方案11】:

            这不实用,但它解决了你的情况

            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]
            

            【讨论】:

              【解决方案12】:

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

              但是,如果您要对各种可能的子序列进行大量此类查询,那么将其压入一个集合可能是值得的,因为集合具有 O(1) 查找。

              ...
              my_pairwise = set(pairwise(x))
              found_subsequences = [subsequence
                                    for subsequence in collection_of_subsequences
                                    if subsequence in my_pairwise]
              

              这样,通过x 的 O(n) 迭代只发生一次,之后的每次查找都是 O(1)。

              【讨论】:

                【解决方案13】:

                也许例如使用正则表达式?您可以在下面找到两个功能。 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
                

                【讨论】:

                • str.join(), str.find() 和正则表达式仍将迭代。转换为字符串 first 对于 OP 已经合理有效地解决的问题需要做很多工作。
                【解决方案14】:

                使用 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

                【讨论】:

                • 为什么假设他们使用的是 Python 2?
                【解决方案15】:

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

                您可以通过将迭代隐含在语言或库例程中来隐藏迭代,但它必须存在。使其隐含可能会使代码更高效(例如,如果您将循环移出 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
                

                (提供给傻瓜部的代码按照合同编号 SW/l10O/Il0O/01L1lO00/22 并置于公共领域)。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-07-02
                  • 2015-05-07
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-01-03
                  • 1970-01-01
                  相关资源
                  最近更新 更多