Stream (lazy sequences)是一种非常强大的数据结构,他只在我们使用一个元素的时候才会真正进行计算。这个性质就允许实现无限长的序列!比如整数的Stream。
Stream这种数据结构在函数式语言中很常见,比如scala中可以这样实现一个无限的Fibonacci数列
val fibs: Stream[BigInt] = BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map { n => n._1 + n._2 }
在c++语言中并没有这样的支持,这篇博客里我要介绍如何用实现Stream。
Suspension
首先需要实现一个有用的功能: 一个可以暂停function执行的类Susp,Susp这个类可以保存一个function f, 在需要结果的时候再去进行实际的计算.
Susp.hpp
#ifndef SUSP_HPP
#define SUSP_HPP
#include <functional>
template<typename T>
class Susp {
public:
explicit Susp(std::function<T()> f)
: _thunk(thunkForce), _f(f)
{}
T const& get() { return _thunk(this); }
private:
T const& getMemo()
{
return _memo;
}
T const& setMemo()
{
_memo = _f();
_thunk = thunkGet;
return _memo;
}
static T const& thunkForce(Susp * susp)
{
return susp->setMemo();
}
static T const& thunkGet(Susp * susp)
{
return susp->getMemo();
}
T const& (*_thunk)(Susp *);
std::function<T()> _f;
mutable T _memo;
};
#endif
构造函数需要一个返回类型为T无需参数的函数f为参数。Susp的成员包括_f,f的复制;_memo用来保存计算过的结果;thunk一个函数指针通过修改这个指针的值可以达到第一次调用get的时候进行计算,之后对get的调用仅仅读取缓存。
Stream
Stream可以是空容器,或者包含一个Cell。一个Cell,为了达到可以获得一个value和Stream去掉第一个元素之后的尾巴的目的,需要包含一个value和一个Stream。
因为在概念上Stream和Cell都是immutable的,所以一个Stream包含一个表示Cell的shared_ptr,是安全的。
stream.hpp
#ifndef STREAM_HPP
#define STREAM_HPP
#include "Susp.hpp"
#include <memory>
#include <functional>
template<class T> class Stream;
template<class T>
class Cell
{
public:
Cell();
Cell(T v, Stream<T> const& tail);
explicit Cell(T v);
T val() const;
Stream<T> pop_front() const;
private:
T _v;
Stream<T> _tail;
};
template<typename T>
class Stream
{
public:
Stream(std::function<Cell<T>()> f): _lazyCell(std::make_shared<Susp<Cell<T> > >(Susp<Cell<T> >(f))) {}
Stream() {}
~Stream() {}
Stream(Stream const & stm) = default;
Stream(Stream && stm): _lazyCell(std::move(stm._lazyCell))
{
}
Stream& operator=(Stream && stm)
{
_lazyCell = std::move(stm._lazyCell);
return *this;
}
T get() const
{
return _lazyCell->get().val();
}
Stream<T> pop_front() const
{
return _lazyCell->get().pop_front();
}
bool is_empty() const
{
return !_lazyCell;
}
private:
std::shared_ptr<Susp<Cell<T> > > _lazyCell;
};
template<class T> Cell<T>::Cell() {}
template<class T> Cell<T>::Cell(T v, Stream<T> const& tail): _v(v), _tail(tail) {}
template<class T> Cell<T>::Cell(T v) : _v(v) {}
template<class T> T Cell<T>::val() const
{
return _v;
}
template<class T> Stream<T> Cell<T>::pop_front() const
{
return _tail;
}
#endif
一个Stream包含一个可以获得Cell的Susp,在 第一次调用get或者pop_front的时候,才真正计算Cell。
看一个简单的例子Application.cpp
#include "stream.hpp"
#include <iostream>
Stream<int> ints(int n, int m)
{
if (n > m)
{
return Stream<int>();
}
return Stream<int>([n, m]() {
return Cell<int>(n, ints(n+1, m));
});
}
int main(int argc, char ** argv)
{
Stream<int> stm = ints(1,10);
while (!stm.is_empty()) {
int a = stm.get();
stm = stm.pop_front();
std::cout << a << std::endl;
}
return 0;
}
在例子中,构建了一个从n开始到m的Stream。Stream的初始化函数返回n,和一个从n+1到m的Stream。
附上Makefile
CC=g++
CFLAGS=-std=c++11 -c
LDFLAGS=
SOURCES=Application.cpp
OBJECTS=$(SOURCES:.cpp=.o)
EXECUTABLE=stream
all: $(SOURCES) $(EXECUTABLE)
$(EXECUTABLE): $(OBJECTS)
$(CC) $(LDFLAGS) $(OBJECTS) -o $@
.cpp.o:
$(CC) $(CFLAGS) $< -o $@
.PHONY: clean
clean:
rm -f *~ *.o stream
这篇博客是我读博客的学习记录详情请见 http://bartoszmilewski.com/2014/04/21/getting-lazy-with-c/
在part 2中,我会记录Stream的用法和性质。