Streams 叁个操作简单的数据结构,很像数组或链接表

 

stream.js

stream.js
是二个非常小、完全独立的Javascript类库,它为您提供了1个新的Javascript数据结构:streams.

 

  1. <script src=’stream-min.js’></script>  

下载 stream.js 2Kb minified

streams是什么?

Streams
是贰个操作不难的数据结构,很像数组或链接表,但附加了部分非凡的能力。

它们有何尤其之处?

跟数组不平等,streams是二个有魔法的数据结构。它可以装载无限多的因素。是的,你没听错。他的那种魅力来自于拥有延后(lazily)执行的力量。那大致的术语完全能表明它们可以加载无穷多的要素。

入门

设若你愿意花10分钟的岁月来读书那篇小说,你对编程的认识有恐怕会被完全的变更(除非您有函数式编程的经验!)。请稍有耐心,让小编来先介绍一下streams支持的跟数组或链接表很接近的基本功效操作。然后我会像您介绍部分它抱有的可怜有趣的性状。

Stream 是一种容器。它能包容成分。你可以拔取 Stream.make
来让2个stream加载一些成分。只须要把想要的因素当成参数传进去:

 

  1. var s = Stream.make( 10, 20, 30 ); // s is now a stream containing 10, 20, and 30  

足足简单吗,以往 s 是一个有着二个成分的stream: 10, 20, and 30;
有各样的。我们得以动用 s.length() 来查看那些stream的长度,用
s.item( i ) 通过索引取出其中的某部成分。你还足以通过调用 s.head()
来拿到那个stream 的首先个要素。让大家实际操作一下:

 

  1. var s = Stream.make( 10, 20, 30 );  
  2. console.log( s.length() );  // outputs 3  
  3. console.log( s.head() );    // outputs 10  
  4. console.log( s.item( 0 ) ); // exactly equivalent to the line above  
  5. console.log( s.item( 1 ) ); // outputs 20  
  6. console.log( s.item( 2 ) ); // outputs 30  

本页面已经加载了那一个 stream.js
类库。借使您想运维那个事例或协调写几句,打开你的浏览器的Javascript控制台直接运营就行了。

咱俩后续,大家也得以使用 new Stream() 或 直接使用 Stream.make()
来构造多个空的stream。你可以行使 s.tail()
方法来获取stream里除了头个因素外的剩余全部因素。若是您在贰个空stream上调用
s.head()s.tail() 方法,会抛出3个老大。你可以行使 s.empty()
来检查一个stream是还是不是为空,它回到 truefalse

 

  1. var s = Stream.make( 10, 20, 30 );  
  2. var t = s.tail();         // returns the stream that contains two items: 20 and 30  
  3. console.log( t.head() );  // outputs 20  
  4. var u = t.tail();         // returns the stream that contains one item: 30  
  5. console.log( u.head() );  // outputs 30  
  6. var v = u.tail();         // returns the empty stream  
  7. console.log( v.empty() ); // prints true  

诸如此类做可以打印出3个stream里的装有因素:

 

  1. var s = Stream.make( 10, 20, 30 );  
  2. while ( !s.empty() ) {  
  3.     console.log( s.head() );  
  4.     s = s.tail();  
  5. }  

我们有个简易的法门来促成那个: s.print()
将会打印出stream里的享有因素。

用它们还是可以做哪些?

另一个简便的效益是 Stream.range( min, max ) 函数。它会回去一个带有有从
minmax 的自然数的stream。

 

  1. var s = Stream.range( 10, 20 );  
  2. s.print(); // prints the numbers from 10 to 20  

在这几个stream上,你可以接纳 map, filter, 和 walk 等功能。
s.map( f ) 接受二个参数 f,它是二个函数,
stream里的有所因素都将会被f处理3回;它的重回值是通过那个函数处理过的stream。所以,举个例子,你可以用它来达成让您的
stream 里的数字翻倍的成效:

 

  1. function doubleNumber( x ) {  
  2.     return 2 * x;  
  3. }  
  4.   
  5. var numbers = Stream.range( 10, 15 );  
  6. numbers.print(); // prints 10, 11, 12, 13, 14, 15  
  7. var doubles = numbers.map( doubleNumber );  
  8. doubles.print(); // prints 20, 22, 24, 26, 28, 30  

很酷,不是吗?相似的, s.filter( f )
也接受一个参数f,是三个函数,stream里的装有因素都将因此那些函数处理;它的重临值也是个stream,但只包罗能让f函数重回true的元素。所以,你可以用它来过滤到你的stream里某个特定的要素。让我们来用那么些方法在事先的stream基础上构建3个只含有奇数的新stream:

 

 

  1. function checkIfOdd( x ) {  
  2.     if ( x % 2 == 0 ) {  
  3.         // even number  
  4.         return false;  
  5.     }  
  6.     else {  
  7.         // odd number  
  8.         return true;  
  9.     }  
  10. }  
  11. var numbers = Stream.range( 10, 15 );  
  12. numbers.print();  // prints 10, 11, 12, 13, 14, 15  
  13. var onlyOdds = numbers.filter( checkIfOdd );  
  14. onlyOdds.print(); // prints 11, 13, 15  

