zoukankan      html  css  js  c++  java
  • 每天看一片代码系列(一):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又可以进一步地划分,所以不可避免地通过递归来实现各种操作。对于应对无限长的数列来说是一个有效的方法,如果把它和数据结构中的链表关联起来,我们就明朗多了。

  • 相关阅读:
    Codeforces Round #650 (Div. 3)
    C. Count Triangles
    A National Pandemic (思维 + 树链剖分模版)
    扫描线专题
    扫描线模版
    莫队模版
    JS设计模式 -- 4种创建型模式
    滑动窗口的最大值 -- 单调队列
    JS链表实现栈和队列
    js数组扁平化的几种实现方式
  • 原文地址:https://www.cnblogs.com/cubika/p/4437402.html
Copyright © 2011-2022 走看看