从RLE模式中删除代码重复,而不是诉诸Haskell?

时间:2012-07-06 08:29:18

标签: python haskell run-length-encoding

RLE(行程编码)模式似乎在我的工作中出现了很多。

它的本质是,每次看到“中断”到达输入的末尾时,您输出的自上次“中断”以来遇到的元素减少。< / p>

(在实际的RLE中,'break'只是这个字符与最后一个字符不匹配,但在现实世界中它通常稍微复杂一点,但仍然是当前和最后元素的函数。)

我想删除在循环和结尾都发生的重复last_val != None: rle.append((last_val, count))条件和操作。

问题是:

  1. 用函数调用替换它们会产生更多代码,而不是更少。
  2. 将其保持在命令式的风格中(例如,在Haskell中,问题就会消失)。
  3. 命令式Python代码是:

    #!/usr/bin/env python
    
    data = "abbbccac"
    
    if __name__ == '__main__':
      rle = []
      last_val = None
      count = 0;
    
      for val in data:
        if val != last_val and last_val != None:
          rle.append((last_val, count))
          count = 1
        else:
          count += 1
        last_val = val
      if last_val != None:
        rle.append((last_val, count))
    
      print rle
    

    P.S。在函数式语言中可以解决这个问题:

    #!/usr/bin/env runhaskell
    import Data.List (group)
    
    dat = "abbbccac"
    
    rle :: Eq a => [a] -> [(a, Int)]
    rle arr = map (\g -> (head g, length g)) $ group arr
    
    main :: IO ()
    main = print $ rle dat
    

5 个答案:

答案 0 :(得分:3)

使用Python“包含电池”可以解决这个问题:

>>> data = "abbbccac"
>>> from itertools import groupby
>>> ilen = lambda gen : sum(1 for x in gen)
>>> print [(ch, ilen(ich)) for ch,ich in groupby(data)]
[('a', 1), ('b', 3), ('c', 2), ('a', 1), ('c', 1)]

groupby返回2元组的迭代器。第一个是表示下一个组的值,第二个是可用于迭代组中项目的迭代器。你只需要组的长度,但你不能直接获取长度,所以我添加了简单的ilen lambda函数来计算迭代器的长度。

答案 1 :(得分:2)

如果我理解正确就很简单......

from itertools import groupby

data = "abbbccac"

print [(k, len(list(g))) for k,g in groupby(data)]
哇 - 比较保罗与我的非常相似的功能,得到了非常令人惊讶的结果。事实证明,加载列表的速度要快10到100倍。更令人惊讶的是,随着块越来越大,列表实现具有更大的优势。

我想这就是为什么我♥Python - 它使得简洁的表达更好 - 即使它有时看起来像魔法。

自己检查数据:

from itertools import groupby
from timeit import Timer

data = "abbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccac" 

def rle_walk(gen):
    ilen = lambda gen : sum(1 for x in gen)
    return [(ch, ilen(ich)) for ch,ich in groupby(data)]

def rle_list(data):
    return [(k, len(list(g))) for k,g in groupby(data)]

# randomy data
t = Timer('rle_walk("abbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccac")', "from __main__ import rle_walk; gc.enable()")
print t.timeit(1000)

t = Timer('rle_list("abbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccacabbbccac")', "from __main__ import rle_list; gc.enable()")
print t.timeit(1000)

# chunky blocks
t = Timer('rle_walk("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccc")', "from __main__ import rle_walk; gc.enable()")
print t.timeit(1000)

t = Timer('rle_list("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccc")', "from __main__ import rle_list; gc.enable()")
print t.timeit(1000)

给我结果(越小越好):

1.42423391342    # lambda and walk - small blocks
0.145968914032   # make list - small blocks
1.41816806793    # lambda and walk - large blocks
0.0165541172028  # make list - large blocks

答案 2 :(得分:2)

这是一种更为迫切的形式。你可以通过添加或链接到一个永远不会匹配任何列表元素的一次性哨兵来消除你的重复代码,强制序列结束通过你的“这不等于最后”的代码:

from itertools import chain

def rle(seq):
    ret = []
    sentinel = object()
    enum = enumerate(chain(seq,[sentinel]))
    start,last = next(enum)
    for i,c in enum:
        if c != last:
            ret.append((last,i-start))
            start,last = i,c
    return ret

这甚至可以优雅地处理输入seq为空的情况,输入可以是任何序列,迭代器或生成器,而不仅仅是字符串。

答案 3 :(得分:1)

如果使用enumerate来跟踪您看到的值的数量并将last_val和last_index(或(last_val,last_index)存储为元组),则至少可以删除else子句。 E.g。

last_index = 0
last_val = None

for index, val in enumerate(data):
    if val != last_val and last_val is not None:
        rle.append((last_val, index - last_index + 1))
        last_val = val
        last_index = index
    last_val = val
if last_val is not None:
    rle.append((last_val, index - last_index + 1))

你也可以在任何时候开始枚举并且它会计数(所以你可以用enumerate(data, last_index)初始化它。看起来你希望计数从1开始,这就是为什么我添加了{ {1}}部分。

枚举只计算从迭代器生成的元素数量,与类型无关。

答案 4 :(得分:0)

我更喜欢groupby解决方案,但编写命令式解决方案最方便的方法通常是作为生成器:

data = "abbbccac"

def rle(xs):
    def g():
        last = object()
        n = 0
        for x in xs:
            if x != last:
                yield last,n
                last = x
                n = 0
            n += 1
    return list(g())[1:]

print rle(data)