Skip to content
Browse files

first commit

  • Loading branch information...
0 parents commit 455d18ccae8f6c18daed33d0014fd8bb0cb835cc @flori committed Jul 25, 2009
Showing with 2,406 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +56 −0 CHANGES
  3. +340 −0 COPYING
  4. +39 −0 README
  5. +96 −0 Rakefile
  6. +1 −0 VERSION
  7. +121 −0 doc-main.txt
  8. +101 −0 examples/examples.rb
  9. +17 −0 examples/hamming.rb
  10. +34 −0 examples/pi.rb
  11. +17 −0 examples/sieve.rb
  12. +13 −0 install.rb
  13. +23 −0 lazylist.gemspec
  14. +662 −0 lib/lazylist.rb
  15. +125 −0 lib/lazylist/enumerable.rb
  16. +35 −0 lib/lazylist/enumerator_queue.rb
  17. +137 −0 lib/lazylist/list_builder.rb
  18. +61 −0 lib/lazylist/thread_queue.rb
  19. +8 −0 lib/lazylist/version.rb
  20. +4 −0 make_doc.rb
  21. +20 −0 tests/runner.rb
  22. +389 −0 tests/test.rb
  23. +104 −0 tests/test_enumerable.rb
3 .gitignore
@@ -0,0 +1,3 @@
+*.sw[pon]
+coverage
+pkg
56 CHANGES
@@ -0,0 +1,56 @@
+2009-07-23 * 0.3.2 * Some cleanup.
+ * Build a gemspec file.
+ * Added end_list method to end a lazy list during the
+ evaluation of a filter predicate.
+2007-11-27 * 0.3.1 * Comron Sattari <comron@gmail.com> reported a bug
+ concerning false values in a lazy list. This problem
+ should be corrected now.
+2007-05-05 * 0.3.0 * Major work on the implementation:
+ - Supports list comprehensions for infinite list like
+ Haskell does by offering a simple DSL.
+ - Added cartesian products to LazyList.
+ Many thanks to Reginald Braithwaite <reg@braythwayt.com>
+ for his interesting ideas & email discussion about this
+ topic. Be sure to check out his blog under
+ http://weblog.raganwald.com/, where he writes about all
+ kinds of other interesting stuff.
+ * A nice side effect of the changes necessary for the
+ new implementation is that the first value of a list is
+ not forced until requested. This also causes a bit of
+ space&time waste, but hardware is getting better and
+ cheaper, isn't it?
+ * Now there is more than one Empty object, be careful not to
+ use equal? to compare them. It's better to use LazyList#empty?
+ for this.
+ * Switched from the continuation based ReadQueue to a Thread
+ based one, that should be a bit faster.
+ * Better organization of the file structure.
+ * Added version information.
+2005-12-23 * 0.2.2 * Added append and + (the binary append case) to append lazy
+ lists to each other.
+2005-12-05 * 0.2.1 * Fixed a documentation bug, reported by Gary Wright <at2002@mac.com>.
+ * enable/disable index reference cache added.
+ * LazyList#sublist added to generate sublists that
+ share elements with their "super" lists.
+2005-10-08 * 0.2.0 * Lots of changes (without backwards compatibility):
+ - Now the usual methods from the Ruby Enumerable module
+ should be much better supported.
+ - In general there are more finite lazy lists returned
+ from methods (for example: lazylist[a, b] or
+ lazylist[a..b]) instead of arrays.
+ - mapper, filter and combine methods give an 'obsoleted'
+ warning now, you better use map, select, and zip from
+ now on.
+2005-09-26 * 0.1.3 * Fixed some Ruby 1.9 incompatibilities.
+ * Added default arguments for drop&take.
+ * I've decided to pollute the Kernel module with the #list
+ method. Sorry, if you don't like it. ;)
+2004-09-30 * 0.1.2 * Added combine method.
+ * The ref method now uses a hash to cache return values.
+ This is not what I would still call a list datastructure.
+ It increases memory consumption (as if that would matter
+ anymore), too. But it speeds up things a great deal, if
+ you persist on using indexes into a list.
+ * Rakefile added
+ * Supports Rubygems now
+2003-09-12 * 0.1.1 * Initial Release
340 COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
39 README
@@ -0,0 +1,39 @@
+Installation
+============
+
+Just type into the command line as root:
+
+# ruby install.rb
+
+Or if you prefer to use rake, try:
+
+# rake install
+
+Or if you want to use rubygems just type this and rubygems fetches the gem and
+installs it for you:
+
+# gem install lazylist
+
+Documentation
+=============
+
+The API documentatin of this library is can be produced by typing:
+$ ruby make_doc.rb
+
+Or with rake:
+$ rake doc
+
+You should also look into the files in the examples directory to get an idea
+how this library is used. It is also interesting to examine the tests directory
+for this reason.
+
+Author
+======
+
+Florian Frank <flori@ping.de>
+
+License
+=======
+
+GNU General Public License (GPL), Version 2
+
96 Rakefile
@@ -0,0 +1,96 @@
+begin
+ require 'rake/gempackagetask'
+rescue LoadError
+end
+require 'rake/clean'
+require 'rbconfig'
+include Config
+
+PKG_NAME = 'lazylist'
+PKG_VERSION = File.read('VERSION').chomp
+PKG_FILES = FileList['**/*'].exclude(/^(doc|CVS|pkg|coverage)/)
+CLEAN.include 'coverage', 'doc'
+
+desc "Installing library"
+task :install do
+ ruby 'install.rb'
+end
+
+desc "Creating documentation"
+task :doc do
+ ruby 'make_doc.rb'
+end
+
+desc "Run unit tests"
+task :test do
+ ruby %{-Ilib tests/runner.rb}
+end
+
+desc "Testing library with coverage"
+task :coverage do
+ sh 'rcov -x tests -Ilib tests/runner.rb'
+end
+
+if defined? Gem
+ spec_src =<<GEM
+# -*- encoding: utf-8 -*-
+Gem::Specification.new do |s|
+ s.name = '#{PKG_NAME}'
+ s.version = '#{PKG_VERSION}'
+ s.summary = "Implementation of lazy lists for Ruby"
+ s.description = ""
+
+ s.add_dependency('dslkit', '~> 0.2')
+
+ s.files = #{PKG_FILES.to_a.sort.inspect}
+
+ s.require_path = 'lib'
+
+ s.has_rdoc = true
+ s.extra_rdoc_files << 'doc-main.txt'
+ s.rdoc_options << '--main' << 'doc-main.txt'
+ s.test_files << 'tests/runner.rb'
+
+ s.author = "Florian Frank"
+ s.email = "flori@ping.de"
+ s.homepage = "http://#{PKG_NAME}.rubyforge.org"
+ s.rubyforge_project = "#{PKG_NAME}"
+ end
+GEM
+
+ desc 'Create a gemspec file'
+ task :gemspec do
+ File.open("#{PKG_NAME}.gemspec", 'w') do |f|
+ f.puts spec_src
+ end
+ end
+
+ spec = eval(spec_src)
+ Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.need_tar = true
+ pkg.package_files += PKG_FILES
+ end
+end
+
+desc m = "Writing version information for #{PKG_VERSION}"
+task :version do
+ puts m
+ File.open(File.join('lib', PKG_NAME, 'version.rb'), 'w') do |v|
+ v.puts <<EOT
+class LazyList
+ # LazyList version
+ VERSION = '#{PKG_VERSION}'
+ VERSION_ARRAY = VERSION.split(/\\./).map { |x| x.to_i } # :nodoc:
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
+end
+EOT
+ end
+end
+
+desc "Default"
+task :default => [ :version, :gemspec, :test ]
+
+desc "Prepare a release"
+task :release => [ :clean, :version, :gemspec, :package ]
1 VERSION
@@ -0,0 +1 @@
+0.3.2
121 doc-main.txt
@@ -0,0 +1,121 @@
+== LazyList - Implementation of lazy lists for Ruby
+
+=== Description
+
+This class implements lazy lists (or streams) for Ruby. Such lists avoid the
+computation of values which aren't needed for some computation. So it's
+possible to define infinite lists with a limited amount of memory. A value
+that hasn't been used yet is calculated on the fly and saved into the list.
+A value which is used for a second time is computed only once and just read
+out of memory for the second usage.
+
+=== Author
+
+Florian Frank mailto:flori@ping.de
+
+=== License
+
+This is free software; you can redistribute it and/or modify it under the
+terms of the GNU General Public License Version 2 as published by the Free
+Software Foundation: www.gnu.org/copyleft/gpl.html
+
+=== Download
+
+The latest version of this library can be downloaded at
+
+* http://rubyforge.org/frs?group_id=394
+
+The homepage of this library is located at
+
+* http://lazylist.rubyforge.org
+
+=== Example
+
+To compute the square numbers with a lazy list you can define one as
+
+ sq = LazyList.tabulate(1) { |x| x * x }
+
+or in the much nicer list builder syntax:
+
+ sq = list { x * x }.where :x => 1..Infinity
+
+Now it's possible to get the first 10 square numbers by calling
+LazyList#take
+
+ sq.take(10)
+ ===>[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
+
+To compute the first 10 square numbers and do something with them you can
+call the each method with:
+
+ sq.each(10) { |x| puts x }
+
+To compute every square number and do something with them you can call the
+"each" method without an argument:
+
+ sq.each { |x| puts x }
+
+Notice that calls to each without an argument will not return if applied to
+infinite lazy lists.
+
+You can also use indices on lazy lists to get the values at a certain range:
+
+ sq[ 0..9 ] or sq[0, 10]
+ ===>[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
+
+To spare memory it's possible to throw away every element after it was
+fetched:
+
+ sq.take!(1) => [1]
+ sq.take!(1) => [4]
+
+Of course it's also possible to compute more complex lists like the Fibonacci
+sequence:
+
+ fib = LazyList.tabulate(0) { |x| x < 2 ? 1 : fib[x-2] + fib[x-1] }
+
+ fib[100] => 573147844013817084101
+computes the 99th Fibonacci number. (We always start with index 0.)
+ fib[101] => 927372692193078999176
+computes the 100th Fibonacci number. The already computed values are reused
+to compute this result. That's a very transparent way to get memoization for
+sequences that require heavy computation.
+
+You can also use the zip method to create fib:
+
+ fib = list(1, 1) { fib.zip(fib.drop) { |a, b| a + b } }
+
+Another way to create the Fibonacci sequence with the build method is this:
+
+ fib = list(1, 1) { build { a + b }.where(:a => fib, :b => fib.drop(1)) }
+
+You can create lazy lists that are based on arbitrary Enumerables, so can for
+example wrap your passwd file in one pretty easily:
+
+ pw = LazyList[ File.new("/etc/passwd") ]
+
+Call grep to find the users root and flori:
+pw.grep /^(root|flori):/ => ["root:x:0:0:...\n",... ]
+
+In this case the whole passwd file is slurped into the memory. If
+you use
+ pw.find { |x| x =~ /^root:/ } => "root:x:0:0:root:/root:/bin/bash\n"
+instead, only every line until the root line is loaded into the memory.
+
+To create more complex lazy lists, you can build them from already existing
+lazy lists.
+
+Natural numbers:
+ naturals = LazyList.from(1)
+
+Odd Numbers > 100:
+ odds = list { x }.where(:x => naturals) { x % 2 === 1 && x > 100 }
+
+Alternative definition of square numbers:
+ squares = build { odds[0, x].inject(0) { |s, y| s + y } }.where :x => naturals
+
+=== References
+
+A very good introduction into lazy lists can be found in the scheme bible
+Structure and Interpretation of Computer Programs (SICP)
+[http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-24.html#%25_sec_3.5]
101 examples/examples.rb
@@ -0,0 +1,101 @@
+require 'lazylist'
+$:.unshift 'examples'
+require 'pi'
+
+puts "Random number lazy list (infinite)"
+def rand(s = 666)
+ current = ((s * 1103515245 + 12345) / 65536) % 32768
+ list(current) { rand(current) }
+end
+r = rand(666)
+r.each(10) { |x| print x, " " } ; puts
+dice = r.map { |x| 1 + x % 6 }
+dice.each(10) { |x| print x, " " } ; puts
+coin = r.map { |x| x % 2 == 0 ? :head : :tail }
+coin.each(10) { |x| print x, " " } ; puts
+
+puts "Prime number lazy list with select (10000..1000000)"
+prime = LazyList[10000..1000000].select do |x|
+ not (2..Math.sqrt(x).to_i).find { |d| x % d == 0 }
+end
+prime.each(10) { |x| print x, " " } ; puts
+p prime[1]
+puts
+
+puts "Squared prime numbers with map"
+prime.map { |x| x ** 2 }.each(5) { |x| print x, " " } ; puts
+puts
+
+puts "Lazy Lists from mathematical sequences"
+a196 = LazyList.iterate(35) { |x| x + x.to_s.reverse.to_i }
+a196.each(10) { |x| print x, " " } ; puts
+hailstone = LazyList.iterate(7) { |x| x % 2 == 0 ? x / 2 : 3 * x + 1 }
+hailstone.each(20) { |x| print x, " " } ; puts
+terras = LazyList.iterate(7) { |x| x % 2 == 0 ? x / 2 : (3 * x + 1) / 2 }
+terras.each(20) { |x| print x, " " } ; puts
+wolfram = LazyList.iterate(1) do |x|
+ ((3.0 / 2) * (x % 2 == 0 ? x : x + 1)).to_i
+end
+wolfram.each(15) { |x| print x, " " } ; puts
+puts
+
+puts "Fibonacci lazy list with recursion"
+fib = LazyList.tabulate(0) { |x| x < 2 ? 1 : fib[x-2] + fib[x-1] }
+fib.each(10) { |x| print x, " " } ; puts
+p fib[100]
+puts
+
+fib = nil
+puts "Fibonacci lazy list with zip"
+fib = list(1, 1) { fib.zip(fib.drop) { |a, b| a + b } }
+fib.each(10) { |x| print x, " " } ; puts
+p fib[100]
+puts
+
+fib = nil
+puts "Fibonacci lazy list with a list and a build call"
+fib = list(1, 1) { build { a + b }.where(:a => fib, :b => fib.drop) }
+fib.each(10) { |x| print x, " " } ; puts
+p fib[100]
+puts
+
+puts "Sum up odd numbers lazylist to get a squares stream"
+odd = LazyList[1..Infinity].select { |x| x % 2 == 1 }
+puts odd
+squares = LazyList.tabulate(0) do |x|
+ (0..x).inject(0) { |s, i| s + odd[i] }
+end
+puts squares
+squares.each(10) { |x| print x, " " } ; puts
+puts squares
+puts odd
+puts
+
+puts "Lazy lists from io objects"
+me = LazyList.io(File.new($0)) { |io| io.readline }
+me.each(6) { |line| puts line }
+p me[60]
+puts me.length
+puts
+
+me = LazyList[File.new($0)]
+me.each(6) { |line| puts line }
+p me[66]
+puts me.length
+puts
+
+
+p PI.take(10)
+
+def window(l, n, m = 0)
+ list([ l.take(n) * '', m ]) { window(l.drop, n, m + 1) }
+end
+
+w = window(PI, 6)
+index = w.find { |(x, i)| x == '999999' and break i }
+puts "Found Feynman point #{PI.take_span(index, 6)} at #{index}!"
+
+puts "Proof that PI is the number of the beast"
+w = window(PI, 3)
+index = w.find { |(x, i)| x == '666' and break i }
+puts "Found #{PI.take_span(index, 3)} at #{index}!"
17 examples/hamming.rb
@@ -0,0 +1,17 @@
+require 'lazylist'
+
+# Computes the hamming sequence: that is the sequence of natural numbers, whose
+# prime divisors are all <= 5.
+hamming = list(1) do
+ hamming.map { |x| 2 * x }.merge(
+ hamming.map { |x| 3 * x }.merge(
+ hamming.map { |x| 5 * x }))
+end
+
+max = (ARGV.shift || 100).to_i
+hamming.each(max) do |x|
+ print x, " "
+ STDOUT.flush
+end
+puts
+print hamming[1000], ", ", hamming[1001], "\n"
34 examples/pi.rb
@@ -0,0 +1,34 @@
+require 'lazylist'
+
+# This spigot algorithm computes an unbounded number of digits of Pi and
+# uses a lazy list to save them.
+#
+# References:
+# - Jeremy Gibbons (2003). An Unbounded Spigot Algorithm for the Digits of Pi.
+# http://web.comlab.ox.ac.uk/oucl/work/jeremy.gibbons/publications/spigot.pdf
+#
+
+def f(q,r,t, k)
+ n = (3 * q + r) / t
+ if n == (4 * q + r) / t
+ list(n) { f(10 * q, 10 * (r - n * t), t, k) }
+ else
+ f(q * k, q * (4 * k + 2) + r * (2 * k + 1), t * (2 * k + 1), k + 1)
+ end
+end
+
+PI = f(1, 0, 1, 1) # Setup my lazy list
+
+if $0 == __FILE__
+ max = ARGV.empty? ? nil : ARGV.shift.to_i
+ sum = PI[0, 1000].inject(0) do |s, i| s += i end
+ puts "Sum of the first 1000 digitis of pi: #{sum}"
+ puts "500th digit using memoized computation: #{PI[499]}"
+
+ puts "Printing #{max ? "the first #{max}" : "all the "} digits of pi:" # vim-uff: "
+ PI.each!(max) do |x|
+ STDOUT.print x
+ STDOUT.flush
+ end
+ puts
+end
17 examples/sieve.rb
@@ -0,0 +1,17 @@
+require 'lazylist'
+
+# Sieve or Eratosthenes with filters on Lazy Lists. It has a very nice
+# notation, but is a real memory and cpu hog. Enjoy!
+def primes(l = LazyList.from(2))
+ current, rest = l.head, l.tail
+ list(current) do
+ primes rest.select { |x| (x % current) != 0 }
+ end
+end
+
+max = (ARGV.shift || 100).to_i
+primes.each(max) do |x|
+ print x, " "
+ STDOUT.flush
+end
+puts
13 install.rb
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+
+require 'rbconfig'
+include Config
+require 'fileutils'
+include FileUtils::Verbose
+
+libdir = CONFIG["sitelibdir"]
+install("lib/lazylist.rb", libdir)
+mkdir_p subdir = File.join(libdir, 'lazylist')
+for f in Dir['lib/lazylist/*.rb']
+ install(f, subdir)
+end
23 lazylist.gemspec
@@ -0,0 +1,23 @@
+# -*- encoding: utf-8 -*-
+Gem::Specification.new do |s|
+ s.name = 'lazylist'
+ s.version = '0.3.2'
+ s.summary = "Implementation of lazy lists for Ruby"
+ s.description = ""
+
+ s.add_dependency('dslkit', '~> 0.2')
+
+ s.files = ["CHANGES", "COPYING", "README", "Rakefile", "VERSION", "examples", "examples/examples.rb", "examples/hamming.rb", "examples/pi.rb", "examples/sieve.rb", "install.rb", "lazylist.gemspec", "lib", "lib/lazylist", "lib/lazylist.rb", "lib/lazylist/enumerable.rb", "lib/lazylist/enumerator_queue.rb", "lib/lazylist/list_builder.rb", "lib/lazylist/thread_queue.rb", "lib/lazylist/version.rb", "make_doc.rb", "tests", "tests/runner.rb", "tests/test.rb", "tests/test_enumerable.rb"]
+
+ s.require_path = 'lib'
+
+ s.has_rdoc = true
+ s.extra_rdoc_files << 'doc-main.txt'
+ s.rdoc_options << '--main' << 'doc-main.txt'
+ s.test_files << 'tests/runner.rb'
+
+ s.author = "Florian Frank"
+ s.email = "flori@ping.de"
+ s.homepage = "http://lazylist.rubyforge.org"
+ s.rubyforge_project = "lazylist"
+ end
662 lib/lazylist.rb
@@ -0,0 +1,662 @@
+class LazyList
+ require 'dslkit'
+ require 'lazylist/list_builder'
+ require 'lazylist/version'
+
+ require 'lazylist/enumerable'
+ include LazyList::Enumerable
+
+ # Exceptions raised by the LazyList implementation.
+ class Exception < ::StandardError; end
+
+ if defined?(::Enumerator)
+ require 'lazylist/enumerator_queue.rb'
+ else
+ require 'lazylist/thread_queue.rb'
+ end
+
+ Promise = DSLKit::BlankSlate.with :inspect, :to_s # :nodoc:
+ # A promise that can be evaluated on demand (if forced).
+ class Promise
+ def initialize(&block)
+ @block = block
+ end
+
+ # Return the value of this Promise.
+ def __value__
+ case v = @block.call
+ when Promise
+ v.__value__
+ else
+ v
+ end
+ end
+
+ # Redirect all missing methods to the value of this Promise.
+ def method_missing( *args, &block )
+ __value__.__send__( *args, &block )
+ end
+ end
+
+ # A promise that can be evaluated on demand (if forced), that caches its
+ # value.
+ class MemoPromise < Promise
+ # Return the value of this Promise and memoize it for later access.
+ def __value__
+ if defined?(@value)
+ @value
+ else
+ @value = super
+ end
+ end
+ end
+
+ # This module contains module functions, that are added to LazyList and it's
+ # instances.
+ module ModuleFunctions
+ # Delay the value of _block_ to a Promise.
+ def delay(&block)
+ MemoPromise.new(&block)
+ end
+
+ # Force the delayed _obj_ to evaluate to its value.
+ def force(obj)
+ case obj
+ when Promise
+ force obj.__value__
+ else
+ obj
+ end
+ end
+
+ # Returns a LazyList, that consists of the mixed elements of the lists
+ # _lists without any special order.
+ def mix(*lists)
+ return empty if lists.empty?
+ first = lists.shift
+ if lists.empty?
+ first
+ elsif first.empty?
+ mix(*lists)
+ else
+ LazyList.new(first.head) do
+ t = first.tail
+ lists = lists.push t unless t.empty?
+ mix(*lists)
+ end
+ end
+ end
+ end
+ extend ModuleFunctions
+ include ModuleFunctions
+
+ # Returns an empty list.
+ def self.empty
+ new(nil, nil)
+ end
+
+ # Returns an empty list.
+ def empty
+ @klass ||= self.class
+ @klass.new(nil, nil)
+ end
+
+ # Creates a new LazyList element. The tail can be given either as
+ # second argument or as block.
+ def initialize(head = nil, tail = nil, &block)
+ @cached = true
+ @ref_cache = {}
+ if tail
+ if block
+ raise LazyList::Exception,
+ "Use block xor second argument for constructor"
+ end
+ @head, @tail = head, tail
+ elsif block
+ @head, @tail = head, delay(&block)
+ else
+ @head, @tail = nil, nil
+ end
+ end
+
+ # Set this to false, if index references into the lazy list shouldn't be
+ # cached for fast access (while spending some memory on this). This value
+ # defaults to true.
+ attr_writer :cached
+
+ # Returns true, if index references into the lazy list are cached for fast
+ # access to the referenced elements.
+ def cached?
+ !!@cached
+ end
+
+ # If the constant Empty is requested return a new empty list object.
+ def self.const_missing(id)
+ if id == :Empty
+ new(nil, nil)
+ else
+ super
+ end
+ end
+
+ # Returns the value of this element.
+ attr_writer :head
+ protected :head=
+
+ # Returns the head of this list by computing its value.
+ def head
+ @head = force @head
+ end
+
+ # Returns the head of this list without forcing it.
+ def peek_head
+ @head
+ end
+ protected :peek_head
+
+ # Writes a tail value.
+ attr_writer :tail
+ protected :tail=
+
+ # Returns the next element by computing its value if necessary.
+ def tail
+ @tail = force @tail
+ end
+
+ # Returns the tail of this list without forcing it.
+ def peek_tail
+ @tail
+ end
+ protected :peek_tail
+
+ # Identity lambda expression, mostly used as a default.
+ Identity = lambda { |x| x }
+
+ # This block returns true.
+ All = lambda { |x| true }
+
+ # Create an array tuple from argument list.
+ Tuple = lambda { |*t| t }
+
+ # Returns true, if a less than b.
+ LessThan = lambda { |a, b| a < b }
+
+ # Swaps _you_ and _me_ and returns an Array tuple of both.
+ SwapTuple = lambda { |you, me| [ me, you ] }
+
+ # Returns a lazy list which is generated from the Enumerable a or
+ # LazyList.span(a, n), if n was given as an argument.
+ def self.[](a, n = nil)
+ case
+ when n
+ span(a, n)
+ when a.respond_to?(:to_io)
+ io(a.to_io)
+ when a.respond_to?(:to_ary)
+ from_queue(a.to_ary)
+ when Range === a
+ from_range(a)
+ when a.respond_to?(:each)
+ from_enum(a)
+ else
+ list(a)
+ end
+ end
+
+ # Generates a lazy list from any data structure e which
+ # responds to the #each method.
+ def self.from_enum(e)
+ from_queue ReadQueue.new(e)
+ end
+
+ # Generates a lazyx list by popping elements from a queue.
+ def self.from_queue(rq)
+ return empty if rq.empty?
+ new(delay { rq.shift }) { from_queue(rq) }
+ end
+
+ # Generates a lazy list from a Range instance _r_.
+ def self.from_range(r)
+ if r.exclude_end?
+ if r.begin >= r.end
+ empty
+ else
+ new(delay { r.begin }) { from_range(r.begin.succ...r.end) }
+ end
+ else
+ case
+ when r.begin > r.end
+ empty
+ when r.begin == r.end
+ new(delay { r.begin }) { empty }
+ else
+ new(delay { r.begin }) { from_range(r.begin.succ..r.end) }
+ end
+ end
+ end
+
+ # Generates a finite lazy list beginning with element a and spanning
+ # n elements. The data structure members have to support the
+ # successor method succ.
+ def self.span(a, n)
+ if n > 0
+ new(delay { a }) { span(a.succ, n - 1) }
+ else
+ empty
+ end
+ end
+
+ # Generates a lazy list which tabulates every element beginning with n
+ # and succeding with succ by calling the Proc object f or the given block.
+ # If none is given the identity function is computed instead.
+ def self.tabulate(n = 0, &f)
+ f = Identity unless f
+ new(delay { f[n] }) { tabulate(n.succ, &f) }
+ end
+
+ # Returns a list of all elements succeeding _n_ (that is created by calling
+ # the #succ method) and starting from _n_.
+ def self.from(n = 0)
+ tabulate(n)
+ end
+
+ # Generates a lazy list which iterates over its previous values
+ # computing something like: f(i), f(f(i)), f(f(f(i))), ...
+ def self.iterate(i = 0, &f)
+ new(delay { i }) { iterate(f[i], &f) }
+ end
+
+ # Generates a lazy list of a give IO-object using a given
+ # block or Proc object to read from this object.
+ def self.io(input, &f)
+ if f
+ input.eof? ? empty : new(delay { f[input] }) { io(input, &f) }
+ else
+ input.eof? ? empty : new(delay { input.readline }) { io(input) }
+ end
+ end
+
+ # Returns the sublist, constructed from the Range _range_ indexed elements,
+ # of this lazy list.
+ def sublist_range(range)
+ f = range.first
+ l = range.exclude_end? ? range.last - 1 : range.last
+ sublist_span(f, l - f + 1)
+ end
+
+ # Returns the sublist, that spans _m_ elements starting from the _n_-th
+ # element of this lazy list, if _m_ was given. If _m_ is non­positive, the
+ # empty lazy list LazyList::Empty is returned.
+ #
+ # If _m_ wasn't given returns the _n_ long sublist starting from the first
+ # (index = 0) element. If _n_ is non­positive, the empty lazy list
+ # LazyList::Empty is returned.
+ def sublist_span(n, m = nil)
+ if not m
+ sublist_span(0, n)
+ elsif m > 0
+ l = ref(n)
+ self.class.new(delay { l.head }) { l.tail.sublist_span(0, m - 1) }
+ else
+ empty
+ end
+ end
+
+ # Returns the result of sublist_range(n), if _n_ is a Range. The result of
+ # sublist_span(n, m), if otherwise.
+ def sublist(n, m = nil)
+ if n.is_a? Range
+ sublist_range(n)
+ else
+ sublist_span(n, m)
+ end
+ end
+
+ def set_ref(n, value)
+ return value unless cached?
+ @ref_cache[n] = value
+ end
+ private :set_ref
+
+ # Returns the n-th LazyList-Object.
+ def ref(n)
+ if @ref_cache.key?(n)
+ return @ref_cache[n]
+ end
+ s = self
+ i = n
+ while i > 0 do
+ if s.empty?
+ return set_ref(n, self)
+ end
+ s.head # force the head
+ s = s.tail
+ i -= 1
+ end
+ set_ref(n, s)
+ end
+ protected :ref
+
+ # If n is a Range every element in this range is returned.
+ # If n isn't a Range object the element at index n is returned.
+ # If m is given the next m elements beginning the n-th element are
+ # returned.
+ def [](n, m = nil)
+ case
+ when Range === n
+ sublist(n)
+ when n < 0
+ nil
+ when m
+ sublist(n, m)
+ else
+ ref(n).head
+ end
+ end
+
+ # Iterates over all elements. If n is given only n iterations are done.
+ # If self is a finite lazy list each returns also if there are no more
+ # elements to iterate over.
+ def each(n = nil)
+ s = self
+ if n
+ until n <= 0 or s.empty?
+ yield s.head
+ s = s.tail
+ n -= 1 unless n.nil?
+ end
+ else
+ until s.empty?
+ yield s.head
+ s = s.tail
+ end
+ end
+ s
+ end
+
+ # Similar to LazyList#each but destroys elements from past iterations perhaps
+ # saving some memory. Try to call GC.start from time to time in your block.
+ def each!(n = nil)
+ s = self
+ if n
+ until n <= 0 or s.empty?
+ yield s.head
+ s = s.tail
+ n -= 1 unless n.nil?
+ @head, @tail = s.head, s.tail
+ end
+ else
+ until s.empty?
+ yield s.head
+ s = s.tail
+ @head, @tail = s.head, s.tail
+ end
+ end
+ self
+ end
+
+ # Merges this lazy list with the other. It uses the &compare block to decide
+ # which elements to place first in the result lazy list. If no compare block
+ # is given lambda { |a,b| a < b } is used as a default value.
+ def merge(other, &compare)
+ compare ||= LessThan
+ case
+ when empty?
+ other
+ when other.empty?
+ self
+ when compare[head, other.head]
+ self.class.new(head) { tail.merge(other, &compare) }
+ when compare[other.head, head]
+ self.class.new(other.head) { merge(other.tail, &compare) }
+ else
+ self.class.new(head) { tail.merge(other.tail, &compare) }
+ end
+ end
+
+ # Append this lazy list to the _*other_ lists, creating a new lists that
+ # consists of the elements of this list and the elements of the lists other1,
+ # other2, ... If any of the lists is infinite, the elements of the following
+ # lists will never occur in the result list.
+ def append(*other)
+ if empty?
+ if other.empty?
+ empty
+ else
+ other.first.append(*other[1..-1])
+ end
+ else
+ self.class.new(delay { head }) { tail.append(*other) }
+ end
+ end
+
+ alias + append
+
+ # Takes the next n elements and returns them as an array.
+ def take(n = 1)
+ result = []
+ each(n) { |x| result << x }
+ result
+ end
+
+ alias first take
+
+ # Takes the _range_ indexes of elements from this lazylist and returns them
+ # as an array.
+ def take_range(range)
+ range.map { |i| ref(i).head }
+ end
+
+ # Takes the m elements starting at index n of this lazy list and returns them
+ # as an array.
+ def take_span(n, m)
+ s = ref(n)
+ s ? s.take(m) : nil
+ end
+
+ # Takes the next n elements and returns them as an array. It destroys these
+ # elements in this lazy list. Also see #each! .
+ def take!(n = 1)
+ result = []
+ each!(n) { |x| result << x }
+ result
+ end
+
+ # Drops the next n elements and returns the rest of this lazy list. n
+ # defaults to 1.
+ def drop(n = 1)
+ each(n) { }
+ end
+
+ # Drops the next n elements, destroys them in the lazy list and
+ # returns the rest of this lazy list. Also see #each! .
+ def drop!(n = 1)
+ each!(n) { }
+ end
+
+ # Return the last +n+ elements of the lazy list. This is only sensible if the
+ # lazy list is finite of course.
+ def last(n = 1)
+ to_a.last n
+ end
+
+ # Returns the size. This is only sensible if the lazy list is finite
+ # of course.
+ def size
+ inject(0) { |s,| s += 1 }
+ end
+
+ alias length size
+
+ # Returns true if this is the empty lazy list.
+ def empty?
+ self.peek_head == nil && self.peek_tail == nil
+ end
+
+ # Returns true, if this lazy list and the other lazy list consist of only
+ # equal elements. This is only sensible, if the lazy lists are finite and you
+ # can spare the memory.
+ def eql?(other)
+ other.is_a? self.class or return false
+ size == other.size or return false
+ to_a.zip(other.to_a) { |x, y| x == y or return false }
+ true
+ end
+ alias == eql?
+
+ # Inspects the list as far as it has been computed by returning
+ # a string of the form [1, 2, 3,... ].
+ def inspect
+ return '[]' if empty?
+ result = '['
+ first = true
+ s = self
+ seen = {}
+ until s.empty? or Promise === s.peek_head or seen[s.__id__]
+ seen[s.__id__] = true
+ if first
+ first = false
+ else
+ result << ', '
+ end
+ result << s.head.inspect
+ Promise === s.peek_tail and break
+ s = s.tail
+ end
+ unless empty?
+ if first
+ result << '... '
+ elsif !s.empty?
+ result << ',... '
+ end
+ end
+ result << ']'
+ end
+
+ alias to_s inspect
+
+ # Returns one "half" of the product of this LazyList and the _other_:
+ # list(1,2,3).half_product(list(1,2)).to_a # => [[1, 1], [2, 1], [2, 2], [3, 1], [3, 2]]
+ # _block_ can be used to yield to every pair generated and create a new list
+ # element out of it. It defaults to Tuple.
+ def half_product(other, &block)
+ block ||= Tuple
+ if empty? or other.empty?
+ empty
+ else
+ mix(
+ delay { zip(other, &block) },
+ delay { tail.half_product(other, &block) }
+ )
+ end
+ end
+
+ def swap(block) # :nodoc:
+ lambda { |you, me| block[me, you] }
+ end
+ private :swap
+
+ # Returns the (cartesian) product of this LazyList instance and the _other_.
+ # _block_ can be used to yield to every pair generated and create a new list
+ # element out of it, but it's useful to at least return the default Tuple
+ # from the block.
+ def product(other, &block)
+ if empty? or other.empty?
+ empty
+ else
+ other_block =
+ if block
+ swap block
+ else
+ block = Tuple
+ SwapTuple
+ end
+ mix(
+ delay { half_product(other, &block)},
+ delay { other.tail.half_product(self, &other_block) }
+ )
+ end
+ end
+ alias * product
+
+ # Returns the cartesian_product of this LazyList and the others as a LazyList
+ # of Array tuples. A block can be given to yield to all the created tuples
+ # and create a LazyList out of the block results.
+ def cartesian_product(*others) # :yields: tuple
+ case
+ when empty?
+ self
+ when others.empty?
+ block_given? ? map(&Proc.new) : map
+ else
+ product = others[1..-1].inject(product(others[0])) do |intermediate, list|
+ intermediate.product(list) do |existing, new_element|
+ existing + [ new_element ]
+ end
+ end
+ if block_given?
+ block = Proc.new
+ product.map { |tuple| block[*tuple] }
+ else
+ product
+ end
+ end
+ end
+
+ # This module contains methods that are included into Ruby's Kernel module.
+ module ObjectMethods
+ # A method to improve the user friendliness for creating new lazy lists, that
+ # cannot be described well with LazyList.iterate or LazyList.tabulate.
+ #
+ # - list without any arguments, returns the empty lazy list LazyList::Empty.
+ # - list { x / y } returns a LazyList::ListBuilder object for a list
+ # comprehension, that can be transformed into a LazyList by calling the
+ # LazyList::ListBuilder#where method.
+ # - list(x) returns the lazy list with only the element x as a member,
+ # list(x,y) returns the lazy list with only the elements x and y as a
+ # members, and so on.
+ # - list(x) { xs } returns the lazy list with the element x as a head
+ # element, and that is continued with the lazy list xs as tail. To define an
+ # infinite lazy list of 1s you can do:
+ # ones = list(1) { ones } # => [1,... ]
+ # To define all even numbers directly, you can do:
+ # def even(n = 0) list(n) { even(n + 2) } end
+ # and then:
+ # e = even # => [0,... ]
+ def list(*values, &block)
+ values_empty = values.empty?
+ result = LazyList[values]
+ if block_given?
+ if values_empty
+ result = LazyList::ListBuilder.create_comprehend(&block)
+ else
+ result.instance_eval do
+ ref(values.size - 1)
+ end.instance_variable_set(:@tail, LazyList.delay(&block))
+ end
+ end
+ result
+ end
+
+ # This method returns a Lazylist::ListBuilder instance for tuplewise building
+ # of lists like the zip method does. This method call
+ #
+ # build { x + y }.where(:x => 1..3, :y => 1..3)
+ #
+ # returns the same list [ 2, 4, 6 ] as this expression does
+ #
+ # LazyList[1..3].zip(LazyList[1..3]) { |x, y| x + y }
+ def build(&block)
+ LazyList::ListBuilder.create_build(&block)
+ end
+ end
+
+ class ::Object
+ unless const_defined? :Infinity
+ Infinity = 1 / 0.0
+ end
+
+ include LazyList::ObjectMethods
+ include LazyList::Enumerable::ObjectMethods
+ end
+end
125 lib/lazylist/enumerable.rb
@@ -0,0 +1,125 @@
+class LazyList
+ module Enumerable
+ include ::Enumerable
+
+ # Returns two lazy lists, the first containing the elements of this lazy
+ # list for which the block evaluates to true, the second containing the
+ # rest.
+ def partition(&block)
+ return select(&block), reject(&block)
+ end
+
+ # Returns a sorted version of this lazy list. This method should only be
+ # called on finite lazy lists or it will never return. Also see
+ # Enumerable#sort.
+ def sort # :yields: a, b
+ LazyList.from_enum(super)
+ end
+
+ # Returns a sorted version of this lazy list. This method should only be
+ # called on finite lazy lists or it will never return. Also see
+ # Enumerable#sort_by.
+ def sort_by # :yields: obj
+ LazyList.from_enum(super)
+ end
+
+ # Calls _block_ with two arguments, the element and its index, for each
+ # element of this lazy list. If _block_ isn't given, the method returns a
+ # lazy list that consists of [ element, index ] pairs instead.
+ def each_with_index(&block)
+ if block
+ each_with_index.each { |x| block[x] }
+ else
+ i = -1
+ map { |x| [ x, i += 1 ] }
+ end
+ end
+
+ # Returns the lazy list, that contains all the given _block_'s return
+ # values, if it was called on every
+ # self[i], others[0][i], others[1][i],... others[others.size - 1][i]
+ # for i in 0..Infinity. If _block_ wasn't given
+ # this result will be the array
+ # [self[i], others[0][i], others[1][i],... ]
+ # and a lazy list of those arrays is returned.
+ def zip(*others, &block)
+ if empty? or others.any? { |o| o.empty? }
+ empty
+ else
+ block ||= Tuple
+ self.class.new(delay { block[head, *others.map { |o| o.head }] }) do
+ tail.zip(*others.map { |o| o.tail }, &block)
+ end
+ end
+ end
+
+ # obsoleted by #zip
+ def combine(other, &operator)
+ warn "method 'combine' is obsolete - use 'zip'"
+ zip(other, &operator)
+ end
+
+ # Returns a lazy list every element of this lazy list for which
+ # pattern === element is true. If the optional _block_ is supplied,
+ # each matching element is passed to it, and the block's result becomes
+ # an element of the returned lazy list.
+ def grep(pattern, &block)
+ result = select { |x| pattern === x }
+ block and result = result.map(&block)
+ result
+ end
+
+ # Returns a lazy list of all elements of this lazy list for which the block
+ # is false (see also +Lazylist#select+).
+ def reject
+ select { |obj| !yield(obj) }
+ end
+
+ # Returns a lazy list of all elements of this lazy list for which _block_
+ # is true.
+ def select(&block)
+ block = All unless block
+ s = self
+ ended = catch(:end_list) do
+ until s.empty? or block[s.head]
+ s = s.tail
+ end
+ end
+ if s.empty? or ended == :end_list
+ empty
+ else
+ self.class.new(delay { s.head }) { s.tail.select(&block) }
+ end
+ end
+ alias find_all select
+
+ # obsoleted by #select
+ def filter(&p)
+ warn "method 'filter' is obsolete - use 'select'"
+ select(&p)
+ end
+
+ # Creates a new Lazylist that maps the block or Proc object f to every
+ # element in the old list.
+ def map(&f)
+ return empty if empty?
+ f = Identity unless f
+ self.class.new(delay { f[head] }) { tail.map(&f) }
+ end
+ alias collect map
+
+ # obsoleted by #map
+ def mapper(&f)
+ warn "method 'mapper' is obsolete - use 'map'"
+ map(&f)
+ end
+
+ module ObjectMethods
+ # This method can be used to end a list in a predicate that is used to
+ # filter the lazy list via the select, reject, or partition method.
+ def end_list
+ throw :end_list, :end_list
+ end
+ end
+ end
+end
35 lib/lazylist/enumerator_queue.rb
@@ -0,0 +1,35 @@
+class LazyList
+ # ReadQueue is the implementation of an read-only queue that only supports
+ # #shift and #empty? methods. It's used as a wrapper to encapsulate
+ # enumerables in lazy lists.
+ class ReadQueue
+ # Creates an ReadQueue object from an enumerable.
+ def initialize(enumerable)
+ @enum = enumerable.to_enum
+ @empty = false
+ shift
+ end
+
+ # Extracts the top element from the queue or nil if the queue is empty.
+ def shift
+ if @empty
+ nil
+ else
+ result = @next
+ @next = @enum.next
+ result
+ end
+ rescue StopIteration
+ @next = nil
+ @empty = true
+ result
+ end
+
+ alias pop shift # for backwards compatibility
+
+ # Returns true if the queue is empty.
+ def empty?
+ !@next and @empty
+ end
+ end
+end
137 lib/lazylist/list_builder.rb
@@ -0,0 +1,137 @@
+require 'dslkit'
+
+class LazyList
+ def do_build(lb, others) # :nodoc:
+ if empty? or others.any? { |o| o.empty? }
+ Empty
+ else
+ variables = [ head ].concat others.map { |o| o.head }
+ if lb.filter
+ if lb.filter[*variables]
+ self.class.new(lb.transform[*variables]) do
+ tail.do_build(lb, others.map { |o| o.tail })
+ end
+ else
+ tail.do_build(lb, others.map { |o| o.tail })
+ end
+ else
+ self.class.new(lb.transform[*variables]) do
+ tail.do_build(lb, others.map { |o| o.tail })
+ end
+ end
+ end
+ end
+
+ def do_comprehend(lb, others) # :nodoc:
+ if lb.filter
+ cartesian_product(*others).select do |variables|
+ lb.filter[*variables]
+ end.map do |variables|
+ lb.transform[*variables]
+ end
+ else
+ cartesian_product(*others).map do |variables|
+ lb.transform[*variables]
+ end
+ end
+ end
+
+ # This class encapsulates a list builder (ListBuilder), that can be transformed
+ # into a LazyList, by calling LazyList::ListBuilder#where.
+ class ListBuilder
+ # This class is a special kind of Proc object, that uses instance_eval to
+ # execute a code block.
+ class ListBuilderProc < Proc
+ include DSLKit::MethodMissingDelegator
+ include DSLKit::BlockSelf
+
+ # Creates a ListBuilderProc working on the list builder _lb_ using the Proc
+ # returned by lb.#{name}. _name_ has to be either :filter or :transform.
+ def initialize(lb, name, &block)
+ @name = name
+ @lb = lb
+ @method_missing_delegator = block_self(&block)
+ super(&@lb.__send__(@name))
+ end
+
+ # Call this ListBuilderProc instance with the arguments _args_, which have to be
+ # the ordered values of the variables.
+ def call(*args)
+ prepare_variables
+ @lb.variables.each_with_index do |var, i|
+ instance_variable_set "@#{var}", args[i]
+ end
+ instance_eval(&@lb.__send__(@name))
+ end
+ alias [] call
+
+ private
+
+ def prepare_variables
+ @variables_prepared and return
+ variables = @lb.variables
+ class << self; self; end.instance_eval do
+ attr_reader(*variables)
+ end
+ @variables_prepared = true
+ end
+ end
+
+ # Creates LazyList::ListBuilder instance. _mode_ has to be either :do_build
+ # or :do_comprehend.
+ def initialize(mode, &block)
+ @mode = mode
+ @transform = ListBuilderProc.new(self, :transform, &block)
+ end
+
+ # The variable names defined in this list builder.
+ attr_reader :variables
+
+ # The transform ListBuilderProc instance of this list builder.
+ attr_reader :transform
+
+ # The filter ListBuilderProc of this list builder or nil.
+ attr_reader :filter
+
+ # This method creates a LazyList instance from this list builder,
+ # using the _sources_ hash to fetch the variables from. _sources_ consists
+ # of the variable name and the values, that can be LazyList instances or
+ # otherwise they will be transformed into a LazyList with LazyList.[].
+ #
+ # It also takes a block, to filter the results by a boolean expression.
+ def where(sources = {}, &block)
+ @variables = []
+ generators = []
+ sources.each do |var, src|
+ @variables << var
+ generators << (src.is_a?(LazyList) ? src : LazyList[src])
+ end
+ if block_given?
+ @filter = ListBuilderProc.new(self, :filter, &block)
+ else
+ @filter = nil
+ end
+ generators.first.__send__(@mode, self, generators[1..-1])
+ end
+
+ # Return a (not much) nicer string representation of the list
+ # builder.
+ def to_s
+ "#<LazyList::ListBuilder>"
+ end
+ alias inspect to_s
+
+ class << self
+ # Used to support the build method
+ def create_build(&block)
+ new(:do_build, &block)
+ end
+
+ # Used to evaluate a list comprehension, usually if calling the list
+ # method with only a block.
+ def create_comprehend(&block)
+ new(:do_comprehend, &block)
+ end
+ end
+ end
+end
61 lib/lazylist/thread_queue.rb
@@ -0,0 +1,61 @@
+class LazyList
+ # ReadQueue is the implementation of an read-only queue that only supports
+ # #shift and #empty? methods. It's used as a wrapper to encapsulate
+ # enumerables in lazy lists.
+ class ReadQueue
+ # Creates an ReadQueue object from an enumerable.
+ def initialize(enumerable)
+ @data = []
+ @producer = Thread.new do
+ Thread.stop
+ begin
+ enumerable.each do |value|
+ old, Thread.critical = Thread.critical, true
+ begin
+ @data << value
+ @consumer.wakeup
+ Thread.stop
+ ensure
+ Thread.critical = old
+ end
+ end
+ rescue => e
+ @consumer.raise e
+ ensure
+ @consumer.wakeup
+ end
+ end
+ Thread.pass until @producer.stop?
+ end
+
+ # Extracts the top element from the queue or nil if the queue is
+ # empty.
+ def shift
+ if empty?
+ nil
+ else
+ @data.shift
+ end
+ end
+
+ alias pop shift # for backwards compatibility
+
+ # Returns true if the queue is empty.
+ def empty?
+ if @data.empty?
+ old, Thread.critical = Thread.critical, true
+ begin
+ @consumer = Thread.current
+ @producer.wakeup
+ Thread.stop
+ rescue ThreadError
+ ;
+ ensure
+ @consumer = nil
+ Thread.critical = old
+ end
+ end
+ @data.empty?
+ end
+ end
+end
8 lib/lazylist/version.rb
@@ -0,0 +1,8 @@
+class LazyList
+ # LazyList version
+ VERSION = '0.3.2'
+ VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
+end
4 make_doc.rb
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+puts "Creating documentation."
+system "rdoc -m doc-main.txt doc-main.txt #{Dir['lib/**/*.rb'] * ' '}"
20 tests/runner.rb
@@ -0,0 +1,20 @@
+#!/usr/bin/env ruby
+
+begin
+ require 'rubygems'
+rescue LoadError
+end
+
+$:.unshift File.expand_path(File.dirname($0))
+$:.unshift 'lib'
+$:.unshift 'tests'
+require 'test'
+require 'test_enumerable'
+
+class TS_AllTests
+ def self.suite
+ suite = Test::Unit::TestSuite.new 'All tests'
+ suite << TC_LazyList.suite
+ suite << TC_LazyEnumerable.suite
+ end
+end
389 tests/test.rb
@@ -0,0 +1,389 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'lazylist'
+require 'tempfile'
+
+class MyEnum
+ include Enumerable
+
+ def initialize(n)
+ @n = n
+ end
+
+ def each(&block)
+ (1..@n).each(&block)
+ end
+end
+
+class TC_LazyList < Test::Unit::TestCase
+ Empty = LazyList::Empty
+
+ def setup
+ @strings = LazyList.tabulate("a")
+ @natural = LazyList.tabulate(1)
+ @ones = LazyList.iterate(1) { 1 }
+ @oddp = lambda { |x| x % 2 == 1 }
+ @odd = @natural.select(&@oddp)
+ @evenp = lambda { |x| x % 2 == 0 }
+ @boolean = @natural.map { |x| x % 2 == 0}
+ @even = @natural.select(&@evenp)
+ @finite_inner0 = MyEnum.new(0)
+ @finite0 = LazyList[@finite_inner0]
+ @finite_inner1 = MyEnum.new(1)
+ @finite1 = LazyList[@finite_inner1]
+ @finite_inner10 = MyEnum.new(10)
+ @finite10 = LazyList[@finite_inner10]
+ @finite_span = LazyList.span("A", 10)
+ @finite_span_generated = ("A".."J").to_a
+ end
+
+ def test_constructor
+ ll1 = LazyList.new(:foo, Empty)
+ assert(!ll1.empty?)
+ ll2 = LazyList.new(:foo) { Empty }
+ assert(!ll2.empty?)
+ assert_raises(LazyList::Exception) do
+ ll3 = LazyList.new(:foo, :argh) { Empty }
+ end
+ end
+
+ def test_read_queue
+ @read_queue0 = LazyList::ReadQueue.new(1..0)
+ @read_queue1 = LazyList::ReadQueue.new(1..1)
+ @read_queue10 = LazyList::ReadQueue.new(1..10)
+ assert(@read_queue0.empty?)
+ assert_equal nil, @read_queue0.pop
+ assert(!@read_queue1.empty?)
+ assert_equal 1, @read_queue1.pop
+ assert(!@read_queue10.empty?)
+ for i in 1..10 do
+ assert_equal i, @read_queue10.pop
+ end
+ end
+
+ def test_finite
+ assert_equal @finite_inner0.to_a, @finite0.to_a
+ assert_equal @finite_inner1.to_a, @finite1.to_a
+ assert_equal @finite_inner10.to_a, @finite10.to_a
+ assert_equal @finite_span_generated, @finite_span.to_a
+ assert_equal @finite_span_generated, LazyList["A"..."K"].to_a
+ end
+
+ def test_size
+ assert_equal 0, @finite0.size
+ assert_equal 1, @finite1.size
+ assert_equal 10, @finite10.size
+ assert_equal 10, @finite_span.size
+ assert_equal 0, @finite0.length
+ assert_equal 1, @finite1.length
+ assert_equal 10, @finite10.length
+ assert_equal 10, @finite_span.length
+ end
+
+ def test_select
+ assert_equal 1, @odd[0]
+ assert_equal 3, @odd[1]
+ assert_equal 5, @odd[2]
+ assert_equal [3, 5, 7], @odd.take_range(1..3)
+ assert_equal [3, 5, 7, 9], @odd.take_span(1, 4)
+ assert_equal((1..19).select(&@oddp), @odd[0, 10].to_a)
+ assert_equal((1..10).to_a, @natural[0, 10].to_a)
+ assert_equal [ 1 ] * 10, @ones[0, 10].to_a
+ ends_with_a = @strings.select { |x| x[-1] == ?a }
+ assert_equal ends_with_a[0, 27].to_a,
+ [ "a", ("a".."z").map { |x| x + "a" } ].flatten
+ end
+
+ def test_map
+ id = @natural.map
+ assert_equal 1, id[0]
+ assert_equal 2, id[1]
+ assert_equal 3, id[2]
+ assert_equal((1..10).to_a, id[0, 10].to_a)
+ assert_equal((1..10).to_a, @natural[0, 10].to_a)
+ squaredf = lambda { |x| x ** 2 }
+ squared = @natural.map(&squaredf)
+ assert_equal 1, squared[0]
+ assert_equal 4, squared[1]
+ assert_equal 9, squared[2]
+ assert_equal((1..10).map(&squaredf), squared[0, 10].to_a)
+ assert_equal((1..10).to_a, @natural[0, 10].to_a)
+ strangef = lambda { |x| x * (x.unpack('c').first - 'a'.unpack('c').first + 1) }
+ strange = @strings.map(&strangef)
+ assert_equal "a", strange[0]
+ assert_equal "bb", strange[1]
+ assert_equal "ccc", strange[2]
+ assert_equal(("a".."z").map(&strangef), strange[0, 26].to_a)
+ assert_equal(("a".."z").to_a, @strings[0, 26].to_a)
+ end
+
+ def test_index
+ assert_equal nil, Empty[-1]
+ assert_equal nil, Empty[0]
+ assert_equal nil, Empty[1]
+ assert @natural.cached?
+ assert_equal nil, @natural[-1]
+ assert_equal nil, @natural[-1]
+ assert_equal 1, @natural[0]
+ assert_equal 1, @natural[0]
+ assert_equal 2, @natural[1]
+ assert_equal 2, @natural[1]
+ assert_equal nil, @natural[-1, 10]
+ assert_equal((1..10).to_a, @natural[0, 10].to_a)
+ assert_equal((6..15).to_a, @natural[5, 10].to_a)
+ assert_equal((1..1).to_a, @natural[0..0].to_a)
+ assert_equal((1..0).to_a, @natural[0..-1].to_a)
+ assert_equal((1...1).to_a, @natural[0...0].to_a)
+ assert_equal((1...0).to_a, @natural[0...-1].to_a)
+ assert_equal((1..10).to_a, @natural[0..9].to_a)
+ assert_equal((6..15).to_a, @natural[5..14].to_a)
+ assert_equal((1..10).to_a, @natural[0...10].to_a)
+ assert_equal((6..15).to_a, @natural[5...15].to_a)
+ end
+
+ def test_index_without_cache
+ Empty.cached = false
+ assert_equal nil, Empty[-1]
+ assert_equal nil, Empty[0]
+ assert_equal nil, Empty[1]
+ @natural.cached = false
+ assert !@natural.cached?
+ assert_equal nil, @natural[-1]
+ assert_equal nil, @natural[-1]
+ assert_equal 1, @natural[0]
+ assert_equal 1, @natural[0]
+ assert_equal 2, @natural[1]
+ assert_equal 2, @natural[1]
+ assert_equal nil, @natural[-1, 10]
+ end
+
+ def test_merge
+ natural = @even.merge(@odd)
+ assert_equal @natural[0, 10].to_a, natural[0, 10].to_a
+ natural = @odd.merge(@even)
+ assert_equal @natural[0, 10].to_a, natural[0, 10].to_a
+ double_list = @natural.merge(@natural) { |a,b| a <= b }
+ assert double_list[0, 10].to_a, (1..5).map { |x| [x, x] }.flatten
+ odd2 = @natural.select(&@oddp).drop(1)
+ some = @even.merge(odd2)
+ assert_equal @natural[1, 9].to_a, some[0, 9].to_a
+ more_ones = @ones.merge(@ones)
+ assert_equal [1] * 10, more_ones.take(10)
+ end
+
+ def test_take_drop
+ assert_equal [ ], @odd.take(0)
+ assert_equal [ 1, 3, 5 ], @odd.take(3)
+ assert_equal [ 1, 3, 5 ], @odd.take(3)
+ assert_equal [ ], @odd.take!(0)
+ assert_equal [ 1 ], @odd.take(1)
+ assert_equal [ 1 ], @odd.take!(1)
+ assert_equal [ 3, 5, 7 ], @odd.take(3)
+ assert_equal [ 3, 5 ], @odd.take!(2)
+ assert_equal [ 7, 9, 11 ], @odd.take(3)
+ assert_equal [ 7, 9, 11 ], @odd.drop(0).take(3)
+ assert_equal [ 7, 9, 11 ], @odd.take(3)
+ assert_equal [ 9, 11, 13 ], @odd.drop(1).take(3)
+ assert_equal [ 7, 9, 11 ], @odd.take(3)
+ assert_equal [ 11, 13, 15 ], @odd.drop(2).take(3)
+ assert_equal [ 7, 9, 11 ], @odd.take(3)
+ @odd.drop!(0)
+ assert_equal [ 7, 9, 11 ], @odd.take(3)
+ @odd.drop!(1)
+ assert_equal [ 9, 11, 13 ], @odd.take(3)
+ @odd.drop!(2)
+ assert_equal [ 13, 15, 17 ], @odd.take(3)
+ assert_equal [ 13, 15, 17 ], @odd.first(3)
+ assert_equal [ 8, 9, 10 ], @finite10.last(3)
+ end
+
+ def test_io
+ @tempfile0 = Tempfile.new("test")
+ 1.upto(0) do |i|
+ @tempfile0.puts i
+ end
+ @tempfile0.close
+ @tempfile0_list = LazyList[File.new(@tempfile0.path)]
+ @tempfile10 = Tempfile.new("test")
+ 1.upto(10) do |i|
+ @tempfile10.puts i
+ end
+ @tempfile10.close
+ @tempfile10_list = LazyList[File.new(@tempfile10.path)]
+ assert_equal 0, @tempfile0_list.size
+ assert_equal [], @tempfile0_list.to_a
+ assert_equal 10, @tempfile10_list.size
+ assert_equal((1..10).map { |x| x.to_s + "\n" }, @tempfile10_list.to_a)
+ temp = LazyList.io(File.new(@tempfile0.path)) do |io|
+ io.readline
+ end
+ content = temp.inject([]) { |c, line| c << line }
+ assert_equal [], content
+ temp = LazyList.io(File.new(@tempfile10.path)) do |io|
+ io.readline
+ end
+ content = temp.inject([]) { |c, line| c << line }
+ assert_equal((1..10).map { |x| x.to_s + "\n" }, content)
+ end
+
+ def test_construct_ref
+ assert_equal Empty, LazyList[0, -1]
+ assert_equal [0], LazyList[0, 1].to_a
+ assert_equal((0..9).to_a, LazyList[0, 10].to_a)
+ assert_equal Empty, LazyList[0..-1]
+ assert_equal [0], LazyList[0..0].to_a
+ assert_equal((0..9).to_a, LazyList[0..9].to_a)
+ end
+
+ def test_iterate
+ f = LazyList.iterate(5) do |x|
+ if x % 2 == 0
+ x / 2
+ else
+ 5 * x + 1
+ end
+ end
+ assert_equal nil, f[-1]
+ assert_equal 5, f[0]
+ assert_equal 26, f[1]
+ assert_equal [5, 26, 13, 66, 33, 166, 83, 416], f[0, 8].to_a
+ a196 = LazyList.iterate(35) { |x| x + x.to_s.reverse.to_i }
+ assert_equal [35, 88, 176, 847, 1595, 7546, 14003, 44044, 88088, 176176],
+ a196.take(10)
+ end
+
+ def test_inspect
+ l = LazyList[1..11]
+ assert_equal "[]", Empty.inspect
+ assert_equal "[... ]", l.inspect
+ l[0]
+ assert_equal "[1,... ]", l.inspect
+ l[1]
+ assert_equal "[1, 2,... ]", l.inspect
+ l[2]
+ assert_equal "[1, 2, 3,... ]", l.inspect
+ l[9]
+ assert_equal "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10,... ]", l.inspect
+ l.to_a
+ assert_equal "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]", l.inspect
+ end
+
+ def test_zip
+ combined = @natural.zip(@ones) { |x, y| x + y }
+ assert_equal((12..21).to_a, combined[10,10].to_a)
+ assert_equal((2..11).to_a, combined[0,10].to_a)
+ end
+
+ def from(n = 0)
+ list(n) { from(n + 1) }
+ end
+
+ def odd
+ from(1).select { |x| x % 2 == 1 }
+ end
+
+ def test_list
+ assert_equal Empty, list
+ assert_equal [], list.to_a
+ assert_equal LazyList.new(1) { Empty }, list(1)
+ assert_equal [1], list(1).to_a
+ assert_equal Empty, list
+ o = odd
+ assert_equal [1, 3, 5, 7, 9, 11, 13, 15, 17, 19], o.take(10)
+ assert_equal @odd.take(10), o.take(10)
+ ones = list(1) { ones }
+ assert_equal '[... ]', ones.inspect
+ assert_equal 1, ones.head
+ assert_equal '[1,... ]', ones.inspect
+ assert_equal [ 1 ] * 10 , ones.take(10)
+ assert_equal '[1,... ]', ones.inspect
+ assert_equal [:foo], LazyList[:foo].to_a
+ end
+
+ def test_sublist
+ assert_equal Empty, @natural.sublist(-1)
+ assert_equal Empty, @natural.sublist(0)
+ assert_equal [1], @natural.sublist(1).to_a
+ assert_equal((1..10).to_a, @natural.sublist(10).to_a)
+ assert_equal((6..15).to_a, @natural[5, 10].to_a)
+ assert_equal((6..15).to_a, @natural[5..14].to_a)
+ assert_equal((6..14).to_a, @natural[5...14].to_a)
+ assert_equal nil, @natural.sublist(10)[10]
+ assert_equal 10, @natural.sublist(10)[9]
+ end
+
+ def test_append
+ l1 = LazyList[1..3]
+ l2 = LazyList[5..7]
+ l3 = @natural.drop(8)
+ assert_equal [], Empty.append.to_a
+ assert_equal [], Empty.append(Empty).to_a
+ assert_equal [], (Empty + Empty).to_a
+ assert_equal [], Empty.append(Empty, Empty).to_a
+ assert_equal [1, 2, 3], Empty.append(Empty, l1).to_a
+ assert_equal [1, 2, 3], Empty.append(l1, Empty).to_a
+ assert_equal [1, 2, 3], Empty.append(Empty, l1, Empty).to_a
+ assert_equal [5, 6, 7, 1, 2, 3], l2.append(Empty, l1, Empty).to_a
+ assert_equal [1, 2, 3], l1.append.to_a
+ assert_equal [1, 2, 3], (l1 + Empty).to_a
+ assert_equal [1, 2, 3], (Empty + l1).to_a
+ assert_equal [1, 2, 3, 5, 6, 7], l1.append(l2).to_a
+ assert_equal [1, 2, 3, 5, 6, 7], (l1 + l2).to_a
+ assert_equal [1, 2, 3], l1.append(Empty).to_a
+ assert_equal [1, 2, 3], Empty.append(l1).to_a
+ assert_equal [1, 2, 3] + [5, 6, 7] + [9, 10, 11, 12],
+ l1.append(l2, l3).take(10)
+ end
+
+ def test_list_builder
+ lb = build { y * x }
+ assert_kind_of LazyList::ListBuilder, lb
+ assert_equal "#<LazyList::ListBuilder>", lb.to_s
+ l = lb.where :x => 1..10, :y => 'a'..'j' do
+ x % 2 == 0 && y < 'j'
+ end
+ assert_kind_of LazyList, l
+ assert_equal ["bb", "dddd", "ffffff", "hhhhhhhh"], l.to_a
+ l = build { y * x }.where :x => 1..10, :y => 'a'..'j'
+ assert_equal ["a", "bb", "ccc", "dddd", "eeeee", "ffffff", "ggggggg",
+ "hhhhhhhh", "iiiiiiiii", "jjjjjjjjjj"], l.to_a
+ end
+
+ def twice(x)
+ 2 * x
+ end
+
+ def test_list_comprehend
+ f = LazyList[1..3]
+ g = LazyList[1..2]
+ assert_equal [ ], LazyList.mix(LazyList::Empty).to_a
+ assert_equal [ ], LazyList.mix(LazyList::Empty, LazyList::Empty).to_a
+ assert_equal [ 1 ], LazyList.mix(list(1), LazyList::Empty).to_a
+ assert_equal [ 1 ], LazyList.mix(LazyList::Empty, list(1)).to_a
+ assert_equal [ 1, 1, 2, 2, 3 ], LazyList.mix(f, g).to_a
+ assert_equal list, list * list
+ assert_equal list, list * list(1)
+ assert_equal list, list(1) * list
+ assert_equal list, list.cartesian_product
+ assert_equal [ [1, 1], [1, 2], [2, 1], [2, 2], [3, 1], [3, 2] ],
+ f.cartesian_product(g).to_a
+ assert_equal [[1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 2, 2], [2, 1, 1], [2, 1,
+ 2], [2, 2, 1], [2, 2, 2]], g.cartesian_product(g, g).to_a
+ assert_equal [1, 2, 2, 4, 2, 4, 4, 8],
+ g.cartesian_product(g, g, &lambda { |x,y,z| x * y * z }).to_a
+ assert_equal g.map { |x| x * x }, g.cartesian_product { |x| x * x }
+ l = list { [ x, y ] }.where :x => g, :y => g
+ assert_equal [ [ 1, 1 ], [ 1, 2 ], [ 2, 1 ], [ 2, 2 ] ], l.to_a.sort
+ l = list { [ x, y ] }.where :x => f, :y => g
+ assert_equal [ [ 1, 1 ], [ 1, 2 ], [ 2, 1 ], [ 2, 2 ], [ 3, 1 ], [ 3, 2 ] ], l.to_a.sort
+ l = list { [ x, y ] }.where(:x => f, :y => f) { x > y }
+ assert_equal [ [ 2, 1 ], [ 3, 1 ], [ 3, 2 ] ], l.to_a.sort
+ l = list { [ x, y ] }.where(:x => f, :y => f) { x <= y }
+ assert_equal [ [1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3] ], l.to_a.sort
+ test = list { x * y }.where :x => 1..4, :y => list(3, 5, 7, 9)
+ assert_equal [ 3, 5, 6, 7, 10, 14, 9, 9, 21, 27, 15, 18, 36, 12, 28, 20 ].sort, test.to_a.sort
+ test = list { x + twice(y) }.where :x => 1..4, :y => list(3, 5, 7, 9)
+ assert_equal [ 7, 11, 8, 15, 12, 16, 9, 19, 17, 21, 13, 20, 22, 10, 18, 14 ].sort, test.to_a.sort
+ end
+end
104 tests/test_enumerable.rb
@@ -0,0 +1,104 @@
+#!/usr/bin/env ruby
+
+require 'test/unit'
+require 'lazylist'
+
+class TC_LazyEnumerable < Test::Unit::TestCase
+ def setup
+ @a = LazyList[%w[abc cdef aab efgh eefgh hijkl]]
+ @o = list(1) { @o }
+ @n = LazyList.from(1)
+ @l = LazyList[1..5]
+ @m = LazyList[5..8]
+ @r = [5, -2, 3, 13, 0]
+ @s = LazyList[@r]
+ end
+
+ def test_zip
+ l = @l.zip @m
+ assert_kind_of LazyList, l
+ assert_equal [[1, 5], [2, 6], [3, 7], [4, 8]], l.to_a
+ end
+
+ def test_zip_and_block
+ l = @l.zip(@m) { |x,y| x + y }
+ assert_kind_of LazyList, l
+ assert_equal [6, 8, 10, 12], l.to_a
+ zipped = @n.zip(@o) { |x, y| x + y }
+ assert_kind_of LazyList, zipped
+ assert_equal (12..21).to_a, zipped[10,10].to_a
+ assert_equal (2..11).to_a, zipped[0,10].to_a
+ end
+
+ def test_zip_multiple_and_block
+ zipped = @n.zip(@o, @m) { |x, y, z| x * z + y }
+ assert_kind_of LazyList, zipped
+ assert_equal [6, 13, 22, 33], zipped.to_a
+ end
+
+ def test_each_with_index
+ l = @m.each_with_index
+ assert_kind_of LazyList, l
+ assert_equal l.to_a, [ [5, 0], [6, 1], [7, 2], [8, 3] ]
+ end
+
+ def test_each_with_index_and_block
+ @m.each_with_index { |x, i| assert_equal x - 5, i }
+ end
+
+ def test_sort
+ l = @s.sort
+ assert_kind_of LazyList, l
+ assert @r.sort, l
+ end
+
+ def test_sort_by
+ l = @s.sort_by { |x| -x }
+ assert_kind_of LazyList, l
+ assert @r.sort_by { |x| -x }, l
+ end
+
+ def test_grep
+ l = @a.grep /e/
+ assert_kind_of LazyList, l
+ assert_equal %w[cdef efgh eefgh], l.to_a
+ end
+
+ def test_grep_with_block
+ expected = %w[cdef efgh eefgh]
+ i = 0
+ l = @a.grep(/e/) do |x|
+ assert_equal expected[i], x
+ end
+ end
+
+ def test_reject