代码之家  ›  专栏  ›  技术社区  ›  Lesmana

滥用屈服以避免循环中的条件

  •  0
  • Lesmana  · 技术社区  · 15 年前

    我需要寻找其他事物中的第一个,最后一个,任何,或所有发生的事情。为了避免重复我自己( DRY )我想出了下面的解决办法。

    有趣的是方法 search_revisions() 和 collect_one_occurence() 两者皆有 Searcher 上课。

    在 SearcherYield 我在中创建生成器 搜索修订() 只不过把发电机放在 收集一次发生() 在收集第一个结果之后。在 SearcherCondition 我提出了一个条件。必须为循环的每次迭代检查此条件。

    我不能决定我使用屈服和随后放弃发电机是天才的罢工还是骇人听闻的黑客行为。你怎么认为?对于这种情况,你还有别的想法吗?

    #!/usr/bin/python
    
    class Revision:
      # a revision is something like a textfile.
      # the search() method will search the textfile
      # and return the lines which match the given pattern.
      # for demonstration purposes this class is simplified
      # to return predefined results
      def __init__(self, results):
        self.results = results
      def search(self, pattern):
        return self.results
    
    class AbstractSearcher:
      def __init__(self, revisions):
        self.revisions = revisions
      def search_for_first_occurence(self, pattern):
        keys = sorted(self.revisions.iterkeys())
        return self.collect_one_occurence(keys, pattern)
      def search_for_last_occurence(self, pattern):
        keys = sorted(self.revisions.iterkeys(), reverse = True)
        return self.collect_one_occurence(keys, pattern)
      def search_for_any_occurence(self, pattern):
        keys = self.revisions.iterkeys()
        return self.collect_one_occurence(keys, pattern)
      def search_for_all_occurences(self, pattern):
        keys = self.revisions.iterkeys()
        return self.collect_all_occurences(keys, pattern)
    
    class SearcherYield(AbstractSearcher):
    
      def search_revisions(self, keys, pattern):
        # create generator which yields the results one by one
        for key in keys:
          rev = self.revisions[key]
          result = rev.search(pattern)
          if result:
            yield result
    
      def collect_one_occurence(self, keys, pattern):
        # take the first result and then abandon the generator
        for result in self.search_revisions(keys, pattern):
          return result
        return []
    
      def collect_all_occurences(self, keys, pattern):
        # collect all results from generator
        results = []
        for result in self.search_revisions(keys, pattern):
          results.extend(result)
        return results
    
    class SearcherCondition(AbstractSearcher):
    
      def search_revisions(self, keys, pattern, just_one):
        # collect either all results from all revisions
        # or break the loop after first result found
        results = []
        for key in keys:
          rev = self.revisions[key]
          result = rev.search(pattern)
          if result:
            results.extend(result)
            if just_one:
              break
        return results
    
      def collect_one_occurence(self, keys, pattern):
        return self.search_revisions(keys, pattern, just_one = True)
    
      def collect_all_occurences(self, keys, pattern):
        return self.search_revisions(keys, pattern, just_one = False)
    
    def demo(searcher):
      print searcher.__class__.__name__
      print 'first:', searcher.search_for_first_occurence('foo')
      print 'last: ', searcher.search_for_last_occurence('foo')
      print 'any:  ', searcher.search_for_any_occurence('foo')
      print 'all:  ', searcher.search_for_all_occurences('foo')
    
    def main():
      revisions = {
            1: Revision([]),
            2: Revision(['a', 'b']),
            3: Revision(['c']),
            4: Revision(['d','e', 'f']),
            5: Revision([])}
      demo(SearcherYield(revisions))
      demo(SearcherCondition(revisions))
    
    if __name__ == '__main__':
      main()
    

    一些上下文:修订基本上是文本文件。你可以把它们想象成维基页面的修订版。通常有成百上千的修订。每个修订包含多达数千行的文本。也有一些情况下,只有几个修订,每行几行。

    在修订中搜索将在文本中搜索模式并返回匹配的行。有时候有成千上万的结果,有时候没有结果。

    有时我只需要知道是否有任何修订结果(搜索任何)。有时我必须收集所有的结果以便进一步处理(搜索所有)。有时我只需要第一次修订与匹配,有时只是最后一次修订(搜索第一次和最后一次)。

    3 回复  |  直到 14 年前
        1
  •  2
  •   Lesmana    14 年前

    我做了一个基准。结果如下:

    $ ./benchmark.py 
    benchmark with revcount: 1000 timeitcount: 1000
    last, first, yield: 0.902059793472
    last, first,  cond: 0.897155046463
    last,   all, yield: 0.818709135056
    last,   all,  cond: 0.818334102631
     all,   all, yield: 1.26602506638
     all,   all,  cond: 1.17208003998
    benchmark with revcount: 2000 timeitcount: 1000
    last, first, yield: 1.80768609047
    last, first,  cond: 1.84234118462
    last,   all, yield: 1.64661192894
    last,   all,  cond: 1.67588806152
     all,   all, yield: 2.55621600151
     all,   all,  cond: 2.37582707405
    benchmark with revcount: 10000 timeitcount: 1000
    last, first, yield: 9.34304785728
    last, first,  cond: 9.33725094795
    last,   all, yield: 8.4673140049
    last,   all,  cond: 8.49153590202
     all,   all, yield: 12.9636368752
     all,   all,  cond: 11.780673027
    

    产率和条件解的时间非常相似。我认为这是因为生成器(yield)有一个带有条件的循环(如果不是空的或类似的)。我以为我能避免这种情况,但我只是把它移到看不见的地方。

    不管怎样,数字表明性能基本上是相同的,所以代码应该根据可读性来判断。我将坚持循环中的条件。我喜欢直白的。

    以下是基准代码:

    #!/usr/bin/python
    
    import functools
    import timeit
    
    class Revision:
      # a revision is something like a textfile.
      # the search() method will search the textfile
      # and return the lines which match the given pattern.
      # for demonstration purposes this class is simplified
      # to return predefined results
      def __init__(self, results):
        self.results = results
      def search(self, pattern):
        return self.results
    
    class AbstractSearcher:
      def __init__(self, revisions):
        self.revisions = revisions
      def search_for_first_occurence(self, pattern):
        keys = sorted(self.revisions.iterkeys())
        return self.collect_one_occurence(keys, pattern)
      def search_for_last_occurence(self, pattern):
        keys = sorted(self.revisions.iterkeys(), reverse = True)
        return self.collect_one_occurence(keys, pattern)
      def search_for_any_occurence(self, pattern):
        keys = self.revisions.iterkeys()
        return self.collect_one_occurence(keys, pattern)
      def search_for_all_occurences(self, pattern):
        keys = self.revisions.iterkeys()
        return self.collect_all_occurences(keys, pattern)
    
    class SearcherYield(AbstractSearcher):
    
      def search_revisions(self, keys, pattern):
        # create generator which yields the results one by one
        for key in keys:
          rev = self.revisions[key]
          result = rev.search(pattern)
          if result:
            yield result
    
      def collect_one_occurence(self, keys, pattern):
        # take the first result and then abandon the generator
        for result in self.search_revisions(keys, pattern):
          return result
        return []
    
      def collect_all_occurences(self, keys, pattern):
        # collect all results from generator
        results = []
        for result in self.search_revisions(keys, pattern):
          results.extend(result)
        return results
    
    class SearcherCondition(AbstractSearcher):
    
      def search_revisions(self, keys, pattern, just_one):
        # collect either all results from all revisions
        # or break the loop after first result found
        results = []
        for key in keys:
          rev = self.revisions[key]
          result = rev.search(pattern)
          if result:
            results.extend(result)
            if just_one:
              break
        return results
    
      def collect_one_occurence(self, keys, pattern):
        return self.search_revisions(keys, pattern, just_one = True)
    
      def collect_all_occurences(self, keys, pattern):
        return self.search_revisions(keys, pattern, just_one = False)
    
    def benchmark(revcount, timeitcount):
    
      lastrev = {}
      for i in range(revcount):
        lastrev[i] = Revision([])
      lastrev[revcount] = Revision([1])
    
      allrevs = {}
      for i in range(revcount):
        allrevs[i] = Revision([1])
    
      last_yield = SearcherYield(lastrev)
      last_cond = SearcherCondition(lastrev)
      all_yield = SearcherYield(allrevs)
      all_cond = SearcherCondition(allrevs)
    
      lfy = functools.partial(last_yield.search_for_first_occurence, 'foo')
      lfc = functools.partial(last_cond.search_for_first_occurence, 'foo')
      lay = functools.partial(last_yield.search_for_all_occurences, 'foo')
      lac = functools.partial(last_cond.search_for_all_occurences, 'foo')
      aay = functools.partial(all_yield.search_for_all_occurences, 'foo')
      aac = functools.partial(all_cond.search_for_all_occurences, 'foo')
    
      print 'benchmark with revcount: %d timeitcount: %d' % (revcount, timeitcount)
      print 'last, first, yield:', timeit.timeit(lfy, number = timeitcount)
      print 'last, first,  cond:', timeit.timeit(lfc, number = timeitcount)
      print 'last,   all, yield:', timeit.timeit(lay, number = timeitcount)
      print 'last,   all,  cond:', timeit.timeit(lac, number = timeitcount)
      print ' all,   all, yield:', timeit.timeit(aay, number = timeitcount)
      print ' all,   all,  cond:', timeit.timeit(aac, number = timeitcount)
    
    def main():
      timeitcount = 1000
      benchmark(1000, timeitcount)
      benchmark(2000, timeitcount)
      benchmark(10000, timeitcount)
    
    if __name__ == '__main__':
      main()
    

    关于我的系统的一些信息:

    $ lsb_release -a
    No LSB modules are available.
    Distributor ID: Ubuntu
    Description:    Ubuntu 10.04.1 LTS
    Release:    10.04
    Codename:   lucid
    $ uname -a
    Linux lesmana-laptop 2.6.32-26-generic #46-Ubuntu SMP Tue Oct 26 16:46:46 UTC 2010 i686 GNU/Linux
    $ python --version
    Python 2.6.5
    $ cat /proc/cpuinfo | grep name
    model name  : Intel(R) Pentium(R) M processor 1.60GHz
    
        2
  •  0
  •   vonPetrushev    15 年前

    这将解决您的问题,如果查找项是不可变的,并且collec是任何有序的集合:

    positions = [i for i, item in enumerate(collec) if item==lookup_item]

    它实际上会返回collec中发生查找项的所有位置。

        3
  •  0
  •   parity3    14 年前

    就我个人而言,我赞成收益率的可读性,但这是一个接近的要求。除了我认为它是一个很好的代码构造并且在很多情况下都适用之外,我真的没有太多的理由。

    您可能已经知道这一点,但代码将需要将匹配的修订返回给调用方。最简单的代码修改方法是在修订搜索方法返回结果时返回到修订的链接。

    您可以通过结合使用python itertools模块和yield来减少代码。可读性可以说是一塌糊涂,但它是如此令人敬畏的极客圆滑:

    from itertools import chain,repeat,islice,ifilter
    def collect_one_occurence(self, keys, pattern):
        return chain(ifilter(None,(rev.search(pattern) for rev in (self.revisions[key] for key in keys)),repeat([]).next()
    
    def collect_all_occurences(self, keys, pattern):
        return list(chain(*[rev.search(pattern) for rev in (self.revisions[key] for key in keys)]))
    

    显然,您可以扩展代码以使其更具可读性,但为了进行基准测试,我将其折叠了。。想知道这在多大程度上改善了你目前的成绩?