很得力,不是吗?最终的一个s.walk( f )主意,也是经受三个参数f,是3个函数,stream里的持有因素都要由此那几个函数处理,但它并不会对那一个stream做其他的影响。大家打印stream里全体因素的想法有了新的落到实处方式:

 

  1. function printItem( x ) {  
  2.     console.log( ‘The element is: ‘ + x );  
  3. }  
  4. var numbers = Stream.range( 10, 12 );  
  5. // prints:  
  6. // The element is: 10  
  7. // The element is: 11  
  8. // The element is: 12  
  9. numbers.walk( printItem );  

再有3个很有用的函数:
s.take( n ),它回到的stream只含有原始stream里第前n个成分。当用来截取stream时,那很有用:

 

  1. var numbers = Stream.range( 10, 100 ); // numbers 10…100  
  2. var fewerNumbers = numbers.take( 10 ); // numbers 10…19  
  3. fewerNumbers.print();  

除此以外一些一蹴而就的东西:s.scale( factor )
会用factor(因子)乘以stream里的拥有因素; s.add( t ) 会让 stream s
各个成分和stream
t里对应的要素相加,重返的是相加后的结果。让大家来看多少个例子:

 

  1. var numbers = Stream.range( 1, 3 );  
  2. var multiplesOfTen = numbers.scale( 10 );  
  3. multiplesOfTen.print(); // prints 10, 20, 30  
  4. numbers.add( multiplesOfTen ).print(); // prints 11, 22, 33  

即使大家方今见到的都以对数字举行操作,但stream里能够装载其余的东西:字符串,布尔值,函数,对象;甚至其余的数组或stream。不过,请留意早晚,stream里不能装载一些卓绝的值:null
undefined

想本人体现你的吸引力!

距今,让我们来拍卖无穷多。你不必要往stream添加无穷多的元素。例如,在Stream.range( low, high )以此艺术中,你可以忽略掉它的第四个参数,写成
Stream.range( low )
这种状态下,数据没有了上限,于是那一个stream里就装载了装有从 low
到无穷大的自然数。你也足以把low参数也不经意掉,这几个参数的缺省值是1。这种情景中,Stream.range()回来的是具备的自然数。

 

那需求用上你无穷多的内存/时间/处理能力呢?

不,不会。那是最卓越的一对。你可以运维这一个代码,它们跑的充足快,就像是一个经常的数组。下边是3个打印从
1 到 10 的例证:

 

  1. var naturalNumbers = Stream.range(); // returns the stream containing all natural numbers from 1 and up  
  2. var oneToTen = naturalNumbers.take( 10 ); // returns the stream containing the numbers 1…10  
  3. oneToTen.print();  

您在骗人

是的,小编在骗人。关键是您可以把这么些社团想成无穷大,那就引入了一种新的编程范式,一种致力于不难的代码,让您的代码比日常的命令式编程更易于精晓、更靠近自然数学的编程范式。那些Javascript类库自身就非常的短小;它是遵从这种编程范式设计出来的。让我们来多用一用它;大家协会多个stream,分别装载全体的奇数和持有的偶数。

 

  1. var naturalNumbers = Stream.range(); // naturalNumbers is now 1, 2, 3, …  
  2. var evenNumbers = naturalNumbers.map( function ( x ) {  
  3.     return 2 * x;  
  4. } ); // evenNumbers is now 2, 4, 6, …  
  5. var oddNumbers = naturalNumbers.filter( function ( x ) {  
  6.     return x % 2 != 0;  
  7. } ); // oddNumbers is now 1, 3, 5, …  
  8. evenNumbers.take( 3 ).print(); // prints 2, 4, 6  
  9. oddNumbers.take( 3 ).print(); // prints 1, 3, 5  

很酷,不是吧?作者没说大话,stream比数组的效益更强大。未来,请容忍自个儿几分钟,让本身来多介绍一些有关stream的事情。你可以使用
new Stream() 来创建多少个空的stream,用
new Stream( head, functionReturningTail )
来创立贰个非空的stream。对于那些非空的stream,你传入的首先个参数成为这一个stream的头成分,而第二个参数是3个函数,它回到stream的尾巴(二个蕴涵有盈余全体因素的stream),很只怕是一个空的stream。怀疑吗?让我们来看2个例证:

 

  1. var s = new Stream( 10, function () {  
  2.     return new Stream();  
  3. } );  
  4. // the head of the s stream is 10; the tail of the s stream is the empty stream  
  5. s.print(); // prints 10  
  6. var t = new Stream( 10, function () {  
  7.     return new Stream( 20, function () {  
  8.         return new Stream( 30, function () {  
  9.             return new Stream();  
  10.         } );  
  11.     } );  
  12. } );  
  13. // the head of the t stream is 10; its tail has a head which is 20 and a tail which  
  14. // has a head which is 30 and a tail which is the empty stream.  
  15. t.print(); // prints 10, 20, 30  

