如何变换子序列?

2024-10-03 17:19:55 发布

您现在位置:Python中文网/ 问答频道 /正文

假设我有一个函数,它接受一个生成器并生成一个新的生成器。例如,pair(gen),它将序列1, 2, 3, 4, ...转换为(1, 2), (3, 4), ...。你知道吗

现在我想取一个序列的生成器,将转换应用到它的子序列,同时保留其他元素的原样。例如,如果gen生成序列1, 2, 3, 4, 5, ...,我想对偶数元素运行pair,那么withEven(gen, pair)将生成1, 3, (2, 4), 5, ...。它应该知道pair将需要多少元素,也不应该缓存接收到的所有奇数,直到产生对。它应该从主序列中获取下一个项目,如果是奇数,则生成它,如果是偶数,则将它馈送到变压器并重复。你知道吗

是否可以定义withEven

如果pair定义为

def pair(gen):
  prev = None
  for x in gen:
    if prev:
      yield (prev, x)
      prev = None
    else:
      prev = x

那么withEvenRunPair(gen),相当于withEven(gen, pair),可以定义为

def withEvenRunPair(gen):
  prev = None
  for x in gen:
    if x % 2 == 1:
      yield x
    else:
      if prev:
        yield (prev, x)
        prev = None
      else:
        prev = x

然而,定义泛型withEven似乎更困难。你知道吗


Tags: none元素forif定义def序列else
3条回答

你的意思是这样的吗?你知道吗

def pair(sequence, selector=bool, fill=None, error_on_fill=False):
    sentinel = object()
    previous = sentinel
    for item in sequence:
        if not selector(item):
            yield item
            continue
        if previous is not sentinel:
            yield previous, item
            previous = sentinel
        else:
            previous = item

    if previous is not sentinel:
        if error_on_fill:
            raise ValueError("Final selected item missing a paired valued")
        yield previous, fill

result = list(pair(range(10), selector=(lambda x: x % 2 == 0)))
assert result == [1, (0, 2), 3, 5, (4, 6), 7, 9, (8, None)]

我终于弄明白了,尽管它需要对pair和其他转换进行一些更改。你知道吗

停止计算并随后恢复计算的唯一方法是yield。因此,如果我们用某种形式的yield替换input_generator.next(),我们就可以控制向转换提供值的速度。你知道吗

首先我定义一个请求对象

class Get(object):
  def isset(self):
    return hasattr(self, 'value')

  def set(self, x):
    self.value = x

我现在将n = foo.next()替换为

g = Get()
yield g
if not g.isset():
  break
n = g.value

例如,我的问题中的pair将变成

def pair(): #notice that there isn't an input generator any more
  prev = None
  while True: #there was a next() hidden inside 'for'
    g = Get()
    yield g
    if not g.isset():
      break
    elif prev:
      yield (prev, g.value)
      prev = None
    else:
      prev = g.value

这种变换很容易转化为适当的变换

def withGen(fn, gen):
  for f in fn: #get the next request or result
    if isinstance(f, Get):
      f.set(gen.next()) #answer requests with gen.next()
    else:
      yield f  #yield results

print [x for x in withGen(pair(), [1, 2, 3, 4].__iter__())]
#prints [(1, 2), (3, 4)]

最后我们可以定义withSelector

def withSelector(select, fn):
  f = None #we need to remember the last unanswered request
  while True:
    if not f: #if last request has been answered...
      f = fn.next() #get the next request, or the result

    if isinstance(f, Get): #if it's a request...
      g = Get()
      yield g #ask for another input value
      if not g.isset():
        break
      elif select(g.value): #if the values matches selector...
        f.set(g.value) #feed it to transformation
        f = None #and mark the request as answered
      else:
        yield g.value #otherwise just yield it

    else: #if transformation yielded a result
      yield f #yield it
      f = None #and note that there is no request waiting

arr = range(1, 18)
print [x for x in withGen(withSelector(isEven, pair()), arr.__iter__())]
#prints [1, 3, (2, 4), 5, 7, (6, 8), 9, 11, (10, 12), 13, 15, (14, 16), 17]

我甚至可以排序两个选择器。例如,如果我将add3定义为yield三个Get(),然后yield它们的结果之和,我可以

print [x for x in withGen(withSelector(isOdd, add3()), withGen(withSelector(isEven, pair()), arr.__iter__()))]
#prints [(2, 4), 9, (6, 8), 27, (10, 12), (14, 16), 45]

我想你可以这样做:

from itertools import islice, izip, izip_longest, tee

def grouper(iterable, n, fillvalue=None):
    # https://docs.python.org/2/library/itertools.html#recipes
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

def withEvenRunPair(gen):
    it1, it2 = tee(gen)
    odd = islice(it1, 0, None, 2)
    even = islice(it2, 1, None, 2)
    for a, b in izip_longest(grouper(odd, 2), grouper(even, 2)):
        yield a
        yield b


gen = (x for x in xrange(1, 13))
for x in withEvenRunPair(gen):
    print x

输出:

(1, 3)
(2, 4)
(5, 7)
(6, 8)
(9, 11)
(10, 12)

相关问题 更多 >