每天看一片代码系列(一):stream.js

简介

stream.js是一个小型的js库,用于处理stream相关的操作。这里的stream是指一种数据结构,它像数组一样,可以放置多个类型的数据,但是并不限制长度,甚至可以达到无限长。可以对该数据结构进行检索、修改、追加等种种操作。由于其长度不限这一特性,使得它与通常意义下的数据结构有明显的区别。

API

stream提供的API包含三种。

第一种是创建类。包括:

  1. new Stream(head, functionReturingTail) 第二个参数是一个放回除第一个元素之外剩下的元素的方法,所以它可能也返回一个Stream,即通过一个或多个现有的Stream来构造一个新的Stream。当然,这两个参数都不是必须的,如果都不填,那就是一个空的stresam。
  2. Stream.make() 相当于一个静态方法,比较简单,直接将数据填入里面就行了。
  3. Stream.range(from, to) 返回从from到to中间所有数字组成的Stream,如果不填,默认为自然数。

第二种是查询类,包括:

  1. head() 返回流中的第一个item
  2. item(index) 返回index位置上的item
  3. tail() 返回除了第一个位置上剩下的元素
  4. empty() 是否为空

第三种是遍历操作类型,包括:

  1. map() 类似于数组中的map,其实就是一个映射的过程
  2. walk() 同样是遍历,但是并不会对元素产生直接的影响
  3. filter() 过滤掉某些元素,这几个都接受参数为函数的情形
  4. take(n)  取前n个元素
  5. scale(n) 将每一个元素都乘以n

栗子

递归调用构造Stream

如前所述,构造函数的第二个参数可以是一个返回Stream的方法,那如果该方法中调用了自身呢?也就构成了无限长的具有相同数字的流。

function ones() {
    return new Stream(
        // the first element of the stream of ones is 1...
        1,
        // and the rest of the elements of this stream are given by calling the function ones() (this same function!)
        ones
    );
} 

Stream的相加

一个无限长的Stream和另一些无限长的Stream相加会出现什么情况?如下:

function ones() {
    return new Stream( 1, ones );
}
function naturalNumbers() {
    return new Stream(
        // the natural numbers are the stream whose first element is 1...
        1,
        function () {
            // and the rest are the natural numbers all incremented by one
            // which is obtained by adding the stream of natural numbers...
            // 1, 2, 3, 4, 5, ...
            // to the infinite stream of ones...
            // 1, 1, 1, 1, 1, ...
            // yielding...
            // 2, 3, 4, 5, 6, ...
            // which indeed are the REST of the natural numbers after one
            return ones().add( naturalNumbers() );
        }
    );
}  

虽然从名字上可以看出这是自然数,但初次看上去确实有些confusing。不妨列一下式子:

设 result = (1, n1, n2, n3, ....)

又有(n1, n2, n3, ...) = (1, 1, 1, ...) + (1, n1, n2, n3, ...)

那么 n1 = 2, n2 = 1 + n1 = 3, n3 = 1 + n2 = 4, ... 所以下一个数总是比上一个数多1, 那么就是自然数了!

代码解析

初看代码时我都惊了,虽然API有那么多,但整个代码仅仅有两百来行,还真是挺小。

首先定义了一个类Stream, 设置了一下它的head和获取剩余部分用到的函数。

function Stream( head, tailPromise ) {
    if ( typeof head != ‘undefined‘ ) {
        this.headValue = head;
    }
    if ( typeof tailPromise == ‘undefined‘ ) {
        tailPromise = function () {
            return new Stream();
        };
    }
    this.tailPromise = tailPromise;
}

接下来就直接在prototype上开放API了,注意由于它无限长的特性,因此不能像遍历数组一样去一个一个索引,而是像链表一样,通过next指针去获取,这里就是通过tail()方法去获取。

var s = this;
        while ( n != 0 ) {
            --n;
            try {
                s = s.tail();
            }
            catch ( e ) {
                throw new Error(‘Item index does not exist in stream.‘);
            }
        }

当然,如果你对一个无限长的Stream做map,它当然不会真正的一个一个去无穷尽地map完,而是重新构造了一个Stream,定义了新的规则,即head = f(head), tail = tail().map(f),所以只有在取数据的时候才会执行这里面的函数。

 map: function( f ) {
        if ( this.empty() ) {
            return this;
        }
        var self = this;
        return new Stream( f( this.head() ), function () {
            return self.tail().map( f );
        } );
    }

其他的API方法也是一样,我认为实现上最大的特点就是:递归调用!一个流无论再长,我都把它划分为两部分:一个元素为头部,剩余的为尾部。然后要做的就是对头部操作一下,对尾部操作一下即可。比如构造rarnge为low - high的流时只需制定头部为low,尾部为low+1到high的流就行了。

    return new Stream( low, function () {
        return Stream.range( low + 1, high );
    } );

Stream 就是一种 headElement + tailStream的结构,由于tailStream又可以进一步地划分,所以不可避免地通过递归来实现各种操作。对于应对无限长的数列来说是一个有效的方法,如果把它和数据结构中的链表关联起来,我们就明朗多了。

时间: 2024-12-17 19:02:14

每天看一片代码系列(一):stream.js的相关文章

每天看一片代码系列(三):codepen上一个音乐播放器的实现