没事找事吗?直接用Stream.make( 10, 20, 30 )就足以做那个。可是,请留意,那种艺术大家得以轻松的打造大家的无边大stream。让大家来做三个可以无穷无尽的stream:

 

  1. function ones() {  
  2.     return new Stream(  
  3.         // the first element of the stream of ones is 1…  
  4.         1,  
  5.         // and the rest of the elements of this stream are given by calling the function ones() (this same function!)  
  6.         ones  
  7.     );  
  8. }  
  9.   
  10. var s = ones();      // now s contains 1, 1, 1, 1, …  
  11. s.take( 3 ).print(); // prints 1, 1, 1  

请留意,假诺你在四个最好大的stream上应用
s.print(),它会无休无止的打印下去,最后耗尽你的内存。所以,你最幸亏采取s.print()前先s.take( n )。在3个无穷大的stream上使用s.length()也是空洞的,全部,不要做这个操作;它会招致四个无尽的大循环(试图到达二个无尽的stream的尽头)。可是对于无穷大stream,你可以运用s.map( f )

s.filter( f )。然而,s.walk( f )对于无穷大stream也是不佳用。全体,某些业务你要铭记;
对于无穷大的stream,一定要采纳s.take( n )取出有限的片段。

让大家看看能或不能够做一些更有趣的事体。还有2个好玩的能创造包括自然数的stream方式:

 

 

  1. function ones() {  
  2.     return new Stream( 1, ones );  
  3. }  
  4. function naturalNumbers() {  
  5.     return new Stream(  
  6.         // the natural numbers are the stream whose first element is 1…  
  7.         1,  
  8.         function () {  
  9.             // and the rest are the natural numbers all incremented by one  
  10.             // which is obtained by adding the stream of natural numbers…  
  11.             // 1, 2, 3, 4, 5, …  
  12.             // to the infinite stream of ones…  
  13.             // 1, 1, 1, 1, 1, …  
  14.             // yielding…  
  15.             // 2, 3, 4, 5, 6, …  
  16.             // which indeed are the REST of the natural numbers after one  
  17.             return ones().add( naturalNumbers() );  
  18.         }   
  19.     );  
  20. }  
  21. naturalNumbers().take( 5 ).print(); // prints 1, 2, 3, 4, 5  

密切的读者会发觉怎么新结构的stream的第1参数是多个赶回尾部的函数、而不是底部本人的缘由了。那种方法可以透过延迟底部截取的操作来防备举行进入无穷尽的执行周期。

让大家来看2个更扑朔迷离的例子。上边的是给读者留下的2个演习,请提议上面那段代码是做什么样的?

 

  1. function sieve( s ) {  
  2.     var h = s.head();  
  3.     return new Stream( h, function () {  
  4.         return sieve( s.tail().filter( function( x ) {  
  5.             return x % h != 0;  
  6.         } ) );  
  7.     } );  
  8. }  
  9. sieve( Stream.range( 2 ) ).take( 10 ).print();  

请一定要花些时日能明白那段代码的用途。除非有函数式编程经验,大部分的程序员都会发觉那段代码很难驾驭,所以,如果您不可以马上看出来,不要觉得颓废。给您或多或少提醒:找出被打印的stream的头成分是怎么。然后找出第2个成分是怎么着(余下的因素的头成分);然后第10个因素,然后第8个。那几个函数的名目也能给你有些唤起。借使你对那种难题感兴趣,那时还有局地

如果你真的想不出那段代码是做怎么着的,你就运维一下它,自身看一看!那样您就很简单通晓它是怎么做的了。

致敬

Streams
实际上不是一个新的想法。很多的函数式的编程语言都协理那种个性。所谓‘stream’是Scheme语言里的叫法,Scheme是LISP语言的一种方言。Haskell语言也支持不过大列表(list)。那几个’take’,
‘tail’, ‘head’, ‘map’ 和 ‘filter’
名字都源于于Haskell语言。Python和别的众多中言语中也设有即使分歧但很一般的那种概念,它们都被称作”发生器(generators)”。

那一个考虑来函数式编程社区里早已流传了很久了。然则,对于绝当先50%的Javascript程序员来说却是一个很新的概念,尤其是那么些尚未函数式编程经验的人。

此地很多的事例和创意都以根源 Structure and Interpretation of Computer
Programs

这本书。若是您喜爱这么些想法,小编中度推荐你读一读它;那本书能够在网上免费得到。它也是作者付出那个Javascript类库的新意来源于。

倘诺您欣赏其它语法情势的stream,你可以试一下linq.js,或许,若是你采用node.js, node-lazy
恐怕更契合您。

万一您只要喜欢 CoffeeScript 的话, 迈克尔 Blume 正在把 stream.js 移植到
CoffeeScript 上,创造出
coffeestream

感激您的翻阅!

本身梦想您能抱有收获,并欣赏上
stream.js。那些类库是免费的,所以,如果您喜爱它,或它能在某方面提供了帮扶,你能够考虑替我买一杯热巧克力饮料
(小编不喝咖啡) 只怕
写信给笔者。假如你打算那样做,请写清你是何地人,做什么的。作者很欢乐收集世界各省的图纸,所以,信中请附上你在你的城市里拍的相片!