【问题标题】:SICP "streams as signals" in PythonPython中的SICP“作为信号流”
【发布时间】:2015-10-02 22:20:09
【问题描述】:

我发现了一些在 Python 中实现类 SICP 流的好例子(herehere)。但我仍然不确定如何处理像 SICP 3.5.3 "Streams as signals" 中的 integral 这样的示例。

在那里找到的Scheme代码

(define (integral integrand initial-value dt)
  (define int
    (cons-stream initial-value
                 (add-streams (scale-stream integrand dt)
                              int)))
  int)

这个问题的棘手之处在于返回的流 int 是根据自身定义的(即流 int 用于流 int 的定义中)。

我相信 Python 可以有类似的表达和简洁的东西......但不知道如何。所以我的问题是,什么是 Python 中类似的 stream-y 构造? (我所说的流是 SICP 中 3.5 的主题,但简而言之,一种构造(如 Python 生成器),它返回不定长度序列的连续元素,并且可以与诸如 add-streams 之类的操作组合和处理以及尊重流的惰性特性的 scale-stream。)

【问题讨论】:

    标签: python stream sicp


    【解决方案1】:

    有两种方法可以阅读您的问题。第一个很简单:您如何使用 Stream 构造,也许是您的第二个 link 中的构造,但具有递归定义?可以这样做,虽然在 Python 中有点笨拙。

    在 Python 中,您可以表示循环数据结构,但不能直接表示。你不能写:

    l = [l]
    

    但你可以写:

    l = [None]
    l[0] = l
    

    同样你不能写:

    def integral(integrand,initial_value,dt):
        int_rec = cons_stream(initial_value,
                              add_streams(scale_stream(integrand,dt),
                                          int_rec))
        return int_rec
    

    但你可以写:

    def integral(integrand,initial_value,dt):
        placeholder = Stream(initial_value,lambda : None)
        int_rec = cons_stream(initial_value,
                              add_streams(scale_stream(integrand,dt),
                                          placeholder))
        placeholder._compute_rest = lambda:int_rec
        return int_rec
    

    请注意,我们需要笨拙地预先计算placeholder 的第一个元素,然后只为流的其余部分修复递归。但这确实有效(以及所有其余代码的适当定义 - 我将把它全部放在这个答案的底部)。

    但是,您问题的第二部分似乎是在询问如何在 Python 中自然地执行此操作。您要求“Python 中的类似 stream-y 构造”。显然,这个问题的答案正是生成器。生成器自然地提供了流概念的惰性求值。它的不同之处在于它不是自然递归地表达的,但是 Python 不支持它以及 Scheme,正如我们将看到的那样。

    换句话说,严格的流概念可以用 Python 来表达(如链接和上面的),但惯用的方法是使用生成器。

    通过流到生成器的一种直接机械转换,或多或少可以复制Scheme示例(但避免内置int):

    def integral_rec(integrand,initial_value,dt):
        def int_rec():
            for x in cons_stream(initial_value,
                        add_streams(scale_stream(integrand,dt),int_rec())):
                yield x
        for x in int_rec():
            yield x
    
    def cons_stream(a,b):
        yield a
        for x in b:
            yield x
    
    def add_streams(a,b):
        while True:
            yield next(a) + next(b)
    
    def scale_stream(a,b):
        for x in a:
            yield x * b
    

    这里唯一棘手的事情是意识到您需要急切地调用递归使用int_rec 作为add_streams 的参数。调用它并不会开始它产生值 - 它只是创建准备好在需要时懒惰地产生它们的生成器。

    这对于小型被积函数非常有效,尽管它不是很 Python。 Scheme 版本通过优化尾递归来工作 - 如果您的被积函数太长,Python 版本将超过最大堆栈深度。所以这在 Python 中并不合适。

    我认为直接和自然的 pythonic 版本看起来像这样:

    def integral(integrand,initial_value,dt):
        value = initial_value
        yield value
        for x in integrand:
            value += dt * x
            yield value
    

    这有效且正确地将integrand 懒惰地视为“流”。但是,它使用迭代而不是递归来解包被积函数迭代,这更像是 Python 方式。

    在迁移到自然 Python 时,我还删除了流组合函数 - 例如,将 add_streams 替换为 +=。但如果我们想要一种中途版本,我们仍然可以使用它们:

    def accum(initial_value,a):
        value = initial_value
        yield value
        for x in a:
            value += x
            yield value
    
    def integral_hybrid(integrand,initial_value,dt):
        for x in accum(initial_value,scale_stream(integrand,dt)):
            yield x
    

    此混合版本使用 Scheme 中的流组合,并且仅避免尾递归。这仍然是 pythonic 并且 python 在 itertools 模块中包含了各种其他使用迭代的好方法。正如你所问,他们都“尊重流的懒惰性格”。

    最后是第一个递归流示例的所有代码,其中大部分来自伯克利参考:

    class Stream(object):
            """A lazily computed recursive list."""
            def __init__(self, first, compute_rest, empty=False):
                self.first = first
                self._compute_rest = compute_rest
                self.empty = empty
                self._rest = None
                self._computed = False
            @property
            def rest(self):
                """Return the rest of the stream, computing it if necessary."""
                assert not self.empty, 'Empty streams have no rest.'
                if not self._computed:
                    self._rest = self._compute_rest()
                    self._computed = True
                return self._rest
            def __repr__(self):
                if self.empty:
                    return '<empty stream>'
                return 'Stream({0}, <compute_rest>)'.format(repr(self.first))
    
    Stream.empty = Stream(None, None, True)
    
    
    def cons_stream(a,b):
        return Stream(a,lambda : b)
    
    def add_streams(a,b):
        if a.empty or b.empty:
                return Stream.empty
        def compute_rest():
            return add_streams(a.rest,b.rest)
        return Stream(a.first+b.first,compute_rest)
    
    def scale_stream(a,scale):
        if a.empty:
                return Stream.empty
        def compute_rest():
            return scale_stream(a.rest,scale)
        return Stream(a.first*scale,compute_rest)
    
    def make_integer_stream(first=1):
          def compute_rest():
            return make_integer_stream(first+1)
          return Stream(first, compute_rest)
    
    def truncate_stream(s, k):
            if s.empty or k == 0:
                return Stream.empty
            def compute_rest():
                return truncate_stream(s.rest, k-1)
            return Stream(s.first, compute_rest)
    
    def stream_to_list(s):
            r = []
            while not s.empty:
                r.append(s.first)
                s = s.rest
            return r
    
    def integral(integrand,initial_value,dt):
        placeholder = Stream(initial_value,lambda : None)
        int_rec = cons_stream(initial_value,
                              add_streams(scale_stream(integrand,dt),
                                          placeholder))
        placeholder._compute_rest = lambda:int_rec
        return int_rec
    
    a = truncate_stream(make_integer_stream(),5)
    print(stream_to_list(integral(a,8,.5)))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2022-11-30
      • 1970-01-01
      • 1970-01-01
      • 2011-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多