今天我们看的是一个使用纯HTML+CSS+JS实现音乐播放器的例子,效果还是很赞的: codePen地址 HTML部分 首先我们要思考一下,一个播放器主要包含哪些元素.首先要有播放的进度信息,还有播放/暂停或者上一首下一首等必要的按钮,同时还要显示一些当前播放的音乐名称等信息.播放多首歌曲时,要显示播放列表...因此,从语义上可以构造出基本的HTML结构: // 背景区块,用于显示当前播放音乐的图片 <div class='background' id='background'></di

每天看一片代码系列(四):layzr.js,处理图片懒加载的库

所谓图片的懒加载,即只有当图片处于或者接近于当前视窗时才开始加载图片.该库的使用方法非常简单: var layzr = new Layzr({ attr: 'data-layzr', // attr和retinaAttr必须至少有一个,用于指定对应的图片 retinaAttr: 'data-layzr-retina', // 一般对应的图像比attr要高清 threshold: 0, // 距离视窗的距离为多少时开始加载 callback: null // 回调函数 }); 代码解析 首先是包装

每天看一片代码系列(二):WebSocket-Node

简介 我们都知道,websocket主要是通过在浏览器和服务端建立长连接,继而实现二者的相互数据通信.不同于HTTP的轮询,它不会有大量无效的HTTP消息交换,从而节省了花销.websocket其实就是双通道的TCP连接. 很明显地,整个工作分为两个步骤,即创建连接和发送数据.那么连接是怎么建立的呢?其实只需要在浏览器和服务器端做一个握手的动作就可以了.而这个握手其实还是一个HTTP请求,只是接下来的工作就和HTTP没关系了. 这个HandShake的请求消息大致为: GET /chat HTT

老二牛车Axure夜话: Axure嵌入代码系列视频教程汇总贴

老二牛车Axure夜话: Axure嵌入代码系列视频教程汇总贴 Axure嵌入代码系列视频教程汇总贴 嵌入代码系列视频教程之QQ一键加群 嵌入代码系列视频教程之新浪微博秀 嵌入代码系列视频教程之腾讯微博秀 嵌入代码系列视频教程之嵌入百度分享 嵌入代码系列视频教程之嵌入视频 嵌入代码系列视频教程之嵌入百度地图

iOS开发一行代码系列:一行搞定输入框

最近总结了下开发过程中常用的功能,发现有时候我在做重复性的劳动.于是决定把常用的功能抽出来,方便下次使用. 我的想法是:用最少的代码来解决问题.于是写了一些常用的工具类,名字就叫一行代码系列吧...好像挺挫的.. 大致内容有: 1.一行搞定输入框 2.一行搞定网络请求 3.一行搞定上下拉刷新(会自动判断是上拉还是下拉还是两者并存) 4.一行搞定数据库(最近还在写,功能已经基本实现) 5.一行搞定图片保存 6.一行搞定定位 7.一行搞定网络状况变化 8.一行搞定X(功能小集合) 一行搞定输入框 输

[2014.5.18][SuperPixel] 也看Greg.Mori.代码的配置与执行

SuperPixel最初由Xiaofeng Ren提出(ICCV 2003),但我在网络上尚未找到有关这个最初想法的源代码:比较容易获得的倒是Greg Mori(CVPR 2004,ICCV 2005)基于Xiaofeng Ren算法做的代码https://www.cs.sfu.ca/~mori/research/superpixels/.代码包分为32bit版和64bit版. 这个代码用到了C与M混合编程,并非是拿来即可运行的,在代码和matlab的配置上还是有一定需要注意的问题. 搜索了下,

gulp入坑系列(2)——初试JS代码合并与压缩

在上一篇里成功安装了gulp到项目中,现在来测试一下gulp的合并与压缩功能 gulp入坑系列(1)--安装gulp(传送门):http://www.cnblogs.com/YuuyaRin/p/6159809.html 在之前建立的项目中写入,在根目录新建js文件夹,并在文件夹中新建两个js文件,代码如下: index.js: var index={}; index={ test:function(argument){ console.log('test'); } } index.test()

为什么学习C语言这么久,看的懂代码,做不出题没项目

我看得懂别人的程序,可是我自己却写不出来,我应该怎么办啊?你了解这些嘛? 你只是能从别人书写的代码知道每一步都做些什么吧? 你明白别人的解题思路吗? 你知道别人为什么要用那样的算法吗? 如果你看着题目,你能写出实现同一功能的代码吗? 你能知道别人在写这个程序的过程中会遇到什么样的问题吗? 你能在看了别人的程序之后写出比他好的代码吗? 你能用另一种算法写出实现同一程序的代码吗? 你真的能看懂别人的程序吗?创一个小群,供大家学习交流聊天如果有对学C++方面有什么疑惑问题的,或者有什么想说的想聊的大家

看懂此文,不再困惑于 JS 中的事件设计

看懂此文,不再困惑于 JS 中的事件设计 今天刚在关注的微信公众号看到的文章,关于JS事件的,写的很详细也很容易理解,相关的知识点都有总结到,看完就有种很舒畅的感觉,该串起来的知识点都串起来了.反正一字节:爽. 作者:aitangyong 链接:blog.csdn.net/aitangyong/article/details/43231111 抽空学习了下javascript和jquery的事件设计,收获颇大,总结此贴,和大家分享. (一)事件绑定的几种方式 javascript给DOM绑定事件