├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Gemfile ├── README.md ├── Rakefile ├── benchmark ├── benchmark_deque.rb ├── benchmark_map.rb └── benchmark_queue.rb ├── immutable.gemspec ├── lib ├── immutable.rb └── immutable │ ├── consable.rb │ ├── deque.rb │ ├── foldable.rb │ ├── headable.rb │ ├── list.rb │ ├── map.rb │ ├── output_restricted_deque.rb │ ├── promise.rb │ ├── queue.rb │ └── stream.rb └── test ├── immutable ├── test_deque.rb ├── test_list.rb ├── test_map.rb ├── test_promise.rb ├── test_queue.rb └── test_stream.rb └── test_helper.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | paths-ignore: 7 | - '.gitignore' 8 | - '**.md' 9 | - 'benchmark/**' 10 | pull_request: 11 | paths-ignore: 12 | - '.gitignore' 13 | - '**.md' 14 | - 'benchmark/**' 15 | jobs: 16 | test: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: ['ubuntu-latest'] 21 | ruby: ['head', '3.1', '3.0', '2.7'] 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby }} 28 | bundler-cache: true 29 | - run: bundle exec rake test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .??*.swp 2 | .bundle 3 | Gemfile.lock 4 | tags 5 | pkg 6 | coverage 7 | .yardoc 8 | doc 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | immutable - immutable data structures for Ruby 2 | ============================================== 3 | 4 | This project aims to provide immutable data structures for Ruby. 5 | 6 | [![Build Status](https://github.com/shugo/immutable/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/shugo/immutable/actions/workflows/ci.yml?query=branch%3Amaster++) 7 | 8 | Install 9 | ------- 10 | 11 | ```bash 12 | $ gem install immutable 13 | ``` 14 | 15 | Documentation 16 | ------------- 17 | 18 | * [API Reference](http://rubydoc.info/github/shugo/immutable/frames) 19 | 20 | License 21 | ------- 22 | 23 | (The MIT License) 24 | 25 | Copyright (c) 2012 Shugo Maeda 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining 28 | a copy of this software and associated documentation files (the 29 | 'Software'), to deal in the Software without restriction, including 30 | without limitation the rights to use, copy, modify, merge, publish, 31 | distribute, sublicense, and/or sell copies of the Software, and to 32 | permit persons to whom the Software is furnished to do so, subject to 33 | the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be 36 | included in all copies or substantial portions of the Software. 37 | 38 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 39 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 40 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 41 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 42 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 43 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 44 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 45 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rake" 2 | require "rake/testtask" 3 | require "rubygems/package_task" 4 | 5 | require 'bundler' 6 | Bundler::GemHelper.install_tasks 7 | 8 | task :default => :test 9 | 10 | Rake::TestTask.new(:test) do |test| 11 | test.libs << 'lib' << 'test' 12 | test.pattern = 'test/**/test_*.rb' 13 | test.verbose = true 14 | end 15 | 16 | require "yard" 17 | require "yard/rake/yardoc_task" 18 | 19 | YARD::Rake::YardocTask.new do |t| 20 | t.files = ['lib/**/*.rb'] 21 | t.options = [] 22 | t.options << '--debug' << '--verbose' if $trace 23 | end 24 | -------------------------------------------------------------------------------- /benchmark/benchmark_deque.rb: -------------------------------------------------------------------------------- 1 | require "benchmark" 2 | require_relative "../lib/immutable/deque" 3 | require_relative "../lib/immutable/map" 4 | 5 | TIMES = 100000 6 | key_size = 10 7 | 8 | def head(deque) 9 | TIMES.times do 10 | deque.head 11 | end 12 | end 13 | 14 | def last(deque) 15 | TIMES.times do 16 | deque.last 17 | end 18 | end 19 | 20 | def tail(deque) 21 | TIMES.times do 22 | deque.tail 23 | end 24 | end 25 | 26 | def init(deque) 27 | TIMES.times do 28 | deque.init 29 | end 30 | end 31 | 32 | def cons(deque) 33 | TIMES.times do 34 | deque.cons(0) 35 | end 36 | end 37 | 38 | def snoc(deque) 39 | TIMES.times do 40 | deque.snoc(0) 41 | end 42 | end 43 | 44 | def run(bm, deque, num, method) 45 | bm.report("#{method} for #{num}-elements deque") do 46 | send(method, deque) 47 | end 48 | end 49 | 50 | Benchmark.bmbm do |bm| 51 | deques = [100, 1000, 10000].inject(Immutable::Map.empty) { |m, n| 52 | m.insert(n, (1..n).inject(Immutable::Deque.empty, &:snoc)) 53 | } 54 | for method in [:head, :last, :tail, :init, :cons, :snoc] 55 | for num, deque in deques 56 | run(bm, deque, num, method) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /benchmark/benchmark_map.rb: -------------------------------------------------------------------------------- 1 | require "benchmark" 2 | require "avl_tree" 3 | require "red_black_tree" 4 | require_relative "../lib/immutable/map" 5 | require "openssl" 6 | 7 | #random = Random.new(0) 8 | 9 | TIMES = 100000 10 | key_size = 10 11 | 12 | def aset(h, keys) 13 | keys.each do |k| 14 | h[k] = 1 15 | end 16 | end 17 | 18 | def aref(h, keys) 19 | keys.each do |k| 20 | h[k] 21 | end 22 | end 23 | 24 | def delete(h, keys) 25 | keys.each do |k| 26 | h.delete(k) 27 | end 28 | end 29 | 30 | def map_insert(h, keys) 31 | keys.inject(h) { |m, k| 32 | m.insert(k, 1) 33 | } 34 | end 35 | 36 | def map_delete(h, keys) 37 | keys.inject(h) { |m, k| 38 | m.delete(k) 39 | } 40 | end 41 | 42 | def run(bm, h, keys) 43 | name = h.class.name 44 | bm.report("#{name} aset") do 45 | aset(h, keys) 46 | end 47 | bm.report("#{name} aref") do 48 | aref(h, keys) 49 | end 50 | bm.report("#{name} delete") do 51 | delete(h, keys) 52 | end 53 | end 54 | 55 | def map_run(bm, h, keys) 56 | name = h.class.name 57 | bm.report("#{name} insert") do 58 | h = map_insert(h, keys) 59 | end 60 | bm.report("#{name} aref") do 61 | aref(h, keys) 62 | end 63 | bm.report("#{name} delete") do 64 | h = map_delete(h, keys) 65 | end 66 | end 67 | 68 | keys = [] 69 | TIMES.times do 70 | keys << OpenSSL::Random.random_bytes(key_size) 71 | end 72 | 73 | Benchmark.bmbm do |bm| 74 | run(bm, Hash.new, keys) 75 | run(bm, AVLTree.new, keys) 76 | run(bm, RedBlackTree.new, keys) 77 | map_run(bm, Immutable::Map.empty, keys) 78 | end 79 | -------------------------------------------------------------------------------- /benchmark/benchmark_queue.rb: -------------------------------------------------------------------------------- 1 | require "benchmark" 2 | require_relative "../lib/immutable/queue" 3 | require_relative "../lib/immutable/map" 4 | 5 | TIMES = 100000 6 | key_size = 10 7 | 8 | def snoc(queue) 9 | TIMES.times do 10 | queue.snoc(0) 11 | end 12 | end 13 | 14 | def head(queue) 15 | TIMES.times do 16 | queue.head 17 | end 18 | end 19 | 20 | def tail(queue) 21 | TIMES.times do 22 | queue.tail 23 | end 24 | end 25 | 26 | def run(bm, queue, num, method) 27 | bm.report("#{method} for #{num}-elements queue") do 28 | send(method, queue) 29 | end 30 | end 31 | 32 | Benchmark.bmbm do |bm| 33 | queues = [10, 1000, 100000].inject(Immutable::Map.empty) { |m, n| 34 | m.insert(n, (1..n).inject(Immutable::Queue.empty, &:snoc)) 35 | } 36 | for method in [:snoc, :head, :tail] 37 | for num, queue in queues 38 | run(bm, queue, num, method) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /immutable.gemspec: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | Gem::Specification.new { |s| 3 | s.name = "immutable" 4 | s.version = "0.4.0" 5 | s.author = "Shugo Maeda" 6 | s.email = "shugo@ruby-lang.org" 7 | s.summary = "Immutable data structures for Ruby" 8 | s.description = "Immutable data structures for Ruby" 9 | s.homepage = "http://github.com/shugo/immutable" 10 | s.license = "MIT" 11 | s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 12 | s.require_path = "lib" 13 | s.required_ruby_version = '>= 2.7.0.dev' 14 | 15 | s.add_development_dependency "rake" 16 | s.add_development_dependency "test-unit" 17 | s.add_development_dependency "kramdown" 18 | s.add_development_dependency "yard" 19 | } 20 | -------------------------------------------------------------------------------- /lib/immutable.rb: -------------------------------------------------------------------------------- 1 | # +Immutable+ is a namespace for immutable data structures. 2 | module Immutable 3 | end 4 | 5 | if defined?(RubyVM) 6 | old_compile_option = RubyVM::InstructionSequence.compile_option 7 | RubyVM::InstructionSequence.compile_option = { 8 | :tailcall_optimization => true, 9 | :trace_instruction => false 10 | } 11 | end 12 | begin 13 | require_relative "immutable/list" 14 | require_relative "immutable/map" 15 | require_relative "immutable/promise" 16 | require_relative "immutable/stream" 17 | require_relative "immutable/queue" 18 | require_relative "immutable/output_restricted_deque" 19 | require_relative "immutable/deque" 20 | ensure 21 | if defined?(RubyVM) 22 | RubyVM::InstructionSequence.compile_option = old_compile_option 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/immutable/consable.rb: -------------------------------------------------------------------------------- 1 | require_relative "headable" 2 | 3 | module Immutable 4 | module Consable 5 | include Headable 6 | 7 | module ClassMethods 8 | # Creates a new +Consable+ object populated with the given objects. 9 | # 10 | # @param [Array] elements the elements of the +Consable+ 11 | # object. 12 | # @return [Consable] the new +Consable+ object. 13 | def [](*elements) 14 | from_array(elements) 15 | end 16 | 17 | # Converts the given array to a +Consable+ object. 18 | # 19 | # @param [Array, #reverse_each] ary the array to convert. 20 | # @return [Consable] the +Consable+ object converted from +ary+. 21 | def from_array(ary) 22 | ary.reverse_each.inject(empty) { |x, y| 23 | Cons(y, x) 24 | } 25 | end 26 | 27 | # Converts the given Enumerable object to a +Consable+ object. 28 | # 29 | # @param [#inject] enum the Enumerable object to convert. 30 | # @return [Cons] the +Consable+ object converted from +enum+. 31 | def from_enum(enum) 32 | enum.inject(empty) { |x, y| 33 | Cons(y, x) 34 | }.reverse 35 | end 36 | 37 | # Builds a +Consable+ object from the seed value +e+ and the given 38 | # block. The block takes a seed value and returns +nil+ if the seed 39 | # should unfold to the empty +Consable+ object, or returns +[a, b]+, 40 | # where +a+ is the head of the +Consable+ object and +b+ is the next 41 | # seed from which to unfold the tail. For example: 42 | # 43 | # xs = List.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] } 44 | # p xs #=> List[3, 2, 1] 45 | # 46 | # +unfoldr+ is the dual of +foldr+. 47 | # 48 | # @param [Object] e the seed value. 49 | # @return [Consable] the +Consable+ object built from the seed value 50 | # and the block. 51 | def unfoldr(e, &block) 52 | x = yield(e) 53 | if x.nil? 54 | empty 55 | else 56 | y, z = x 57 | Cons(y, unfoldr(z, &block)) 58 | end 59 | end 60 | 61 | private 62 | 63 | def Cons(x, y) 64 | y.cons(x) 65 | end 66 | end 67 | 68 | def self.included(c) 69 | c.extend(ClassMethods) 70 | end 71 | 72 | # Adds a new element at the head of +self+. A class including 73 | # +Immutable::Consable+ must implement this method. 74 | # 75 | # @param [Object] x the element to add. 76 | # @return [Consable] a new +Consable+ object. 77 | def cons(x) 78 | raise NotImplementedError 79 | end 80 | 81 | # Same as {#cons}. This method just calls {#cons}. 82 | # 83 | # @param [Object] x the element to add. 84 | # @return [Consable] a new +Consable+ object. 85 | def unshift(x) 86 | cons(x) 87 | end 88 | 89 | # Same as {#cons}. This method just calls {#cons}. 90 | # 91 | # @param [Object] x the element to add. 92 | # @return [Consable] a new +Consable+ object. 93 | def prepend(x) 94 | cons(x) 95 | end 96 | 97 | # Appends two +Consable+ objects +self+ and +xs+. 98 | # 99 | # @param [Consable] xs the +Consable+ object to append. 100 | # @return [Consable] a new +Consable+ object. 101 | def +(xs) 102 | foldr(xs) { |y, ys| Cons(y, ys) } 103 | end 104 | 105 | # Returns the +Consable+ object obtained by applying the given block to 106 | # each element in +self+. 107 | # 108 | # @return [Consable] the obtained +Consable+ object. 109 | def map(&block) 110 | rev_map(&block).reverse 111 | end 112 | 113 | # Returns the +Consable+ object obtained by applying the given block to 114 | # each element in +self+ in reverse order. 115 | # 116 | # @return [Consable] the obtained +Consable+ object. 117 | def rev_map 118 | foldl(empty) { |xs, x| Cons(yield(x), xs) } 119 | end 120 | 121 | # Returns the elements of +self+ in reverse order. 122 | # 123 | # @return [Consable] the reversed +Consable+ object. 124 | def reverse 125 | foldl(empty) { |x, y| Cons(y, x) } 126 | end 127 | 128 | # Returns a new +Consable+ object obtained by inserting +sep+ in between 129 | # the elements of +self+. 130 | # 131 | # @param [Object] sep the object to insert between elements. 132 | # @return [Consable] the new +Consable+ object. 133 | def intersperse(sep) 134 | if empty? 135 | empty 136 | else 137 | Cons(head, tail.prepend_to_all(sep)) 138 | end 139 | end 140 | 141 | # Returns a new +Consable+ object obtained by inserting +xs+ in between 142 | # the +Consable+ objects in +self+ and concatenates the result. 143 | # +xss.intercalate(xs)+ is equivalent to +xss.intersperse(xs).flatten+. 144 | # 145 | # @param [Consable] xs the +Consable+ object to insert between 146 | # +Consable+ objects. 147 | # @return [Consable] the new +Consable+ object. 148 | def intercalate(xs) 149 | intersperse(xs).flatten 150 | end 151 | 152 | # Transposes the rows and columns of +self+. For example: 153 | # 154 | # p List[List[1, 2, 3], List[4, 5, 6]].transpose 155 | # #=> List[List[1, 4], List[2, 5], List[3, 6]] 156 | # 157 | # @return [Consable] the transposed +Consable+ object. 158 | def transpose 159 | if empty? 160 | empty 161 | else 162 | if head.empty? 163 | tail.transpose 164 | else 165 | t = tail.filter { |x| !x.empty? } 166 | Cons(Cons(head.head, t.map(&:head)), 167 | Cons(head.tail, t.map(&:tail)).transpose) 168 | end 169 | end 170 | end 171 | 172 | # Returns the +Consable+ object of all subsequences of +self+. 173 | # 174 | # @return [Consable] the subsequences of +self+. 175 | def subsequences 176 | Cons(empty, nonempty_subsequences) 177 | end 178 | 179 | # Concatenates a +Consable+ object of +Consable+ objects. 180 | # 181 | # @return [Consable] the concatenated +Consable+ object. 182 | def flatten 183 | foldr(empty) { |x, xs| safe_append(x, xs) } 184 | end 185 | 186 | alias concat flatten 187 | 188 | # Returns the +Consable+ object obtained by concatenating the results of 189 | # the given block for each element in +self+. 190 | # 191 | # @return [Consable] the obtained +Consable+ object. 192 | def flat_map 193 | foldr(empty) { |x, xs| safe_append(yield(x), xs) } 194 | end 195 | 196 | alias concat_map flat_map 197 | alias bind flat_map 198 | 199 | # Returns the first +n+ elements of +self+, or all the elements of 200 | # +self+ if +n > self.length+. 201 | # 202 | # @param [Integer] n the number of elements to take. 203 | # @return [Consable] the first +n+ elements of +self+. 204 | def take(n) 205 | if empty? 206 | empty 207 | else 208 | if n <= 0 209 | empty 210 | else 211 | Cons(head, tail.take(n - 1)) 212 | end 213 | end 214 | end 215 | 216 | # Returns the suffix of +self+ after the first +n+ elements, or 217 | # an empty +Consable+ object if +n > self.length+. 218 | # 219 | # @param [Integer] n the number of elements to drop. 220 | # @return [Consable] the suffix of +self+ after the first +n+ elements. 221 | def drop(n) 222 | if empty? 223 | empty 224 | else 225 | if n > 0 226 | tail.drop(n - 1) 227 | else 228 | self 229 | end 230 | end 231 | end 232 | 233 | # Returns the longest prefix of the elements of +self+ for which +block+ 234 | # evaluates to true. 235 | # 236 | # @return [Consable] the prefix of the elements of +self+. 237 | def take_while(&block) 238 | if empty? 239 | empty 240 | else 241 | if yield(head) 242 | Cons(head, tail.take_while(&block)) 243 | else 244 | empty 245 | end 246 | end 247 | end 248 | 249 | # Returns the suffix remaining after 250 | # +self.take_while(&block)+. 251 | # 252 | # @return [Consable] the suffix of the elements of +self+. 253 | def drop_while(&block) 254 | if empty? 255 | empty 256 | else 257 | if yield(head) 258 | tail.drop_while(&block) 259 | else 260 | self 261 | end 262 | end 263 | end 264 | 265 | # Returns the elements in +self+ for which the given block evaluates to 266 | # true. 267 | # 268 | # @return [Consable] the elements that satisfies the condition. 269 | def filter 270 | foldr(empty) { |x, xs| 271 | if yield(x) 272 | Cons(x, xs) 273 | else 274 | xs 275 | end 276 | } 277 | end 278 | 279 | # Takes zero or more lists and returns a new +Consable+ object in which 280 | # each element is an array of the corresponding elements of +self+ and 281 | # the input +Consable+ objects. 282 | # 283 | # @param [Array] xss the input +Consable+ objects. 284 | # @return [Consable] the new +Consable+ object. 285 | def zip(*xss) 286 | if empty? 287 | empty 288 | else 289 | heads = xss.map { |xs| xs.empty? ? nil : xs.head } 290 | tails = xss.map { |xs| xs.empty? ? empty : xs.tail } 291 | Cons([head, *heads], tail.zip(*tails)) 292 | end 293 | end 294 | 295 | # Takes zero or more +Consable+ objects and returns the +Consable+ 296 | # object obtained by applying the given block to an array of the 297 | # corresponding elements of +self+ and the input +Consable+ objects. 298 | # +xs.zip_with(*yss, &block)+ is equivalent to 299 | # +xs.zip(*yss).map(&block)+. 300 | # 301 | # @param [Array] xss the input +Consable+ objects. 302 | # @return [Consable] the new +Consable+ object. 303 | def zip_with(*xss, &block) 304 | if empty? 305 | empty 306 | else 307 | heads = xss.map { |xs| xs.null? ? nil : xs.head } 308 | tails = xss.map { |xs| xs.null? ? empty : xs.tail } 309 | Cons(yield(head, *heads), tail.zip_with(*tails, &block)) 310 | end 311 | end 312 | 313 | protected 314 | 315 | def prepend_to_all(sep) 316 | if empty? 317 | empty 318 | else 319 | Cons(sep, Cons(head, tail.prepend_to_all(sep))) 320 | end 321 | end 322 | 323 | def nonempty_subsequences 324 | if empty? 325 | empty 326 | else 327 | yss = tail.nonempty_subsequences.foldr(empty) { |xs, xss| 328 | Cons(xs, Cons(Cons(head, xs), xss)) 329 | } 330 | Cons(Cons(head, empty), yss) 331 | end 332 | end 333 | 334 | private 335 | 336 | def empty 337 | self.class.empty 338 | end 339 | 340 | def Cons(x, y) 341 | y.cons(x) 342 | end 343 | 344 | def safe_append(xs, ys) 345 | xs.foldr(ys) { |z, zs| Cons(z, zs) } 346 | rescue NoMethodError 347 | Cons(xs, ys) 348 | end 349 | end 350 | end 351 | -------------------------------------------------------------------------------- /lib/immutable/deque.rb: -------------------------------------------------------------------------------- 1 | require_relative "stream" 2 | require_relative "consable" 3 | 4 | module Immutable 5 | # +Immutable::Deque+ is an implementation of real-time deques described in 6 | # "Purely Functional Data Structures" by Chris Okasaki. 7 | class Deque 8 | include Consable 9 | 10 | # +Deque.new+ is for internal use only. Use {Deque.empty} or {Deque.[]} 11 | # instead. 12 | def initialize(front, front_len, front_schedule, 13 | rear, rear_len, rear_schedule) 14 | @front = front 15 | @front_len = front_len 16 | @front_schedule = front_schedule 17 | @rear = rear 18 | @rear_len = rear_len 19 | @rear_schedule = rear_schedule 20 | @c = 3 # @c should be 2 or 3 21 | end 22 | 23 | # Returns an empty deque. 24 | # 25 | # @return [Deque] the empty deque. 26 | def self.empty 27 | Deque.new(Stream.empty, 0, Stream.empty, 28 | Stream.empty, 0, Stream.empty) 29 | end 30 | 31 | # Creates a new deque populated with the given objects. 32 | # 33 | # @param [Array] elements the elements of the deque. 34 | # @return [Deque] the new deque. 35 | def self.[](*elements) 36 | elements.inject(empty, &:snoc) 37 | end 38 | 39 | # Returns whether +self+ is empty. 40 | # 41 | # @return [true, false] +true+ if +self+ is empty; otherwise, +false+. 42 | def empty? 43 | length == 0 44 | end 45 | 46 | # Returns the number of elements in +self+. May be zero. 47 | # 48 | # @return [Integer] the number of elements in +self+. 49 | def length 50 | @front_len + @rear_len 51 | end 52 | 53 | # Adds a new element at the head of +self+. 54 | # 55 | # @param [Object] x the element to add. 56 | # @return [Deque] a new deque. 57 | def cons(x) 58 | queue(Stream.cons(->{x}, ->{@front}), @front_len + 1, 59 | exec1(@front_schedule), 60 | @rear, @rear_len, exec1(@rear_schedule)) 61 | end 62 | 63 | # Returns the first element of +self+. If +self+ is empty, 64 | # +Immutable::EmptyError+ is raised. 65 | # 66 | # @return [Object] the first element of +self+. 67 | def head 68 | if @front.empty? 69 | if @rear.empty? 70 | raise EmptyError 71 | else 72 | @rear.head 73 | end 74 | else 75 | @front.head 76 | end 77 | end 78 | 79 | # Returns the elements after the head of +self+. If +self+ is empty, 80 | # +Immutable::EmptyError+ is raised. 81 | # 82 | # @return [Deque] the elements after the head of +self+. 83 | def tail 84 | if @front.empty? 85 | if @rear.empty? 86 | raise EmptyError 87 | else 88 | self.class.empty 89 | end 90 | else 91 | queue(@front.tail, @front_len - 1, exec2(@front_schedule), 92 | @rear, @rear_len, exec2(@rear_schedule)) 93 | end 94 | end 95 | 96 | # Adds a new element at the end of +self+. 97 | # 98 | # @param [Object] x the element to add. 99 | # @return [Deque] a new queue. 100 | def snoc(x) 101 | queue(@front, @front_len, exec1(@front_schedule), 102 | Stream.cons(->{x}, ->{@rear}), @rear_len + 1, 103 | exec1(@rear_schedule)) 104 | end 105 | 106 | alias push snoc 107 | 108 | # Returns the last element of +self+. If +self+ is empty, 109 | # +Immutable::EmptyError+ is raised. 110 | # 111 | # @return [Object] the last element of +self+. 112 | def last 113 | if @rear.empty? 114 | if @front.empty? 115 | raise EmptyError 116 | else 117 | @front.head 118 | end 119 | else 120 | @rear.head 121 | end 122 | end 123 | 124 | # Returns all the elements of +self+ except the last one. 125 | # If +self+ is empty, +Immutable::EmptyError+ is 126 | # raised. 127 | # 128 | # @return [Deque] the elements of +self+ except the last one. 129 | def init 130 | if @rear.empty? 131 | if @front.empty? 132 | raise EmptyError 133 | else 134 | self.class.empty 135 | end 136 | else 137 | queue(@front, @front_len, exec2(@front_schedule), 138 | @rear.tail, @rear_len - 1, exec2(@rear_schedule)) 139 | end 140 | end 141 | 142 | alias pop init 143 | 144 | private 145 | 146 | def exec1(s) 147 | if s.empty? 148 | s 149 | else 150 | s.tail 151 | end 152 | end 153 | 154 | def exec2(s) 155 | exec1(exec1(s)) 156 | end 157 | 158 | def rotate_rev(r, f, a) 159 | if r.empty? 160 | f.reverse + a 161 | else 162 | Stream.cons(->{r.head}, 163 | ->{rotate_rev(r.tail, f.drop(@c), 164 | f.take(@c).reverse + a)}) 165 | end 166 | end 167 | 168 | def rotate_drop(r, i, f) 169 | if i < @c 170 | rotate_rev(r, f.drop(i), Stream.empty) 171 | else 172 | x = r.head 173 | r2 = r.tail 174 | Stream.cons(->{x}, ->{rotate_drop(r2, i - @c, f.drop(@c))}) 175 | end 176 | end 177 | 178 | def queue(f, f_len, f_schedule, r, r_len, r_schedule) 179 | if f_len > @c * r_len + 1 180 | i = (f_len + r_len) / 2 181 | j = (f_len + r_len) - i 182 | f2 = f.take(i) 183 | r2 = rotate_drop(r, i, f) 184 | self.class.new(f2, i, f2, r2, j, r2) 185 | elsif r_len > @c * f_len + 1 186 | j = (f_len + r_len) / 2 187 | i = (f_len + r_len) - j 188 | f2 = rotate_drop(f, j, r) 189 | r2 = r.take(j) 190 | self.class.new(f2, i, f2, r2, j, r2) 191 | else 192 | self.class.new(f, f_len, f_schedule, r, r_len, r_schedule) 193 | end 194 | end 195 | end 196 | end 197 | -------------------------------------------------------------------------------- /lib/immutable/foldable.rb: -------------------------------------------------------------------------------- 1 | module Immutable 2 | module Foldable 3 | # Reduces +self+ using +block+ from left to right. +e+ is used as the 4 | # starting value. A class including +Immutable::Foldable+ must implement 5 | # this method. 6 | # 7 | # @param [Object] e the start value. 8 | # @return [Object] the reduced value. 9 | def foldl(e, &block) 10 | raise NotImplementedError 11 | end 12 | 13 | # Returns the number of elements in +self+. May be zero. 14 | # 15 | # @return [Integer] the number of elements in +self+. 16 | def length 17 | foldl(0) { |x, y| x + 1 } 18 | end 19 | 20 | # Returns the number of elements in +self+. May be zero. 21 | # 22 | # @return [Integer] the number of elements in +self+. 23 | alias size length 24 | 25 | # Computes the sum of the numbers in +self+. 26 | # 27 | # @return [#+] the sum of the numbers. 28 | def sum 29 | foldl(0) { |x, y| x + y } 30 | end 31 | 32 | # Computes the product of the numbers in +self+. 33 | # 34 | # @return [#*] the product of the numbers. 35 | def product 36 | foldl(1) { |x, y| x * y } 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/immutable/headable.rb: -------------------------------------------------------------------------------- 1 | require_relative "foldable" 2 | 3 | module Immutable 4 | class EmptyError < StandardError 5 | def initialize(msg = "collection is empty") 6 | super(msg) 7 | end 8 | end 9 | 10 | module Headable 11 | include Enumerable 12 | include Foldable 13 | 14 | # Returns the first element of +self+. If +self+ is empty, 15 | # +Immutable::EmptyError+ is raised. A class including 16 | # +Immutable::Headable+ must implement this method. 17 | # 18 | # @return [Object] the first element of +self+. 19 | def head 20 | raise NotImplementedError 21 | end 22 | 23 | # Same as {#head}. This method just calls {#head}. 24 | # 25 | # @return [Object] the first element of +self+. 26 | def first 27 | head 28 | end 29 | 30 | # Returns the elements after the head of +self+. If +self+ is empty, 31 | # +Immutable::EmptyError+ is raised. A class including 32 | # +Immutable::Headable+ must implement this method. 33 | # 34 | # @return [Headable] the elements after the head of +self+. 35 | def tail 36 | raise NotImplementedError 37 | end 38 | 39 | # Same as {#tail}. This method just calls {#tail}. 40 | # 41 | # @return [Headable] the elements after the head of +self+. 42 | def shift 43 | tail 44 | end 45 | 46 | # Returns whether +self+ is empty. A class including 47 | # +Immutable::Headable+ must implement this method. 48 | # 49 | # @return [true, false] +true+ if +self+ is empty; otherwise, +false+. 50 | def empty? 51 | raise NotImplementedError 52 | end 53 | 54 | # Same as {#empty?}. This method just calls {#empty?}. 55 | # 56 | # @return [true, false] +true+ if +self+ is empty; otherwise, +false+. 57 | def null? 58 | empty? 59 | end 60 | 61 | # Returns a string containing a human-readable representation of +self+. 62 | # 63 | # @return [String] a string representation of +self+. 64 | def inspect 65 | if empty? 66 | class_name + "[]" 67 | else 68 | class_name + "[" + head.inspect + 69 | tail.foldl("") {|x, y| x + ", " + y.inspect } + "]" 70 | end 71 | end 72 | 73 | alias to_s inspect 74 | 75 | # Returns whether +self+ equals to +x+. 76 | # 77 | # @param [Object] x the object to compare. 78 | # @return [true, false] +true+ if +self+ equals to +x+; otherwise, 79 | # +false+. 80 | def ==(x) 81 | if !x.is_a?(self.class) 82 | false 83 | else 84 | if empty? 85 | x.empty? 86 | else 87 | !x.empty? && head == x.head && tail == x.tail 88 | end 89 | end 90 | end 91 | 92 | def eql?(x) 93 | if !x.is_a?(self.class) 94 | false 95 | else 96 | if empty? 97 | x.empty? 98 | else 99 | !x.empty? && head.eql?(x.head) && tail.eql?(x.tail) 100 | end 101 | end 102 | end 103 | 104 | # @return [Integer] 105 | def hash 106 | to_a.hash 107 | end 108 | 109 | # Calls +block+ once for each element in +self+. 110 | # @yield [element] 111 | # @yieldreturn [self] 112 | # @return [Enumerator] 113 | def each(&block) 114 | return to_enum unless block_given? 115 | 116 | unless empty? 117 | yield(head) 118 | tail.each(&block) 119 | end 120 | 121 | self 122 | end 123 | 124 | # Calls +block+ once for each index in +self+. 125 | # @yield [index] 126 | # @yieldparam [Integer] index 127 | # @yieldreturn [self] 128 | # @return [Enumerator] 129 | def each_index 130 | return to_enum(__callee__) unless block_given? 131 | 132 | each_with_index{ |_, idx| yield(idx) } 133 | end 134 | 135 | # Reduces +self+ using +block+ from right to left. +e+ is used as the 136 | # starting value. For example: 137 | # 138 | # List[1, 2, 3].foldr(9) { |x, y| x - y } #=> 1 - (2 - (3 - 9)) = -7 139 | # 140 | # @param [Object] e the start value. 141 | # @return [Object] the reduced value. 142 | def foldr(e, &block) 143 | if empty? 144 | e 145 | else 146 | yield(head, tail.foldr(e, &block)) 147 | end 148 | end 149 | 150 | # Reduces +self+ using +block+ from right to left. If +self+ is empty, 151 | # +Immutable::EmptyError+ is raised. 152 | # 153 | # @return [Object] the reduced value. 154 | def foldr1(&block) 155 | if empty? 156 | raise EmptyError 157 | else 158 | if tail.empty? 159 | head 160 | else 161 | yield(head, tail.foldr1(&block)) 162 | end 163 | end 164 | end 165 | 166 | # Reduces +self+ using +block+ from left to right. +e+ is used as the 167 | # starting value. For example: 168 | # 169 | # List[1, 2, 3].foldl(9) { |x, y| x - y } #=> ((9 - 1) - 2) - 3 = 3 170 | # 171 | # @param [Object] e the start value. 172 | # @return [Object] the reduced value. 173 | def foldl(e, &block) 174 | if empty? 175 | e 176 | else 177 | tail.foldl(yield(e, head), &block) 178 | end 179 | end 180 | 181 | # Reduces +self+ using +block+ from left to right. If +self+ is empty, 182 | # +Immutable::EmptyError+ is raised. 183 | # 184 | # @return [Object] the reduced value. 185 | def foldl1(&block) 186 | if empty? 187 | raise EmptyError 188 | else 189 | tail.foldl(head, &block) 190 | end 191 | end 192 | 193 | # Returns the first element in +self+ for which the given block 194 | # evaluates to true. If such an element is not found, it 195 | # returns +nil+. 196 | # 197 | # @param [#call, nil] ifnone 198 | # @yield [element] 199 | # @yieldreturn [Object] the found element. 200 | # @return [Enumerator] 201 | def find(ifnone=nil, &block) 202 | return to_enum(__callee__, ifnone) unless block_given? 203 | 204 | if empty? 205 | if ifnone.nil? 206 | nil 207 | else 208 | ifnone.call 209 | end 210 | else 211 | if yield(head) 212 | head 213 | else 214 | tail.find(ifnone, &block) 215 | end 216 | end 217 | end 218 | 219 | alias detect find 220 | 221 | # Returns the +n+th element of +self+. If +n+ is out of range, +nil+ is 222 | # returned. 223 | # 224 | # @param [Integer, #to_int] n 225 | # @return [Object] the +n+th element. 226 | def [](n) 227 | raise TypeError unless n.respond_to?(:to_int) 228 | int = n.to_int 229 | 230 | if int < 0 || empty? 231 | nil 232 | elsif int == 0 233 | head 234 | else 235 | tail[int - 1] 236 | end 237 | end 238 | 239 | alias at [] 240 | 241 | # Returns the +n+th element of +self+. If +n+ is out of range, +IndexError+ 242 | # returned. 243 | # 244 | # @overload fetch(n) 245 | # @param [Integer, #to_int] n 246 | # @return [Object] the +n+th element. 247 | # @raise [IndexError] if n is out of rage 248 | # @overload fetch(n, ifnone) 249 | # @param [Integer, #to_int] n 250 | # @return ifnone 251 | # @overload fetch(n) {|n|} 252 | # @param [Integer, #to_int] n 253 | # @yield [n] 254 | # @yieldparam [Integer, #to_int] n 255 | def fetch(*args) 256 | alen = args.length 257 | n, ifnone = *args 258 | 259 | unless (1..2).cover?(alen) 260 | raise ArgumentError, "wrong number of arguments (#{alen} for 1..2)" 261 | end 262 | 263 | raise TypeError unless n.respond_to?(:to_int) 264 | int = n.to_int 265 | 266 | return at(int) if int >= 0 && int < length 267 | 268 | if block_given? 269 | if alen == 2 270 | warn "#{__LINE__}:warning: block supersedes default value argument" 271 | end 272 | 273 | yield n 274 | else 275 | if alen == 2 276 | ifnone 277 | else 278 | raise IndexError, "index #{int} outside of list bounds" 279 | end 280 | end 281 | end 282 | 283 | # @overload index(val) 284 | # @return [Integer, nil] index 285 | # @overload index 286 | # @return [Enumerator] 287 | # @overload index {} 288 | # @yieldreturn [Integer, nil] index 289 | def index(*args) 290 | alen = args.length 291 | val = args.first 292 | 293 | raise ArgumentError unless (0..1).cover?(alen) 294 | return to_enum(__callee__) if !block_given? && alen == 0 295 | 296 | if alen == 1 297 | if block_given? 298 | warn "#{__LINE__}:warning: given block not used" 299 | end 300 | 301 | each_with_index { |e, idx| 302 | return idx if e == val 303 | } 304 | else 305 | if block_given? 306 | each_with_index { |e, idx| 307 | return idx if yield(e) 308 | } 309 | end 310 | end 311 | 312 | nil 313 | end 314 | 315 | # @overload rindex(val) 316 | # @return [Integer, nil] index 317 | # @overload rindex 318 | # @return [Enumerator] 319 | # @overload rindex {} 320 | # @yieldreturn [Integer, nil] index 321 | def rindex(*args) 322 | alen = args.length 323 | val = args.first 324 | 325 | raise ArgumentError unless (0..1).cover?(alen) 326 | return to_enum(__callee__) if !block_given? && alen == 0 327 | 328 | if alen == 1 329 | if block_given? 330 | warn "#{__LINE__}:warning: given block not used" 331 | end 332 | 333 | reverse_each.with_index { |e, idx| 334 | return length - (idx + 1) if e == val 335 | } 336 | else 337 | if block_given? 338 | reverse_each.with_index { |e, idx| 339 | return length - (idx + 1) if yield(e) 340 | } 341 | end 342 | end 343 | 344 | nil 345 | end 346 | 347 | # Converts +self+ to a list. 348 | # 349 | # @return [List] a list. 350 | def to_list 351 | foldr(List[]) { |x, xs| Cons[x, xs] } 352 | end 353 | 354 | private 355 | 356 | def class_name 357 | self.class.name.slice(/[^:]*\z/) 358 | end 359 | end 360 | end 361 | -------------------------------------------------------------------------------- /lib/immutable/list.rb: -------------------------------------------------------------------------------- 1 | require_relative "consable" 2 | 3 | module Immutable 4 | # +Immutable::List+ represents an immutable list. 5 | # 6 | # +Immutable::List+ is an abstract class and 7 | # {Immutable::List.[]} should be used instead of 8 | # {Immutable::List.new}. For example: 9 | # 10 | # include Immutable 11 | # p List[] #=> List[] 12 | # p List[1, 2] #=> List[1, 2] 13 | # 14 | # +Immutable::Nil+ represents an empty list, and 15 | # +Immutable::Cons+ represents a cons cell, which is a node in 16 | # a list. For example: 17 | # 18 | # p Nil #=> List[] 19 | # p Cons[1, Cons[2, Nil]] #=> List[1, 2] 20 | class List 21 | include Consable 22 | 23 | # Returns an empty list. 24 | # 25 | # @return [list] the empty list. 26 | def self.empty 27 | Nil 28 | end 29 | 30 | # Adds a new element at the head of +self+. 31 | # 32 | # @param [Object] x the element to add. 33 | # @return [List] a new list. 34 | def cons(x) 35 | Cons[x, self] 36 | end 37 | 38 | # Returns the first element of +self+. If +self+ is empty, 39 | # +Immutable::EmptyError+ is raised. 40 | # 41 | # @return [Object] the first element of +self+. 42 | def head 43 | # this method should be overriden 44 | end 45 | 46 | # Returns the last element of +self+. If +self+ is empty, 47 | # +Immutable::EmptyError+ is raised. 48 | # 49 | # @return [Object] the last element of +self+. 50 | def last 51 | # this method should be overriden 52 | end 53 | 54 | # Returns the elements after the head of +self+. If +self+ is empty, 55 | # +Immutable::EmptyError+ is raised. 56 | # 57 | # @return [List] the elements after the head of +self+. 58 | def tail 59 | # this method should be overriden 60 | end 61 | 62 | # Returns all the elements of +self+ except the last one. 63 | # If +self+ is empty, +Immutable::EmptyError+ is 64 | # raised. 65 | # 66 | # @return [List] the elements of +self+ except the last one. 67 | def init 68 | # this method should be overriden 69 | end 70 | 71 | # Same as {#init}. 72 | # 73 | # @return [List] the elements of +self+ except the last one. 74 | def pop 75 | init 76 | end 77 | 78 | # Returns whether +self+ is empty. 79 | # 80 | # @return [true, false] +true+ if +self+ is empty; otherwise, +false+. 81 | def empty? 82 | # this method should be overriden 83 | end 84 | 85 | # Returns +self+. 86 | # 87 | # @return [List] +self+. 88 | def to_list 89 | self 90 | end 91 | 92 | private 93 | 94 | def class_name 95 | "List" 96 | end 97 | end 98 | 99 | # +Immutable::Nil+ represents an empty list. 100 | Nil = List.new 101 | 102 | # +Immutable::Cons+ represents a cons cell. 103 | class Cons < List 104 | # Creates a new list obtained by prepending +head+ to the list +tail+. 105 | # 106 | # @return [Cons] the new list. 107 | def self.[](head, tail = Nil) 108 | self.new(head, tail) 109 | end 110 | 111 | # Creates a list obtained by prepending +head+ to the list +tail+. 112 | def initialize(head, tail = Nil) 113 | @head = head 114 | @tail = tail 115 | end 116 | 117 | def Nil.head 118 | raise EmptyError 119 | end 120 | 121 | attr_reader :head 122 | 123 | def Nil.last 124 | raise EmptyError 125 | end 126 | 127 | def last 128 | if @tail.empty? 129 | @head 130 | else 131 | @tail.last 132 | end 133 | end 134 | 135 | def Nil.tail 136 | raise EmptyError 137 | end 138 | 139 | attr_reader :tail 140 | 141 | def Nil.init 142 | raise EmptyError 143 | end 144 | 145 | def init 146 | if @tail.empty? 147 | Nil 148 | else 149 | Cons[@head, @tail.init] 150 | end 151 | end 152 | 153 | def Nil.empty? 154 | true 155 | end 156 | 157 | def empty? 158 | false 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /lib/immutable/map.rb: -------------------------------------------------------------------------------- 1 | require_relative "foldable" 2 | 3 | module Immutable 4 | # +Immutable::Map+ represents an immutable map from keys to 5 | # values. 6 | # 7 | # +Immutable::Map+ is an abstract class and 8 | # {Immutable::Map.[]} should be used instead of 9 | # {Immutable::Map.new}. For example: 10 | # 11 | # include Immutable 12 | # p Map[] #=> Map[] 13 | # p Map[a: 1, b: 2] #=> Map[:a => 1, :b => 2] 14 | # 15 | # {#insert} inserts a key/value pair and 16 | # returns a new +Immutable::Map+. The original map never be 17 | # changed by {#insert}. For example: 18 | # 19 | # m = Map[a: 1] 20 | # p m #=> Map[:a => 1] 21 | # m2 = m.insert(:b, 2) 22 | # p m2 #=> Map[:a => 1, :b => 2] 23 | # p m #=> Map[:a => 1] 24 | class Map 25 | include Enumerable 26 | include Foldable 27 | 28 | class InvarianceViolationError < StandardError 29 | end 30 | 31 | # Returns an empty map. 32 | def self.empty 33 | Leaf 34 | end 35 | 36 | # Returns a map that has only one pair whose key is +key+ and whose 37 | # value is +value+. 38 | def self.singleton(key, value) 39 | Leaf.insert(key, value) 40 | end 41 | 42 | # Returns a map that has the same key/value pairs as the 43 | # +Hash+ object +h+. 44 | def self.[](h = {}) 45 | h.inject(Leaf) { |m, (k, v)| m.insert(k, v) } 46 | end 47 | 48 | # Inserts +key+/+value+ in +self+. 49 | def insert(key, value) 50 | ins(key, value).make_black 51 | end 52 | 53 | # Returns the value at +key+ in +self+, or +nil+ if +key+ 54 | # isn't in +self+. 55 | def [](key) 56 | raise ScriptError, "this method should be overriden" 57 | end 58 | 59 | # Deletes +key+ and its value from +self+. 60 | def delete(key) 61 | m = del(key) 62 | if m.empty? 63 | m 64 | else 65 | m.make_black 66 | end 67 | end 68 | 69 | # @return [Boolean] 70 | def ==(x) 71 | x.is_a?(self.class) && (to_h == x.to_h) 72 | end 73 | 74 | alias === == 75 | 76 | def eql?(x) 77 | x.is_a?(self.class) && to_h.eql?(x.to_h) 78 | end 79 | 80 | # @return [Integer] 81 | def hash 82 | to_h.hash 83 | end 84 | 85 | # @return [String] 86 | def inspect 87 | "Map[" + foldr_with_key("") { |k, v, s| 88 | x = k.inspect + " => " + v.inspect 89 | if s.empty? 90 | x 91 | else 92 | x + ", " + s 93 | end 94 | } + "]" 95 | end 96 | 97 | alias to_s inspect 98 | 99 | # Calls +block+ once for each key/value in +self+. 100 | # @yield [key, element] 101 | # @yieldreturn [self] 102 | # @return [self] 103 | def each(&block) 104 | return to_enum(__callee__) unless block_given? 105 | 106 | foldl_with_key(nil) { |x, k, v| yield([k, v]) } 107 | self 108 | end 109 | 110 | alias each_pair each 111 | 112 | # Folds the values in +self+ from right to left. 113 | def foldr(e) 114 | foldr_with_key(e) { |k, v, x| yield(v, x) } 115 | end 116 | 117 | # Folds the values in +self+ from left to right. 118 | def foldl(e) 119 | foldl_with_key(e) { |x, k, v| yield(x, v) } 120 | end 121 | 122 | # Maps the given block over all values in +self+. 123 | def map 124 | map_with_key { |k, v| yield(v) } 125 | end 126 | 127 | # Maps the given block over all keys and values in +self+. 128 | def map_with_key 129 | foldr_with_key(List[]) { |k, v, xs| Cons[yield(k, v), xs] } 130 | end 131 | 132 | # @return [Hash] 133 | def to_h 134 | Hash[each_pair.to_a] 135 | end 136 | 137 | # :nodoc: 138 | Leaf = Map.new 139 | 140 | def Leaf.empty? 141 | true 142 | end 143 | 144 | def Leaf.red? 145 | false 146 | end 147 | 148 | def Leaf.black? 149 | true 150 | end 151 | 152 | def Leaf.[](key) 153 | nil 154 | end 155 | 156 | def Leaf.ins(key, value) 157 | RedFork[Leaf, key, value, Leaf] 158 | end 159 | 160 | def Leaf.del(key) 161 | Leaf 162 | end 163 | 164 | class Fork < Map #:nodoc: 165 | attr_reader :left, :key, :value, :right 166 | 167 | def initialize(left, key, value, right) 168 | @left = left 169 | @key = key 170 | @value = value 171 | @right = right 172 | end 173 | 174 | class << self 175 | alias [] new 176 | end 177 | 178 | def empty? 179 | false 180 | end 181 | 182 | def [](key) 183 | x = key <=> @key 184 | if x < 0 185 | @left[key] 186 | elsif x > 0 187 | @right[key] 188 | else 189 | @value 190 | end 191 | end 192 | 193 | def del(key) 194 | if key < self.key 195 | del_left(left, self.key, self.value, right, key) 196 | elsif key > self.key 197 | del_right(left, self.key, self.value, right, key) 198 | else 199 | app(left, right) 200 | end 201 | end 202 | 203 | def Leaf.foldr_with_key(e) 204 | e 205 | end 206 | 207 | def foldr_with_key(e, &block) 208 | r = @right.foldr_with_key(e, &block) 209 | @left.foldr_with_key(yield(@key, @value, r), &block) 210 | end 211 | 212 | def Leaf.foldl_with_key(e) 213 | e 214 | end 215 | 216 | def foldl_with_key(e, &block) 217 | l = @left.foldl_with_key(e, &block) 218 | @right.foldl_with_key(yield(l, @key, @value), &block) 219 | end 220 | 221 | def deconstruct 222 | [@left, @key, @value, @right] 223 | end 224 | 225 | private 226 | 227 | def balance(*args) 228 | case args 229 | in [RedFork[a, xk, xv, b], yk, yv, RedFork[c, zk, zv, d]] 230 | RedFork[BlackFork[a, xk, xv, b], yk, yv, BlackFork[c, zk, zv, d]] 231 | in [RedFork[RedFork[a, xk, xv, b], yk, yv, c], zk, zv, d] 232 | RedFork[BlackFork[a, xk, xv, b], yk, yv, BlackFork[c, zk, zv, d]] 233 | in [RedFork[a, xk, xv, RedFork[b, yk, yv, c]], zk, zv, d] 234 | RedFork[BlackFork[a, xk, xv, b], yk, yv, BlackFork[c, zk, zv, d]] 235 | in [a, xk, xv, RedFork[b, yk, yv, RedFork[c, zk, zv, d]]] 236 | RedFork[BlackFork[a, xk, xv, b], yk, yv, BlackFork[c, zk, zv, d]] 237 | in [a, xk, xv, RedFork[RedFork[b, yk, yv, c], zk, zv, d]] 238 | RedFork[BlackFork[a, xk, xv, b], yk, yv, BlackFork[c, zk, zv, d]] 239 | in [a, xk, xv, b] 240 | BlackFork[a, xk, xv, b] 241 | end 242 | end 243 | 244 | def del_left(left, key, value, right, del_key) 245 | if left.black? 246 | bal_left(left.del(del_key), key, value, right) 247 | else 248 | RedFork[left.del(del_key), key, value, right] 249 | end 250 | end 251 | 252 | def del_right(left, key, value, right, del_key) 253 | if right.black? 254 | bal_right(left, key, value, right.del(del_key)) 255 | else 256 | RedFork[left, key, value, right.del(del_key)] 257 | end 258 | end 259 | 260 | def bal_left(*args) 261 | case args 262 | in [RedFork[a, xk, xv, b], yk, yv, c] 263 | RedFork[BlackFork[a, xk, xv, b], yk, yv, c] 264 | in [a, xk, xv, BlackFork[b, yk, yv, c]] 265 | balance(a, xk, xv, RedFork[b, yk, yv, c]) 266 | in [a, xk, xv, RedFork[BlackFork[b, yk, yv, c], zk, zv, d]] 267 | RedFork[ 268 | BlackFork[a, xk, xv, b], 269 | yk, yv, 270 | balance(c, zk, zv, sub1(d)) 271 | ] 272 | end 273 | end 274 | 275 | def bal_right(*args) 276 | case args 277 | in [a, xk, xv, RedFork[b, yk, yv, c]] 278 | RedFork[a, xk, xv, BlackFork[b, yk, yv, c]] 279 | in [BlackFork[a, xk, xv, b], yk, yv, c] 280 | balance(RedFork[a, xk, xv, b], yk, yv, c) 281 | in [RedFork[a, xk, xv, BlackFork[b, yk, yv, c]], zk, zv, d] 282 | RedFork[ 283 | balance(sub1(a), xk, xv, b), 284 | yk, yv, 285 | BlackFork[c, zk, zv, d] 286 | ] 287 | end 288 | end 289 | 290 | def sub1(node) 291 | if node.black? 292 | node.make_red 293 | else 294 | raise InvarianceViolationError, "invariance violation" 295 | end 296 | end 297 | 298 | def app(left, right) 299 | if left.empty? 300 | right 301 | elsif right.empty? 302 | left 303 | elsif left.red? && right.red? 304 | m = app(left.right, right.left) 305 | if m.red? 306 | RedFork[ 307 | RedFork[left.left, left.key, left.value, m.left], 308 | m.key, m.value, 309 | RedFork[m.right, right.key, right.value, right.right] 310 | ] 311 | else 312 | RedFork[ 313 | left.left, left.key, left.value, 314 | RedFork[m, right.key, right.value, right.right] 315 | ] 316 | end 317 | elsif left.black? && right.black? 318 | m = app(left.right, right.left) 319 | if m.red? 320 | RedFork[ 321 | BlackFork[left.left, left.key, left.value, m.left], 322 | m.key, m.value, 323 | BlackFork[m.right, right.key, right.value, right.right] 324 | ] 325 | else 326 | bal_left(left.left, left.key, left.value, 327 | BlackFork[m, right.key, right.value, right.right]) 328 | end 329 | elsif right.red? 330 | RedFork[app(left, right.left), right.key, right.value, 331 | right.right] 332 | elsif left.red? 333 | RedFork[left.left, left.key, left.value, app(left.right, right)] 334 | else 335 | raise ScriptError, "should not reach here" 336 | end 337 | end 338 | end 339 | 340 | class RedFork < Fork #:nodoc: 341 | def red? 342 | true 343 | end 344 | 345 | def black? 346 | false 347 | end 348 | 349 | def make_red 350 | self 351 | end 352 | 353 | def make_black 354 | BlackFork[left, key, value, right] 355 | end 356 | 357 | def ins(key, value) 358 | x = key <=> @key 359 | if x < 0 360 | RedFork[@left.ins(key, value), @key, @value, @right] 361 | elsif x > 0 362 | RedFork[@left, @key, @value, @right.ins(key, value)] 363 | else 364 | RedFork[@left, key, value, @right] 365 | end 366 | end 367 | end 368 | 369 | class BlackFork < Fork #:nodoc: 370 | def red? 371 | false 372 | end 373 | 374 | def black? 375 | true 376 | end 377 | 378 | def make_red 379 | RedFork[left, key, value, right] 380 | end 381 | 382 | def make_black 383 | self 384 | end 385 | 386 | def ins(key, value) 387 | x = key <=> @key 388 | if x < 0 389 | balance(@left.ins(key, value), @key, @value, @right) 390 | elsif x > 0 391 | balance(@left, @key, @value, @right.ins(key, value)) 392 | else 393 | BlackFork[@left, key, value, @right] 394 | end 395 | end 396 | end 397 | end 398 | end 399 | -------------------------------------------------------------------------------- /lib/immutable/output_restricted_deque.rb: -------------------------------------------------------------------------------- 1 | require_relative "queue" 2 | 3 | module Immutable 4 | # +Immutable::OutputRestrictedDeque+ is an implementation of 5 | # output-restricted deques described in "Purely Functional Data 6 | # Structures" by Chris Okasaki. 7 | class OutputRestrictedDeque < Queue 8 | include Consable 9 | 10 | # Adds a new element at the head of +self+. 11 | # 12 | # @param [Object] x the element to add. 13 | # @return [Queue] a new queue. 14 | def cons(x) 15 | self.class.new(Stream.cons(->{x}, ->{@front}), @rear, 16 | Stream.cons(->{x}, ->{@schedule})) 17 | end 18 | alias prepend cons 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/immutable/promise.rb: -------------------------------------------------------------------------------- 1 | module Immutable 2 | # +Immutable::Promise+ represents a promise to evaluate an expression 3 | # later. 4 | # 5 | # @example Delayed computation 6 | # promise = Promise.delay { puts "hello"; 1 + 2 } 7 | # x = promise.force #=> hello 8 | # p x #=> 3 9 | # y = promise.force #=> (no output; the value is memoized) 10 | # p y #=> 3 11 | # @example Infinite streams 12 | # def from(n) 13 | # Promise.delay { 14 | # Cons[n, from(n + 1)] 15 | # } 16 | # end 17 | # 18 | # def stream_ref(s, n) 19 | # xs = s.force 20 | # if xs.empty? 21 | # nil 22 | # else 23 | # n == 0 ? xs.head : stream_ref(xs.tail, n - 1) 24 | # end 25 | # end 26 | # 27 | # nats = from(0) 28 | # p stream_ref(nats, 0) #=> 0 29 | # p stream_ref(nats, 3) #=> 3 30 | class Promise 31 | # :nodoc: 32 | Content = Struct.new(:type, :value) 33 | 34 | def initialize(type, value) 35 | @content = Content.new(type, value) 36 | end 37 | 38 | private_class_method :new 39 | 40 | # Takes a block which evaluates to a promise, and returns a promise 41 | # which at some point in the future may be asked (by +Promise#force+) 42 | # to evaluate the block and deliver the value of the resulting promise. 43 | # 44 | # @return [Promise] the created promise. 45 | def self.lazy(&block) 46 | new(:lazy, block) 47 | end 48 | 49 | # Takes an argument, and returns a promise which deliver the value of 50 | # the argument. 51 | # 52 | # Promise.eager(expresion) is equivalent to 53 | # (value = Promise.eager; Promise.delay { value }). 54 | # 55 | # @param [Object] value the value to be returned by +Promise#force+. 56 | # @return [Promise] the created promise. 57 | def self.eager(value) 58 | new(:eager, value) 59 | end 60 | 61 | # Returns whether +self+ is lazy. 62 | # 63 | # @return [true, false] +true+ if +self+ is lazy; otherwise, +false+. 64 | def lazy? 65 | content.type == :lazy 66 | end 67 | 68 | # Returns whether +self+ is eager. 69 | # 70 | # @return [true, false] +true+ if +self+ is eager; otherwise, +false+. 71 | def eager? 72 | content.type == :eager 73 | end 74 | 75 | # Takes a block, and returns a promise which at some point in the future 76 | # may be asked (by +Promise#force+) to evaluate the block and deliver 77 | # the resulting value. 78 | # 79 | # Promise.delay { expression } is equivalent to 80 | # Promise.lazy { Promise.eager(expression) }. 81 | # 82 | # @return [Promise] the created promise. 83 | def self.delay 84 | lazy { 85 | eager(yield) 86 | } 87 | end 88 | 89 | # Returns the value of +self+. 90 | # If a value of +self+ has already been computated, the value is 91 | # returned. Otherwise, the promise is first evaluated, and the resulting 92 | # value is returned. 93 | # 94 | # @return [Object] the value of +self+. 95 | def force 96 | case content.type 97 | when :eager 98 | content.value 99 | when :lazy 100 | promise = content.value.call 101 | if content.type != :eager 102 | content.type = promise.content.type 103 | content.value = promise.content.value 104 | promise.content = content 105 | end 106 | force 107 | else 108 | raise ScriptError, "should not reach here" 109 | end 110 | end 111 | 112 | protected 113 | 114 | attr_accessor :content 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/immutable/queue.rb: -------------------------------------------------------------------------------- 1 | require_relative "stream" 2 | 3 | module Immutable 4 | # +Immutable::Queue+ is an implementation of real-time queues described in 5 | # "Purely Functional Data Structures" by Chris Okasaki. 6 | class Queue 7 | include Headable 8 | 9 | # +Queue.new+ is for internal use only. Use {Queue.empty} or {Queue.[]} 10 | # instead. 11 | def initialize(front, rear, schedule) 12 | @front = front 13 | @rear = rear 14 | @schedule = schedule 15 | end 16 | 17 | # Returns an empty queue. 18 | # 19 | # @return [Queue] the empty queue. 20 | def self.empty 21 | new(Stream.empty, Nil, Stream.empty) 22 | end 23 | 24 | # Creates a new queue populated with the given objects. 25 | # 26 | # @param [Array] elements the elements of the queue. 27 | # @return [Queue] the new queue. 28 | def self.[](*elements) 29 | elements.inject(empty, &:snoc) 30 | end 31 | 32 | # Returns whether +self+ is empty. 33 | # 34 | # @return [true, false] +true+ if +self+ is empty; otherwise, +false+. 35 | def empty? 36 | @front.empty? 37 | end 38 | 39 | # Adds a new element at the end of +self+. 40 | # 41 | # @param [Object] x the element to add. 42 | # @return [Queue] a new queue. 43 | def snoc(x) 44 | queue(@front, Cons[x, @rear], @schedule) 45 | end 46 | 47 | alias push snoc 48 | 49 | # Returns the first element of +self+. If +self+ is empty, 50 | # +Immutable::EmptyError+ is raised. 51 | # 52 | # @return [Object] the first element of +self+. 53 | def head 54 | @front.head 55 | end 56 | 57 | # Returns the elements after the head of +self+. If +self+ is empty, 58 | # +Immutable::EmptyError+ is raised. 59 | # 60 | # @return [Queue] the elements after the head of +self+. 61 | def tail 62 | if @front.empty? 63 | raise EmptyError 64 | else 65 | queue(@front.tail, @rear, @schedule) 66 | end 67 | end 68 | 69 | private 70 | 71 | def rotate(front, rear, accumulator) 72 | Stream.lazy { 73 | if front.empty? 74 | Stream.cons(->{rear.head}, ->{accumulator}) 75 | else 76 | Stream.cons(->{front.head}, ->{ 77 | rotate(front.tail, rear.tail, 78 | Stream.cons(->{rear.head}, ->{accumulator})) 79 | }) 80 | end 81 | } 82 | end 83 | 84 | def queue(front, rear, schedule) 85 | if schedule.empty? 86 | f = rotate(front, rear, Stream.empty) 87 | self.class.new(f, Nil, f) 88 | else 89 | self.class.new(front, rear, schedule.tail) 90 | end 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/immutable/stream.rb: -------------------------------------------------------------------------------- 1 | require_relative "list" 2 | require_relative "promise" 3 | 4 | module Immutable 5 | # +Immutable::Stream+ represents a stream, also known as a lazy list. 6 | # A stream is similar to a list. However the evaluation of a stream 7 | # element is delayed until its value is needed 8 | # 9 | # @example Natural numbers 10 | # def from(n) 11 | # Stream.cons ->{n}, ->{from(n + 1)} 12 | # end 13 | # 14 | # nats = from(0) 15 | # p from(0).take(10).to_list #=> List[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 16 | # @example Prime numbers 17 | # primes = Stream.from(2).filter { |n| 18 | # (2 ... n / 2 + 1).all? { |x| 19 | # n % x != 0 20 | # } 21 | # } 22 | # p primes.take_while { |n| n < 10 }.to_list #=> List[2, 3, 5, 7] 23 | class Stream < Promise 24 | include Consable 25 | 26 | NULL = Object.new 27 | 28 | def NULL.head 29 | raise EmptyError 30 | end 31 | 32 | def NULL.tail 33 | raise EmptyError 34 | end 35 | 36 | def NULL.inspect 37 | "NULL" 38 | end 39 | 40 | class Pair 41 | attr_reader :head, :tail 42 | 43 | def initialize(head, tail) 44 | @head = head 45 | @tail = tail 46 | end 47 | end 48 | 49 | # Returns an empty stream. 50 | # 51 | # @return [Stream] an empty stream. 52 | def self.empty 53 | delay { NULL } 54 | end 55 | 56 | # Creates a new stream. 57 | # 58 | # @example A Stream which has 123 as the only element. 59 | # s = Stream.cons(->{123}, ->{Stream.empty}) 60 | # p s.to_list #=> List[123] 61 | # @example A Stream which has two elements: "abc" and "def". 62 | # s = Stream.cons(->{"abc"}, 63 | # ->{Stream.cons(->{"def"}, ->{Stream.empty})}) 64 | # p s.to_list #=> List["abc", "def"] 65 | # 66 | # @param [Proc] head a +Proc+ whose value is the head of +self+. 67 | # @param [Proc] tail a +Proc+ whose value is the tail of +self+. 68 | # @return [Stream] the new stream. 69 | def self.cons(head, tail) 70 | Stream.eager(Pair.new(Stream.delay(&head), Stream.lazy(&tail))) 71 | end 72 | 73 | # Creates a new stream whose head is the value of +x+ or +block+ and 74 | # whose tail is +self+. 75 | # 76 | # @example A Stream which has 123 as the only element. 77 | # s = Stream.empty.cons(123) 78 | # p s.to_list #=> List[123] 79 | # @example A Stream which has two elements: "abc" and "def". 80 | # s = Stream.empty.cons {"def"}.cons {"abc"} 81 | # p s.to_list #=> List["abc", "def"] 82 | # 83 | # @return [Stream] the new stream. 84 | def cons(x = nil, &block) 85 | if block 86 | Stream.eager(Pair.new(Stream.delay(&block), self)) 87 | else 88 | Stream.eager(Pair.new(Stream.eager(x), self)) 89 | end 90 | end 91 | 92 | # Creates a new stream. Note that the arguments are evaluated eagerly. 93 | # 94 | # @param [Array] elements the elements of the stream. 95 | # @return [Stream] the new stream. 96 | def self.[](*elements) 97 | from_enum(elements) 98 | end 99 | 100 | # Creates a new stream from an +Enumerable+ object. 101 | # 102 | # @param [Enumerable] e an +Enumerable+ object. 103 | # @return [Stream] the new stream. 104 | def self.from_enum(e) 105 | from_enumerator(e.each) 106 | end 107 | 108 | # Creates a new stream from an +Enumerator+ object. 109 | # Note that +from_enumerator+ has side effects because it calls 110 | # +Enumerator#next+. 111 | # 112 | # @param [Enumerator] e an +Enumerator+ object. 113 | # @return [Stream] the new stream. 114 | def self.from_enumerator(e) 115 | lazy { 116 | begin 117 | x = e.next 118 | cons ->{ x }, ->{ from_enumerator(e) } 119 | rescue StopIteration 120 | empty 121 | end 122 | } 123 | end 124 | 125 | # Creates an infinite stream which starts from +first+ and increments 126 | # each succeeding element by +step+. 127 | # 128 | # @param [#+] first the first element. 129 | # @param [#+] step the step for succeeding elements. 130 | # @return [Stream] the new stream. 131 | def self.from(first, step = 1) 132 | cons ->{ first }, ->{ from(first + step, step) } 133 | end 134 | 135 | # Returns whether +self+ is empty. 136 | # 137 | # @return [true, false] +true+ if +self+ is empty; otherwise, +false+. 138 | def empty? 139 | force == NULL 140 | end 141 | 142 | # Returns the first element of +self+. If +self+ is empty, 143 | # +Immutable::EmptyError+ is raised. 144 | # 145 | # @return [Object] the first element of +self+. 146 | def head 147 | force.head.force 148 | end 149 | 150 | # Returns the last element of +self+. If +self+ is empty, 151 | # +Immutable::EmptyError+ is raised. 152 | # 153 | # @return [Object] the last element of +self+. 154 | def last 155 | if tail.empty? 156 | head 157 | else 158 | tail.last 159 | end 160 | end 161 | 162 | # Returns the stream stored in the tail of +self+. If +self+ is empty, 163 | # +Immutable::EmptyError+ is raised. 164 | # 165 | # @return [Stream] the stream stored in the tail of +self+. 166 | def tail 167 | force.tail 168 | end 169 | 170 | # Returns all the elements of +self+ except the last one. 171 | # If +self+ is empty, +Immutable::EmptyError+ is 172 | # raised. 173 | # 174 | # @return [Stream] the elements of +self+ except the last one. 175 | def init 176 | Stream.lazy { 177 | if empty? 178 | raise EmptyError 179 | else 180 | if tail.empty? 181 | Stream.empty 182 | else 183 | Stream.cons(->{head}, ->{tail.init}) 184 | end 185 | end 186 | } 187 | end 188 | 189 | # Creates a string representation of +self+. 190 | # 191 | # @return [String] a stream representation of +self+. 192 | def inspect 193 | "Stream[" + inspect_i + "]" 194 | end 195 | 196 | alias to_s inspect 197 | 198 | def inspect_i(s = nil) 199 | if eager? 200 | if empty? 201 | s || "" 202 | else 203 | h = force.head.eager? ? head.inspect : "?" 204 | if s 205 | tail.inspect_i(s + ", " + h) 206 | else 207 | tail.inspect_i(h) 208 | end 209 | end 210 | else 211 | if s 212 | s + ", ..." 213 | else 214 | "..." 215 | end 216 | end 217 | end 218 | protected :inspect_i 219 | 220 | # Appends two streams +self+ and +s+. 221 | # 222 | # @param [Stream] s the stream to append. 223 | # @return [Stream] the new stream. 224 | def +(s) 225 | Stream.lazy { 226 | if empty? 227 | s 228 | else 229 | Stream.cons ->{head}, ->{tail + s} 230 | end 231 | } 232 | end 233 | 234 | # Returns the stream obtained by applying the given block to each 235 | # element in +self+. 236 | # 237 | # @return [Stream] the obtained stream. 238 | def map(&block) 239 | Stream.lazy { 240 | if empty? 241 | Stream.empty 242 | else 243 | Stream.cons ->{ yield(head) }, ->{ tail.map(&block) } 244 | end 245 | } 246 | end 247 | 248 | # Returns the elements of +self+ in reverse order. 249 | # 250 | # @return [Stream] the reversed stream. 251 | def reverse 252 | foldl(Stream.empty) { |x, y| Stream.cons(->{y}, ->{x}) } 253 | end 254 | 255 | # Returns a new stream obtained by inserting +sep+ in between the 256 | # elements of +self+. 257 | # 258 | # @param [Object] sep the object to insert between elements. 259 | # @return [Stream] the new stream. 260 | def intersperse(sep) 261 | Stream.lazy { 262 | if empty? 263 | self 264 | else 265 | Stream.cons(->{head}, ->{tail.prepend_to_all(sep)}) 266 | end 267 | } 268 | end 269 | 270 | def prepend_to_all(sep) 271 | Stream.lazy { 272 | if empty? 273 | self 274 | else 275 | Stream.cons ->{sep}, ->{ 276 | Stream.cons ->{head}, ->{tail.prepend_to_all(sep)} 277 | } 278 | end 279 | } 280 | end 281 | protected :prepend_to_all 282 | 283 | # Returns a new stream obtained by inserting +xs+ in between the streams 284 | # in +self+ and concatenates the result. 285 | # +xss.intercalate(xs)+ is equivalent to 286 | # +xss.intersperse(xs).flatten+. 287 | # 288 | # @param [Stream] xs the stream to insert between streams. 289 | # @return [Stream] the new stream. 290 | def intercalate(xs) 291 | intersperse(xs).flatten 292 | end 293 | 294 | # Concatenates a stream of streams. 295 | # 296 | # @return [Stream] the concatenated stream. 297 | def flatten 298 | Stream.lazy { 299 | if empty? 300 | self 301 | else 302 | if head.empty? 303 | tail.flatten 304 | else 305 | Stream.cons ->{head.head}, ->{ 306 | Stream.cons(->{head.tail}, ->{tail}).flatten 307 | } 308 | end 309 | end 310 | } 311 | end 312 | 313 | alias concat flatten 314 | 315 | # Returns the first +n+ elements of +self+, or +self+ itself if 316 | # +n > self.length+. 317 | # 318 | # @param [Integer] n the number of elements to take. 319 | # @return [Stream] the first +n+ elements of +self+. 320 | def take(n) 321 | Stream.lazy { 322 | if n <= 0 || empty? 323 | Stream.empty 324 | else 325 | Stream.cons ->{ head }, ->{ tail.take(n - 1) } 326 | end 327 | } 328 | end 329 | 330 | # Returns the suffix of +self+ after the first +n+ elements, or 331 | # +Stream[]+ if +n > self.length+. 332 | # 333 | # @param [Integer] n the number of elements to drop. 334 | # @return [Stream] the suffix of +self+ after the first +n+ elements. 335 | def drop(n) 336 | Stream.lazy { 337 | if n <= 0 || empty? 338 | self 339 | else 340 | tail.drop(n - 1) 341 | end 342 | } 343 | end 344 | 345 | # Returns the longest prefix of the elements of +self+ for which +block+ 346 | # evaluates to true. 347 | # 348 | # @return [Stream] the prefix of the elements of +self+. 349 | def take_while(&block) 350 | Stream.lazy { 351 | if empty? || !yield(head) 352 | Stream.empty 353 | else 354 | Stream.cons ->{ head }, ->{ tail.take_while(&block) } 355 | end 356 | } 357 | end 358 | 359 | # Returns the suffix remaining after 360 | # +self.take_while(&block)+. 361 | # 362 | # @return [Stream] the suffix of the elements of +self+. 363 | def drop_while(&block) 364 | Stream.lazy { 365 | if empty? || !yield(head) 366 | self 367 | else 368 | tail.drop_while(&block) 369 | end 370 | } 371 | end 372 | 373 | # Returns the elements in +self+ for which the given block evaluates to 374 | # true. 375 | # 376 | # @return [Stream] the elements that satisfies the condition. 377 | def filter(&block) 378 | Stream.lazy { 379 | if empty? 380 | Stream.empty 381 | else 382 | if yield(head) 383 | Stream.cons ->{ head }, ->{ tail.filter(&block) } 384 | else 385 | tail.filter(&block) 386 | end 387 | end 388 | } 389 | end 390 | 391 | # Builds a stream from the seed value +e+ and the given block. The block 392 | # takes a seed value and returns +nil+ if the seed should 393 | # unfold to the empty stream, or returns +[a, b]+, where 394 | # +a+ is the head of the stream and +b+ is the 395 | # next seed from which to unfold the tail. For example: 396 | # 397 | # xs = List.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] } 398 | # p xs #=> List[3, 2, 1] 399 | # 400 | # +unfoldr+ is the dual of +foldr+. 401 | # 402 | # @param [Object] e the seed value. 403 | # @return [Stream] the stream built from the seed value and the block. 404 | def self.unfoldr(e, &block) 405 | Stream.lazy { 406 | x = yield(e) 407 | if x.nil? 408 | Stream.empty 409 | else 410 | y, z = x 411 | Stream.cons ->{ y }, ->{ unfoldr(z, &block) } 412 | end 413 | } 414 | end 415 | 416 | # Takes zero or more streams and returns a new stream in which each 417 | # element is an array of the corresponding elements of +self+ and the 418 | # input streams. 419 | # 420 | # @param [Array] xss the input streams. 421 | # @return [Stream] the new stream. 422 | def zip(*xss) 423 | Stream.lazy { 424 | if empty? 425 | self 426 | else 427 | heads = xss.map { |xs| xs.empty? ? nil : xs.head } 428 | tails = xss.map { |xs| xs.empty? ? Stream.empty : xs.tail } 429 | Stream.cons ->{ [head, *heads] }, ->{ tail.zip(*tails) } 430 | end 431 | } 432 | end 433 | 434 | # Takes zero or more streams and returns the stream obtained by applying 435 | # the given block to an array of the corresponding elements of +self+ 436 | # and the input streams. 437 | # +xs.zip_with(*yss, &block)+ is equivalent to 438 | # +xs.zip(*yss).map(&block)+. 439 | # 440 | # @param [Array] xss the input streams. 441 | # @return [Stream] the new stream. 442 | def zip_with(*xss, &block) 443 | Stream.lazy { 444 | if empty? 445 | self 446 | else 447 | heads = xss.map { |xs| xs.empty? ? nil : xs.head } 448 | tails = xss.map { |xs| xs.empty? ? Stream.empty : xs.tail } 449 | h = yield(head, *heads) 450 | Stream.cons ->{ h }, ->{ tail.zip_with(*tails, &block) } 451 | end 452 | } 453 | end 454 | end 455 | end 456 | -------------------------------------------------------------------------------- /test/immutable/test_deque.rb: -------------------------------------------------------------------------------- 1 | require_relative "../test_helper" 2 | 3 | with_tailcall_optimization { 4 | require_relative "../../lib/immutable/deque" 5 | } 6 | 7 | module Immutable 8 | class TestDeque < Test::Unit::TestCase 9 | def test_head 10 | assert_raise(EmptyError) do 11 | Deque[].head 12 | end 13 | assert_equal(1, Deque[1].head) 14 | assert_equal(1, Deque[1, 2, 3].head) 15 | end 16 | 17 | def test_last 18 | assert_raise(EmptyError) do 19 | Deque[].last 20 | end 21 | assert_equal(1, Deque[1].last) 22 | assert_equal(3, Deque[1, 2, 3].last) 23 | end 24 | 25 | def test_tail 26 | assert_raise(EmptyError) do 27 | Deque[].tail 28 | end 29 | assert(Deque[1].tail.empty?) 30 | assert_equal(2, Deque[1, 2].tail.head) 31 | assert_equal(2, Deque[1, 2, 3].tail.head) 32 | assert_equal(3, Deque[1, 2, 3].tail.tail.head) 33 | end 34 | 35 | def test_init 36 | assert_raise(EmptyError) do 37 | Deque[].init 38 | end 39 | assert(Deque[1].init.empty?) 40 | assert_equal(1, Deque[1, 2].init.last) 41 | assert_equal(2, Deque[1, 2, 3].init.last) 42 | assert_equal(1, Deque[1, 2, 3].init.init.last) 43 | end 44 | 45 | def test_cons 46 | q1 = Deque.empty.cons(1) 47 | assert_equal(1, q1.head) 48 | assert(q1.tail.empty?) 49 | q2 = q1.cons(2) 50 | assert_equal(2, q2.head) 51 | assert_equal(1, q2.tail.head) 52 | assert(q2.tail.tail.empty?) 53 | assert_equal(1, q1.head) 54 | assert(q1.tail.empty?) 55 | 56 | a = (1..1000).to_a.shuffle 57 | q = a.inject(Deque.empty, :cons) 58 | assert_equal(a.reverse, q.to_a) 59 | end 60 | 61 | def test_snoc 62 | q1 = Deque.empty.snoc(1) 63 | assert_equal(1, q1.head) 64 | assert(q1.tail.empty?) 65 | q2 = q1.snoc(2) 66 | assert_equal(1, q2.head) 67 | assert_equal(2, q2.tail.head) 68 | assert(q2.tail.tail.empty?) 69 | assert_equal(1, q1.head) 70 | assert(q1.tail.empty?) 71 | 72 | a = (1..1000).to_a.shuffle 73 | q = a.inject(Deque.empty, :snoc) 74 | assert_equal(a, q.to_a) 75 | end 76 | 77 | def test_invariants 78 | a = (1..1000).to_a.shuffle 79 | deque = a.inject(Deque.empty) { |d, i| 80 | assert_deque_invariants(d) 81 | if rand(2) == 0 82 | d2 = d.snoc(i) 83 | else 84 | d2 = d.cons(i) 85 | end 86 | case rand(4) 87 | when 0 88 | d2.tail 89 | when 1 90 | d2.init 91 | else 92 | d2 93 | end 94 | } 95 | assert_deque_invariants(deque) 96 | until deque.empty? 97 | if rand(2) == 0 98 | deque = deque.tail 99 | else 100 | deque = deque.init 101 | end 102 | assert_deque_invariants(deque) 103 | end 104 | end 105 | 106 | private 107 | 108 | def assert_deque_invariants(d) 109 | c = d.instance_variable_get(:@c) 110 | front_len = d.instance_variable_get(:@front_len) 111 | rear_len = d.instance_variable_get(:@rear_len) 112 | assert(front_len <= c * rear_len + 1) 113 | assert(rear_len <= c * front_len + 1) 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /test/immutable/test_list.rb: -------------------------------------------------------------------------------- 1 | require_relative "../test_helper" 2 | 3 | with_tailcall_optimization { 4 | require_relative "../../lib/immutable/list" 5 | } 6 | 7 | module Immutable 8 | class TestList < Test::Unit::TestCase 9 | def test_s_from_array 10 | assert_equal(List[], List.from_array([])) 11 | assert_equal(List[1, 2, 3], List.from_array([1, 2, 3])) 12 | end 13 | 14 | def test_s_from_enum 15 | assert_equal(List[], List.from_enum([])) 16 | assert_equal(List[1, 2, 3], List.from_enum(1..3)) 17 | assert_equal(List["a", "b", "c"], List.from_enum("abc".each_char)) 18 | end 19 | 20 | def test_head 21 | assert_raise(EmptyError) do 22 | List[].head 23 | end 24 | assert_equal(1, List[1].head) 25 | assert_equal(1, List[1, 2, 3].head) 26 | end 27 | 28 | def test_last 29 | assert_raise(EmptyError) do 30 | List[].last 31 | end 32 | assert_equal(1, List[1].last) 33 | assert_equal(3, List[1, 2, 3].last) 34 | end 35 | 36 | def test_tail 37 | assert_raise(EmptyError) do 38 | List[].tail 39 | end 40 | assert_equal(List[], List[1].tail) 41 | assert_equal(List[2, 3], List[1, 2, 3].tail) 42 | end 43 | 44 | def test_init 45 | assert_raise(EmptyError) do 46 | List[].init 47 | end 48 | assert_equal(List[], List[1].init) 49 | assert_equal(List[1, 2], List[1, 2, 3].init) 50 | end 51 | 52 | def test_empty? 53 | assert(List[].empty?) 54 | assert(!List[1].empty?) 55 | assert(!List[1, 2, 3].empty?) 56 | end 57 | 58 | def test_each 59 | list = List[] 60 | a = [] 61 | assert_same(list, list.each { |x| a << x }) 62 | assert_equal([], a) 63 | 64 | list = List[1, 2, 3] 65 | a = [] 66 | assert_same(list, list.each { |x| a << x }) 67 | assert_equal([1, 2, 3], a) 68 | 69 | enum = List[1, 2, 3].each 70 | assert_instance_of(Enumerator, enum) 71 | assert_equal(1, enum.next) 72 | assert_equal(2, enum.next) 73 | assert_equal(3, enum.next) 74 | assert_raise(StopIteration) do 75 | enum.next 76 | end 77 | end 78 | 79 | def test_each_index 80 | list = List[] 81 | a = [] 82 | assert_same(list, list.each_index { |x| a << x }) 83 | assert_equal([], a) 84 | 85 | list = List[1, 2, 3] 86 | a = [] 87 | assert_same(list, list.each_index { |x| a << x }) 88 | assert_equal([0, 1, 2], a) 89 | 90 | enum = List[1, 2, 3].each_index 91 | assert_instance_of(Enumerator, enum) 92 | assert_equal(0, enum.next) 93 | assert_equal(1, enum.next) 94 | assert_equal(2, enum.next) 95 | assert_raise(StopIteration) do 96 | enum.next 97 | end 98 | end 99 | 100 | def test_index 101 | list = List[] 102 | assert_equal(nil, list.index(1)) 103 | 104 | list = List[1, 2, 1] 105 | assert_equal(0, list.index(1)) 106 | assert_equal(1, list.index(2)) 107 | assert_equal(nil, list.index(3)) 108 | 109 | verbose = $VERBOSE 110 | $VERBOSE = nil 111 | list = List[1, 2, 1] 112 | assert_equal(0, list.index(1) {|e| e * 2 == 4 }) 113 | assert_equal(1, list.index(2) {|e| e * 2 == 4 }) 114 | assert_equal(nil, list.index(3) {|e| e * 2 == 4 }) 115 | $VERBOSE = verbose 116 | 117 | list = List[1, 2, 1] 118 | assert_equal(0, list.index {|e| e * 2 == 2 }) 119 | assert_equal(1, list.index {|e| e * 2 == 4 }) 120 | assert_equal(nil, list.index {|e| e * 2 == 6 }) 121 | 122 | enum = List[1, 2, 1].index 123 | assert_instance_of(Enumerator, enum) 124 | assert_equal(0, enum.each {|v|v == 1}) 125 | assert_equal(1, enum.each {|v|v == 2}) 126 | assert_equal(nil, enum.each {|v|v == 3}) 127 | end 128 | 129 | def test_rindex 130 | list = List[] 131 | assert_equal(nil, list.rindex(1)) 132 | 133 | list = List[1, 2, 1] 134 | assert_equal(2, list.rindex(1)) 135 | assert_equal(1, list.rindex(2)) 136 | assert_equal(nil, list.rindex(3)) 137 | 138 | verbose = $VERBOSE 139 | $VERBOSE = nil 140 | list = List[1, 2, 1] 141 | assert_equal(2, list.rindex(1) {|e| e * 2 == 4 }) 142 | assert_equal(1, list.rindex(2) {|e| e * 2 == 4 }) 143 | assert_equal(nil, list.rindex(3) {|e| e * 2 == 4 }) 144 | $VERBOSE = verbose 145 | 146 | list = List[1, 2, 1] 147 | assert_equal(2, list.rindex {|e| e * 2 == 2 }) 148 | assert_equal(1, list.rindex {|e| e * 2 == 4 }) 149 | assert_equal(nil, list.rindex {|e| e * 2 == 6 }) 150 | 151 | enum = List[1, 2, 1].rindex 152 | assert_instance_of(Enumerator, enum) 153 | assert_equal(2, enum.each {|v|v == 1}) 154 | assert_equal(nil, enum.each {|v|v == 3}) 155 | end 156 | 157 | def test_foldr 158 | assert_equal(0, List[].foldr(0, &:+)) 159 | assert_equal(123, List[].foldr(123, &:+)) 160 | 161 | assert_equal(6, List[1, 2, 3].foldr(0, &:+)) 162 | # 1 - (2 - (3 - 10)) 163 | assert_equal(-8, List[1, 2, 3].foldr(10, &:-)) 164 | end 165 | 166 | def test_foldr1 167 | assert_raise(EmptyError) do 168 | List[].foldr1(&:+) 169 | end 170 | assert_equal(1, List[1].foldr1(&:+)) 171 | assert_equal(3, List[1, 2].foldr1(&:+)) 172 | assert_equal(6, List[1, 2, 3].foldr1(&:+)) 173 | assert_equal(2, List[1, 2, 3].foldr1(&:-)) 174 | end 175 | 176 | def test_foldl 177 | assert_equal(0, List[].foldl(0, &:+)) 178 | assert_equal(123, List[].foldl(123, &:+)) 179 | 180 | assert_equal(6, List[1, 2, 3].foldl(0, &:+)) 181 | # ((10 - 1) - 2) - 3 182 | assert_equal(4, List[1, 2, 3].foldl(10, &:-)) 183 | end 184 | 185 | def test_foldl1 186 | assert_raise(EmptyError) do 187 | List[].foldl1(&:+) 188 | end 189 | assert_equal(1, List[1].foldl1(&:+)) 190 | assert_equal(3, List[1, 2].foldl1(&:+)) 191 | assert_equal(6, List[1, 2, 3].foldl1(&:+)) 192 | assert_equal(-4, List[1, 2, 3].foldl1(&:-)) 193 | end 194 | 195 | def test_eq 196 | assert(List[] == List[]) 197 | assert(List[] != List[1]) 198 | assert(List[1] != List[]) 199 | assert(List[1] == List[1]) 200 | assert(List[1] != List[2]) 201 | assert(List[1] != [1]) 202 | assert(List[1] == List[1.0]) 203 | assert(List["foo"] == List["foo"]) 204 | assert(List["foo"] != List["bar"]) 205 | assert(List[1, 2, 3] == List[1, 2, 3]) 206 | assert(List[1, 2, 3] != List[1, 2]) 207 | assert(List[1, 2, 3] != List[1, 2, 3, 4]) 208 | assert(List[List[1, 2], List[3, 4]] == List[List[1, 2], List[3, 4]]) 209 | assert(List[List[1, 2], List[3, 4]] != List[List[1, 2], List[3]]) 210 | end 211 | 212 | def test_eql 213 | assert(List[].eql? List[]) 214 | assert(List[1].eql? List[1]) 215 | assert_same(List[1].eql?(List[1.0]), false) 216 | assert(List["foo"].eql? List["foo"]) 217 | assert(List[1, 2, 3].eql? List[1, 2, 3]) 218 | assert(List[List[1, 2], List[3, 4]].eql? List[List[1, 2], List[3, 4]]) 219 | end 220 | 221 | def test_hash_key 222 | assert_same({List[1] => true}[List[1.0]], nil) 223 | assert_same({List[1] => true}[List[1]], true) 224 | end 225 | 226 | def test_inspect 227 | assert_equal('List[]', List[].inspect) 228 | assert_equal('List[1]', List[1].inspect) 229 | assert_equal('List["foo"]', List["foo"].inspect) 230 | assert_equal('List[1, 2, 3]', List[1, 2, 3].inspect) 231 | assert_equal('List[List[1, 2], List[3, 4]]', 232 | List[List[1, 2], List[3, 4]].inspect) 233 | end 234 | 235 | def test_to_s 236 | assert_equal('List[]', List[].to_s) 237 | assert_equal('List[1]', List[1].to_s) 238 | assert_equal('List["foo"]', List["foo"].to_s) 239 | assert_equal('List[1, 2, 3]', List[1, 2, 3].to_s) 240 | assert_equal('List[List[1, 2], List[3, 4]]', 241 | List[List[1, 2], List[3, 4]].to_s) 242 | end 243 | 244 | def test_length 245 | assert_equal(0, List[].length) 246 | assert_equal(1, List[1].length) 247 | assert_equal(3, List[1, 2, 3].length) 248 | assert_equal(100, List[*(1..100)].length) 249 | end 250 | 251 | def test_cons 252 | assert_equal(List[1], List[].cons(1)) 253 | assert_equal(List[1, 2, 3], List[2, 3].cons(1)) 254 | xs = List[1, 2, 3] 255 | ys = xs.cons(0) 256 | assert_equal(List[1, 2, 3], xs) 257 | assert_equal(List[0, 1, 2, 3], ys) 258 | end 259 | 260 | def test_plus 261 | assert_equal(List[], List[] + List[]) 262 | assert_equal(List[1, 2, 3], List[] + List[1, 2, 3]) 263 | assert_equal(List[1, 2, 3], List[1, 2, 3] + List[]) 264 | assert_equal(List[1, 2, 3], List[1] + List[2, 3]) 265 | assert_equal(List[1, 2, 3], List[1, 2] + List[3]) 266 | end 267 | 268 | def test_flatten 269 | assert_equal(List[], List[].flatten) 270 | assert_equal(List[1], List[List[1]].flatten) 271 | assert_equal(List[List[1]], List[List[List[1]]].flatten) 272 | assert_equal(List[1, 2, 3], List[List[1, 2], List[3]].flatten) 273 | assert_equal(List[1, 2, 3], List[List[1], List[2], List[3]].flatten) 274 | assert_equal(List[1, 2, 3], List[List[1], 2, List[3]].flatten) 275 | assert_equal(List[1, [2], 3], List[List[1], [2], List[3]].flatten) 276 | obj = Object.new 277 | assert_equal(List[obj, 'str', obj], List[obj, 'str', obj].flatten) 278 | end 279 | 280 | def test_map 281 | assert_equal(List[], List[].map(&:to_s)) 282 | assert_equal(List["1", "2", "3"], List[1, 2, 3].map(&:to_s)) 283 | end 284 | 285 | def test_rev_map 286 | assert_equal(List[], List[].rev_map(&:to_s)) 287 | assert_equal(List["3", "2", "1"], List[1, 2, 3].rev_map(&:to_s)) 288 | end 289 | 290 | def test_flat_map 291 | assert_equal(List[], List[].flat_map {}) 292 | xs = List["foo", "bar"].flat_map {|s| List.from_enum(s.each_char)} 293 | assert_equal(List["f", "o", "o", "b", "a", "r"], xs) 294 | xs = List["FOO", "BAR"].flat_map {|s| s.upcase} 295 | end 296 | 297 | def test_find 298 | assert_equal(nil, List[].find(&:odd?)) 299 | assert_equal(1, List[1, 2, 3, 4, 5].find(&:odd?)) 300 | assert_equal(2, List[1, 2, 3, 4, 5].find(&:even?)) 301 | assert_equal(nil, List[1, 2, 3, 4, 5].find{|elm|elm < 0}) 302 | assert_raise(ArgumentError) do 303 | List[1, 2, 3, 4, 5].find(->{raise ArgumentError}){|elm|elm < 0} 304 | end 305 | 306 | enum = List[1, 2, 3, 4, 5].find 307 | assert_instance_of(Enumerator, enum) 308 | assert_equal(2, enum.each(&:even?)) 309 | end 310 | 311 | def test_filter 312 | assert_equal(List[], List[].filter(&:odd?)) 313 | assert_equal(List[1, 3, 5], List[1, 2, 3, 4, 5].filter(&:odd?)) 314 | assert_equal(List[2, 4], List[1, 2, 3, 4, 5].filter(&:even?)) 315 | end 316 | 317 | def test_aref 318 | assert_equal(nil, List[][0]) 319 | assert_equal(1, List[1, 2, 3][0]) 320 | assert_equal(nil, List[1, 2, 3][-1]) 321 | assert_equal(2, List[1, 2, 3][1]) 322 | assert_equal(3, List[1, 2, 3][2]) 323 | assert_equal(nil, List[1, 2, 3][3]) 324 | assert_equal(2, List[1, 2, 3][1.1]) 325 | assert_raise TypeError do 326 | List[1, 2, 3]['1'] 327 | end 328 | end 329 | 330 | def test_at 331 | assert_equal(nil, List[].at(0)) 332 | assert_equal(1, List[1, 2, 3].at(0)) 333 | assert_equal(nil, List[1, 2, 3].at(-1)) 334 | assert_equal(2, List[1, 2, 3].at(1)) 335 | assert_equal(3, List[1, 2, 3].at(2)) 336 | assert_equal(nil, List[1, 2, 3].at(3)) 337 | assert_equal(2, List[1, 2, 3].at(1.1)) 338 | assert_raise TypeError do 339 | List[1, 2, 3].at('1') 340 | end 341 | end 342 | 343 | def test_fetch 344 | assert_raise ArgumentError do 345 | List[].fetch 346 | end 347 | 348 | assert_raise IndexError do 349 | List[].fetch(0) 350 | end 351 | 352 | assert_equal(1, List[1, 2, 3].fetch(0)) 353 | 354 | assert_raise IndexError do 355 | List[1, 2, 3].fetch(-1) 356 | end 357 | 358 | assert_equal(2, List[1, 2, 3].fetch(1)) 359 | assert_equal(3, List[1, 2, 3].fetch(2)) 360 | 361 | assert_raise IndexError do 362 | List[1, 2, 3].fetch(3) 363 | end 364 | 365 | assert_equal(2, List[1, 2, 3].fetch(1.1)) 366 | 367 | assert_raise TypeError do 368 | List[1, 2, 3].fetch('1') 369 | end 370 | 371 | assert_equal(9, List[].fetch(0, 9)) 372 | assert_equal(2, List[1, 2, 3].fetch(1, 9)) 373 | assert_equal(9, List[1, 2, 3].fetch(4, 9)) 374 | 375 | assert_equal(5, List[].fetch(0) {|n| n + 5 }) 376 | assert_equal(2, List[1, 2, 3].fetch(1) {|n| n + 5 }) 377 | assert_equal(8, List[1, 2, 3].fetch(4) {|n| n * 2 }) 378 | 379 | verbose = $VERBOSE 380 | $VERBOSE = nil 381 | assert_equal(5, List[].fetch(0, 9) {|n| n + 5 }) 382 | assert_equal(2, List[1, 2, 3].fetch(1, 9) {|n| n + 5 }) 383 | assert_equal(8, List[1, 2, 3].fetch(4, 9) {|n| n * 2 }) 384 | $VERBOSE = verbose 385 | end 386 | 387 | def test_take 388 | assert_equal(List[], List[].take(1)) 389 | assert_equal(List[], List[1, 2, 3].take(0)) 390 | assert_equal(List[], List[1, 2, 3].take(-1)) 391 | assert_equal(List[1], List[1, 2, 3].take(1)) 392 | assert_equal(List[1, 2], List[1, 2, 3].take(2)) 393 | assert_equal(List[1, 2, 3], List[1, 2, 3].take(3)) 394 | assert_equal(List[1, 2, 3], List[1, 2, 3].take(4)) 395 | end 396 | 397 | def test_take_while 398 | assert_equal(List[], List[].take_while { true }) 399 | assert_equal(List[], List[1, 2, 3].take_while { |x| x < 1 }) 400 | assert_equal(List[1], List[1, 2, 3].take_while { |x| x < 2 }) 401 | assert_equal(List[1, 2], List[1, 2, 3].take_while { |x| x < 3 }) 402 | assert_equal(List[1, 2, 3], List[1, 2, 3].take_while { |x| x < 4 }) 403 | end 404 | 405 | def test_drop 406 | assert_equal(List[], List[].drop(1)) 407 | assert_equal(List[1, 2, 3], List[1, 2, 3].drop(0)) 408 | assert_equal(List[1, 2, 3], List[1, 2, 3].drop(-1)) 409 | assert_equal(List[2, 3], List[1, 2, 3].drop(1)) 410 | assert_equal(List[3], List[1, 2, 3].drop(2)) 411 | assert_equal(List[], List[1, 2, 3].drop(3)) 412 | assert_equal(List[], List[1, 2, 3].drop(4)) 413 | end 414 | 415 | def test_drop_while 416 | assert_equal(List[], List[].drop_while { false }) 417 | assert_equal(List[1, 2, 3], List[1, 2, 3].drop_while { |x| x < 1 }) 418 | assert_equal(List[2, 3], List[1, 2, 3].drop_while { |x| x < 2 }) 419 | assert_equal(List[3], List[1, 2, 3].drop_while { |x| x < 3 }) 420 | assert_equal(List[], List[1, 2, 3].drop_while { |x| x < 4 }) 421 | end 422 | 423 | def test_reverse 424 | assert_equal(List[], List[].reverse) 425 | assert_equal(List[1], List[1].reverse) 426 | assert_equal(List[2, 1], List[1, 2].reverse) 427 | assert_equal(List[3, 2, 1], List[1, 2, 3].reverse) 428 | end 429 | 430 | def test_intersperse 431 | assert_equal(List[], List[].intersperse(0)) 432 | assert_equal(List[1], List[1].intersperse(0)) 433 | assert_equal(List[1, 0, 2], List[1, 2].intersperse(0)) 434 | assert_equal(List[1, 0, 2, 0, 3], List[1, 2, 3].intersperse(0)) 435 | end 436 | 437 | def test_intercalate 438 | assert_equal(List[], List[].intercalate(List[0])) 439 | assert_equal(List[1], List[List[1]].intercalate(List[0])) 440 | xs = List[List[1, 2], List[3, 4], List[5, 6]].intercalate(List[0]) 441 | assert_equal(List[1, 2, 0, 3, 4, 0, 5, 6], xs) 442 | end 443 | 444 | def test_transpose 445 | assert_equal(List[], List[].transpose) 446 | assert_equal(List[], List[List[]].transpose) 447 | assert_equal(List[List[1], List[2]], List[List[1, 2]].transpose) 448 | assert_equal(List[List[1, 4], List[2, 5], List[3, 6]], 449 | List[List[1, 2, 3], List[4, 5, 6]].transpose) 450 | assert_equal(List[List[1, 4], List[2, 5], List[3]], 451 | List[List[1, 2, 3], List[4, 5]].transpose) 452 | end 453 | 454 | def test_subsequences 455 | assert_equal(List[List[]], List[].subsequences) 456 | assert_equal(List[List[], List[1], List[2], List[1, 2], 457 | List[3], List[1, 3], List[2, 3], List[1, 2, 3]], 458 | List[1, 2, 3].subsequences) 459 | end 460 | 461 | def test_sum 462 | assert_equal(0, List[].sum) 463 | assert_equal(1, List[1].sum) 464 | assert_equal(10, List[1, 2, 3, 4].sum) 465 | end 466 | 467 | def test_product 468 | assert_equal(1, List[].product) 469 | assert_equal(1, List[1].product) 470 | assert_equal(24, List[1, 2, 3, 4].product) 471 | end 472 | 473 | def test_unfoldr 474 | xs = List.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] } 475 | assert_equal(List[3, 2, 1], xs) 476 | end 477 | 478 | def test_s_unfoldr 479 | xs = List.unfoldr("foo,bar,baz") { |x| 480 | if x.empty? 481 | nil 482 | else 483 | y = x.slice(/([^,]*),?/, 1) 484 | [y, $'] 485 | end 486 | } 487 | assert_equal(List["foo", "bar", "baz"], xs) 488 | end 489 | 490 | def test_zip 491 | xs = List[0, 1, 2] 492 | ys = List[0, 2, 4] 493 | zs = List[0, 3, 6] 494 | assert_equal(List[[0, 0, 0], [1, 2, 3], [2, 4, 6]], 495 | xs.zip(ys, zs)) 496 | end 497 | 498 | def test_zip_with 499 | xs = List[0, 1, 2] 500 | ys = List[0, 2, 4] 501 | zs = List[0, 3, 6] 502 | l = xs.zip_with(ys, zs) { |x, y, z| 503 | x + y + z 504 | } 505 | assert_equal(List[0, 6, 12], l) 506 | end 507 | end 508 | end 509 | -------------------------------------------------------------------------------- /test/immutable/test_map.rb: -------------------------------------------------------------------------------- 1 | require_relative "../test_helper" 2 | 3 | with_tailcall_optimization { 4 | require_relative "../../lib/immutable/list" 5 | require_relative "../../lib/immutable/map" 6 | } 7 | 8 | module Immutable 9 | class TestMap < Test::Unit::TestCase 10 | def test_empty? 11 | assert(Map[].empty?) 12 | assert(!Map[a: 1].empty?) 13 | assert(!Map[a: 1, b: 2].empty?) 14 | end 15 | 16 | def test_inspect 17 | assert_equal("Map[]", Map.empty.inspect) 18 | assert_equal("Map[:a => 1, :b => 2]", Map[a: 1, b: 2].inspect) 19 | end 20 | 21 | def test_to_s 22 | assert_equal("Map[]", Map.empty.to_s) 23 | assert_equal("Map[:a => 1, :b => 2]", Map[a: 1, b: 2].to_s) 24 | end 25 | 26 | def test_insert 27 | 5.times do 28 | map = (1..100).to_a.shuffle.inject(Map.empty) { |m, k| 29 | m.insert(k, k.to_s) 30 | } 31 | for i in 1..100 32 | assert_equal(i.to_s, map[i]) 33 | end 34 | end 35 | end 36 | 37 | def test_delete 38 | 5.times do 39 | keys = (1..100).to_a.shuffle 40 | map = keys.inject(Map.empty) { |m, k| 41 | m.insert(k, k.to_s) 42 | } 43 | keys.shuffle! 44 | deleted_keys = keys.shift(20) 45 | map2 = deleted_keys.inject(map) { |m, k| 46 | m.delete(k) 47 | } 48 | for i in 1..100 49 | assert_equal(i.to_s, map[i]) 50 | end 51 | keys.each do |k| 52 | assert_equal(k.to_s, map2[k]) 53 | end 54 | deleted_keys.each do |k| 55 | assert_equal(nil, map2[k]) 56 | end 57 | end 58 | end 59 | 60 | def test_eq 61 | assert_same(true, Map[] == Map[]) 62 | assert_same(true, Map[a: 1, c: 3, b: 2] == Map[c: 3, b: 2, a: 1]) 63 | assert_same(false, Map[] == {}) 64 | assert_same(false, Map[a: 1, c: 3, b: 2] == Map[c: 3, b: 2, a: 1, A: 1]) 65 | assert_same(false, Map[a: 1, c: 3, b: 2] == Map[c: 3, b: 2, a: '1']) 66 | assert_same(false, Map[a: 1, c: 3, b: 2] == Map[c: 3, b: 2]) 67 | end 68 | 69 | def test_case_equal 70 | assert_same(true, Map[] === Map[]) 71 | assert_same(true, Map[a: 1, c: 3, b: 2] === Map[c: 3, b: 2, a: 1]) 72 | assert_same(false, Map[] === {}) 73 | assert_same(false, Map[a: 1, c: 3, b: 2] === Map[c: 3, b: 2, a: 1, A: 1]) 74 | assert_same(false, Map[a: 1, c: 3, b: 2] === Map[c: 3, b: 2, a: '1']) 75 | assert_same(false, Map[a: 1, c: 3, b: 2] === Map[c: 3, b: 2]) 76 | end 77 | 78 | def test_eql 79 | assert_same(true, Map[] == Map[]) 80 | assert_same(true, Map[a: 1, c: 3, b: 2].eql?(Map[c: 3, b: 2, a: 1])) 81 | assert_same(false, Map[].eql?({})) 82 | assert_same(false, Map[a: 1, c: 3, b: 2].eql?(Map[c: 3, b: 2, a: 1, A: 1])) 83 | assert_same(false, Map[a: 1, c: 3, b: 2].eql?(Map[c: 3, b: 2, a: '1'])) 84 | assert_same(false, Map[a: 1, c: 3, b: 2].eql?(Map[c: 3, b: 2])) 85 | end 86 | 87 | def test_hash_key 88 | map1 = Map[1 => 1, 2 => 3] 89 | map2 = Map[1 => 1, 2 => 3] 90 | map3 = Map[1 => 1, 2.0 => 3] 91 | hash = {map1 => true} 92 | assert_same(true, hash.has_key?(map1)) 93 | assert_same(true, hash.has_key?(map2)) 94 | assert_same(false, hash.has_key?(map3)) 95 | end 96 | 97 | def test_each 98 | a = [] 99 | map = Map[] 100 | ret = map.each { |x| a << x } 101 | assert_equal([], a) 102 | assert_same(ret, map) 103 | enum = map.each 104 | assert_instance_of(Enumerator, enum) 105 | 106 | a = [] 107 | map = Map[a: 1, c: 3, b: 2] 108 | ret = map.each { |x| a << x } 109 | assert_equal([[:a, 1], [:b, 2], [:c, 3]], a) 110 | assert_same(ret, map) 111 | enum = map.each 112 | assert_instance_of(Enumerator, enum) 113 | assert_equal([:a, 1], enum.next) 114 | assert_equal([:b, 2], enum.next) 115 | assert_equal([:c, 3], enum.next) 116 | assert_raise(StopIteration) do 117 | enum.next 118 | end 119 | end 120 | 121 | def test_each_pair 122 | a = [] 123 | map = Map[] 124 | ret = map.each_pair { |x| a << x } 125 | assert_equal([], a) 126 | assert_same(ret, map) 127 | enum = map.each_pair 128 | assert_instance_of(Enumerator, enum) 129 | 130 | a = [] 131 | map = Map[a: 1, c: 3, b: 2] 132 | ret = map.each_pair { |x| a << x } 133 | assert_equal([[:a, 1], [:b, 2], [:c, 3]], a) 134 | assert_same(ret, map) 135 | enum = map.each_pair 136 | assert_instance_of(Enumerator, enum) 137 | assert_equal([:a, 1], enum.next) 138 | assert_equal([:b, 2], enum.next) 139 | assert_equal([:c, 3], enum.next) 140 | assert_raise(StopIteration) do 141 | enum.next 142 | end 143 | end 144 | 145 | def test_to_h 146 | assert_equal({}, Map[].to_h) 147 | assert_equal({a: 1, c: 3, b: 2}, Map[a: 1, c: 3, b: 2].to_h) 148 | end 149 | 150 | def test_foldr 151 | xs = Map[].foldr(List[]) { |v, ys| Cons[v, ys] } 152 | assert_equal(List[], xs) 153 | 154 | xs = Map[a: 1, c: 3, b: 2].foldr(List[]) { |v, ys| Cons[v, ys] } 155 | assert_equal(List[1, 2, 3], xs) 156 | end 157 | 158 | def test_foldl 159 | xs = Map[].foldl(List[]) { |ys, v| Cons[v, ys] } 160 | assert_equal(List[], xs) 161 | 162 | xs = Map[a: 1, c: 3, b: 2].foldl(List[]) { |ys, v| Cons[v, ys] } 163 | assert_equal(List[3, 2, 1], xs) 164 | end 165 | 166 | def test_foldl_with_key 167 | xs = Map[].foldl_with_key(List[]) { |ys, k, v| Cons[[k, v], ys] } 168 | assert_equal(List[], xs) 169 | 170 | xs = Map[a: 1, c: 3, b: 2].foldl_with_key(List[]) { |ys, k, v| 171 | Cons[[k, v], ys] 172 | } 173 | assert_equal(List[[:c, 3], [:b, 2], [:a, 1]], xs) 174 | end 175 | 176 | def test_foldr_with_key 177 | xs = Map[].foldr_with_key(List[]) { |k, v, ys| Cons[[k, v], ys] } 178 | assert_equal(List[], xs) 179 | 180 | xs = Map[a: 1, c: 3, b: 2].foldr_with_key(List[]) { |k, v, ys| 181 | Cons[[k, v], ys] 182 | } 183 | assert_equal(List[[:a, 1], [:b, 2], [:c, 3]], xs) 184 | end 185 | 186 | def test_map 187 | xs = Map[].map { |v| v.to_s } 188 | assert_equal(List[], xs) 189 | 190 | xs = Map[a: 1, c: 3, b: 2].map { |v| v.to_s } 191 | assert_equal(List["1", "2", "3"], xs) 192 | end 193 | 194 | def test_map_with_key 195 | xs = Map[].map_with_key { |k, v| [k, v].join(":") } 196 | assert_equal(List[], xs) 197 | 198 | xs = Map[a: 1, c: 3, b: 2].map_with_key { |k, v| [k, v].join(":") } 199 | assert_equal(List["a:1", "b:2", "c:3"], xs) 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /test/immutable/test_promise.rb: -------------------------------------------------------------------------------- 1 | require_relative "../test_helper" 2 | 3 | with_tailcall_optimization { 4 | require_relative "../../lib/immutable/promise" 5 | require_relative "../../lib/immutable/list" 6 | } 7 | 8 | module Immutable 9 | class TestPromise < Test::Unit::TestCase 10 | COUNT = 10000 11 | 12 | def test_eager 13 | assert_equal(123, Promise.eager(123).force) 14 | end 15 | 16 | def test_delay 17 | assert_equal(123, Promise.delay { 123 }.force) 18 | count = 0 19 | x = Promise.delay { count += 1; 123 } 20 | assert_equal(0, count) 21 | assert_equal(123, x.force) 22 | assert_equal(1, count) 23 | end 24 | 25 | def test_memoization1 26 | count = 0 27 | s = Promise.delay { count += 1; 1 } 28 | assert_equal(1, s.force) 29 | assert_equal(1, s.force) 30 | assert_equal(1, count) 31 | end 32 | 33 | def test_memoization2 34 | count = 0 35 | s = Promise.delay { count += 1; 2 } 36 | assert_equal(4, s.force + s.force) 37 | assert_equal(1, count) 38 | end 39 | 40 | def test_memoization3 41 | count = 0 42 | r = Promise.delay { count += 1; 1 } 43 | s = Promise.lazy { r } 44 | t = Promise.lazy { s } 45 | assert_equal(1, t.force) 46 | assert_equal(1, s.force) 47 | assert_equal(1, count) 48 | end 49 | 50 | def test_memoization4 51 | stream_drop = ->(s, index) { 52 | Promise.lazy { 53 | if index.zero? 54 | s 55 | else 56 | stream_drop[s.force.tail, index - 1] 57 | end 58 | } 59 | } 60 | count = 0 61 | ones = -> { 62 | Promise.delay { 63 | count += 1 64 | Cons[1, ones[]] 65 | } 66 | } 67 | s = ones[] 68 | assert_equal(1, stream_drop[s, 4].force.head) 69 | assert_equal(1, stream_drop[s, 4].force.head) 70 | assert_equal(5, count) 71 | end 72 | 73 | def test_reentrancy1 74 | count = 0 75 | x = 5 76 | p = Promise.delay { 77 | count += 1 78 | if count > x 79 | count 80 | else 81 | p.force 82 | end 83 | } 84 | assert_equal(6, p.force) 85 | x = 10 86 | assert_equal(6, p.force) 87 | end 88 | 89 | def test_reentrancy2 90 | first = true 91 | f = Promise.delay { 92 | if first 93 | first = false 94 | f.force 95 | else 96 | :second 97 | end 98 | } 99 | assert_equal(:second, f.force) 100 | end 101 | 102 | def test_reentrancy3 103 | q = -> { 104 | count = 5 105 | get_count = -> { count } 106 | p = Promise.delay { 107 | if count <= 0 108 | count 109 | else 110 | count -= 1 111 | p.force 112 | count += 2 113 | count 114 | end 115 | } 116 | [get_count, p] 117 | }[] 118 | get_count, p = q 119 | assert_equal(5, get_count[]) 120 | assert_equal(0, p.force) 121 | assert_equal(10, get_count[]) 122 | end 123 | 124 | def nloop(n) 125 | Promise.lazy { n <= 0 ? Promise.eager(0) : nloop(n - 1) } 126 | end 127 | 128 | def test_leak1 129 | assert_equal(0, nloop(COUNT).force) 130 | end 131 | 132 | def test_leak2 133 | s = nloop(COUNT) 134 | assert_equal(0, s.force) 135 | end 136 | 137 | def from(n) 138 | Promise.delay { 139 | Cons[n, from(n + 1)] 140 | } 141 | end 142 | 143 | def traverse(s, n) 144 | Promise.lazy { 145 | if n <= 0 146 | s 147 | else 148 | traverse(s.force.tail, n - 1) 149 | end 150 | } 151 | end 152 | 153 | def test_leak3 154 | assert_equal(COUNT, traverse(from(0), COUNT).force.head) 155 | end 156 | 157 | def test_leak4 158 | s = traverse(from(0), COUNT) 159 | assert_equal(COUNT, s.force.head) 160 | end 161 | 162 | def stream_filter(s, &block) 163 | Promise.lazy { 164 | xs = s.force 165 | if xs.empty? 166 | Promise.delay { List[] } 167 | else 168 | if yield(xs.head) 169 | Promise.delay { Cons[xs.head, stream_filter(xs.tail, &block)] } 170 | else 171 | stream_filter(xs.tail, &block) 172 | end 173 | end 174 | } 175 | end 176 | 177 | def test_leak5 178 | stream_filter(from(0)) { |n| n == COUNT }.force 179 | end 180 | 181 | def stream_ref(s, index) 182 | Promise.lazy { 183 | xs = s.force 184 | if xs.empty? 185 | :error 186 | else 187 | if index.zero? 188 | Promise.delay { xs.head } 189 | else 190 | stream_ref(xs.tail, index - 1) 191 | end 192 | end 193 | } 194 | end 195 | 196 | def test_leak6 197 | assert_equal(0, stream_ref(stream_filter(from(0), &:zero?), 0).force) 198 | s = stream_ref(from(0), COUNT) 199 | assert_equal(COUNT, s.force) 200 | end 201 | end 202 | end 203 | -------------------------------------------------------------------------------- /test/immutable/test_queue.rb: -------------------------------------------------------------------------------- 1 | require_relative "../test_helper" 2 | 3 | with_tailcall_optimization { 4 | require_relative "../../lib/immutable/queue" 5 | } 6 | 7 | module Immutable 8 | class TestQueue < Test::Unit::TestCase 9 | def test_head 10 | assert_raise(EmptyError) do 11 | Queue[].head 12 | end 13 | assert_equal(1, Queue[1].head) 14 | assert_equal(1, Queue[1, 2, 3].head) 15 | end 16 | 17 | def test_tail 18 | assert_raise(EmptyError) do 19 | Queue[].tail 20 | end 21 | assert(Queue[1].tail.empty?) 22 | assert_equal(2, Queue[1, 2].tail.head) 23 | assert_equal(2, Queue[1, 2, 3].tail.head) 24 | assert_equal(3, Queue[1, 2, 3].tail.tail.head) 25 | end 26 | 27 | def test_snoc 28 | q1 = Queue.empty.snoc(1) 29 | assert_equal(1, q1.head) 30 | assert(q1.tail.empty?) 31 | q2 = q1.snoc(2) 32 | assert_equal(1, q2.head) 33 | assert_equal(2, q2.tail.head) 34 | assert(q2.tail.tail.empty?) 35 | assert_equal(1, q1.head) 36 | assert(q1.tail.empty?) 37 | 38 | a = (1..1000).to_a.shuffle 39 | q = a.inject(Queue.empty, :snoc) 40 | assert_equal(a, q.to_a) 41 | end 42 | 43 | def test_invariant 44 | a = (1..100).to_a.shuffle 45 | queue = a.inject(Queue.empty) { |q, i| 46 | assert_queue_invariant(q) 47 | q2 = q.snoc(i) 48 | if rand(3) == 0 49 | q2.tail 50 | else 51 | q2 52 | end 53 | } 54 | assert_queue_invariant(queue) 55 | until queue.empty? 56 | queue = queue.tail 57 | assert_queue_invariant(queue) 58 | end 59 | end 60 | 61 | private 62 | 63 | def assert_queue_invariant(d) 64 | front = d.instance_variable_get(:@front) 65 | rear = d.instance_variable_get(:@rear) 66 | schedule = d.instance_variable_get(:@schedule) 67 | assert_equal(schedule.length, front.length - rear.length) 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/immutable/test_stream.rb: -------------------------------------------------------------------------------- 1 | require_relative "../test_helper" 2 | 3 | with_tailcall_optimization { 4 | require_relative "../../lib/immutable/stream" 5 | } 6 | 7 | module Immutable 8 | class TestStream < Test::Unit::TestCase 9 | def test_s_from_enum 10 | assert_equal([], Stream.from_enum([]).to_a) 11 | assert_equal([1, 2, 3], Stream.from_enum([1, 2, 3]).to_a) 12 | assert_equal(["a", "b", "c"], 13 | Stream.from_enum("abc".chars).to_a) 14 | end 15 | 16 | def test_s_from_enumerator 17 | e = [1, 2, 3, 4, 5].each 18 | s1 = Stream.from_enumerator(e) 19 | s2 = Stream.from_enumerator(e) 20 | assert_equal(1, s1.head) 21 | assert_equal(2, s2.head) 22 | assert_equal(1, s1.head) 23 | assert_equal([1, 3], s1.take(2).to_a) 24 | assert_equal(4, s2.drop(1).head) 25 | assert_equal([1, 3, 5], s1.to_a) 26 | assert_equal([2, 4], s2.to_a) 27 | end 28 | 29 | def test_s_from 30 | s = Stream.from(1) 31 | assert_equal(Stream[1], s.take(1)) 32 | assert_equal(Stream[1, 2, 3], s.take(3)) 33 | assert_equal(Stream[1, 3, 5], Stream.from(1, 2).take(3)) 34 | end 35 | 36 | def test_cons 37 | assert_equal(Stream[1], Stream.empty.cons(1)) 38 | assert_equal(Stream[1, 2], Stream.empty.cons(2).cons(1)) 39 | assert_equal(Stream[1], Stream.empty.cons { 1 }) 40 | assert_equal(Stream[1, 2], Stream.empty.cons { 2 }.cons { 1 }) 41 | end 42 | 43 | def test_head 44 | assert_raise(EmptyError) do 45 | Stream.empty.head 46 | end 47 | assert_equal(1, Stream[1].head) 48 | assert_equal(1, Stream[1, 2, 3].head) 49 | end 50 | 51 | def test_tail 52 | assert_raise(EmptyError) do 53 | Stream.empty.tail 54 | end 55 | assert(Stream[1].tail.empty?) 56 | assert_equal([2, 3], Stream[1, 2, 3].tail.to_a) 57 | end 58 | 59 | def test_last 60 | assert_raise(EmptyError) do 61 | Stream.empty.last 62 | end 63 | assert_equal(1, Stream[1].last) 64 | assert_equal(3, Stream[1, 2, 3].last) 65 | end 66 | 67 | def test_init 68 | assert_raise(EmptyError) do 69 | Stream.empty.init.force 70 | end 71 | assert_equal(Stream[], Stream[1].init) 72 | assert_equal(Stream[1], Stream[1, 2].init) 73 | assert_equal(Stream[1, 2], Stream[1, 2, 3].init) 74 | end 75 | 76 | def test_aref 77 | s = Stream[1, 2, 3, 4, 5] 78 | 2.times do 79 | assert_equal(nil, s[-1]) 80 | assert_equal(1, s[0]) 81 | assert_equal(5, s[4]) 82 | assert_equal(nil, s[5]) 83 | assert_equal(4, s[3]) 84 | end 85 | end 86 | 87 | def test_empty? 88 | assert(Stream[].empty?) 89 | assert(!Stream[1].empty?) 90 | assert(!Stream[1, 2, 3].empty?) 91 | end 92 | 93 | def test_each 94 | a = [] 95 | Stream[].each { |x| a << x } 96 | assert_equal([], a) 97 | 98 | a = [] 99 | Stream[1, 2, 3].each { |x| a << x } 100 | assert_equal([1, 2, 3], a) 101 | end 102 | 103 | def test_foldr 104 | assert_equal(0, Stream[].foldr(0, &:+)) 105 | assert_equal(123, Stream[].foldr(123, &:+)) 106 | 107 | assert_equal(6, Stream[1, 2, 3].foldr(0, &:+)) 108 | # 1 - (2 - (3 - 10)) 109 | assert_equal(-8, Stream[1, 2, 3].foldr(10, &:-)) 110 | end 111 | 112 | def test_foldr1 113 | assert_raise(EmptyError) do 114 | Stream[].foldr1(&:+) 115 | end 116 | assert_equal(1, Stream[1].foldr1(&:+)) 117 | assert_equal(3, Stream[1, 2].foldr1(&:+)) 118 | assert_equal(6, Stream[1, 2, 3].foldr1(&:+)) 119 | assert_equal(2, Stream[1, 2, 3].foldr1(&:-)) 120 | end 121 | 122 | def test_foldl 123 | assert_equal(0, Stream[].foldl(0, &:+)) 124 | assert_equal(123, Stream[].foldl(123, &:+)) 125 | 126 | assert_equal(6, Stream[1, 2, 3].foldl(0, &:+)) 127 | # ((10 - 1) - 2) - 3 128 | assert_equal(4, Stream[1, 2, 3].foldl(10, &:-)) 129 | end 130 | 131 | def test_foldl1 132 | assert_raise(EmptyError) do 133 | Stream[].foldl1(&:+) 134 | end 135 | assert_equal(1, Stream[1].foldl1(&:+)) 136 | assert_equal(3, Stream[1, 2].foldl1(&:+)) 137 | assert_equal(6, Stream[1, 2, 3].foldl1(&:+)) 138 | assert_equal(-4, Stream[1, 2, 3].foldl1(&:-)) 139 | end 140 | 141 | def test_eq 142 | assert(Stream[] == Stream[]) 143 | assert(Stream[] != Stream[1]) 144 | assert(Stream[1] != Stream[]) 145 | assert(Stream[1] == Stream[1]) 146 | assert(Stream[1] != Stream[2]) 147 | assert(Stream["foo"] == Stream["foo"]) 148 | assert(Stream["foo"] != Stream["bar"]) 149 | assert(Stream[1, 2, 3] == Stream[1, 2, 3]) 150 | assert(Stream[1, 2, 3] != Stream[1, 2]) 151 | assert(Stream[1, 2, 3] != Stream[1, 2, 3, 4]) 152 | assert(Stream[Stream[1, 2], Stream[3, 4]] == 153 | Stream[Stream[1, 2], Stream[3, 4]]) 154 | assert(Stream[Stream[1, 2], Stream[3, 4]] != 155 | Stream[Stream[1, 2], Stream[3]]) 156 | assert(Stream[] != Stream.from(1)) 157 | assert(Stream.from(1) != Stream[]) 158 | assert(Stream[1] != Stream.from(1)) 159 | assert(Stream.from(1) != Stream[1]) 160 | end 161 | 162 | def test_inspect 163 | s = Stream[] 164 | assert_equal('Stream[...]', s.inspect) 165 | assert_equal(nil, s[0]) 166 | assert_equal('Stream[]', s.inspect) 167 | s = Stream[1] 168 | assert_equal('Stream[...]', s.inspect) 169 | assert_equal(1, s[0]) 170 | assert_equal('Stream[1, ...]', s.inspect) 171 | assert_equal(nil, s[1]) 172 | assert_equal('Stream[1]', s.inspect) 173 | s = Stream["foo"] 174 | assert_equal("foo", s[0]) 175 | assert_equal('Stream["foo", ...]', s.inspect) 176 | assert_equal(nil, s[1]) 177 | assert_equal('Stream["foo"]', s.inspect) 178 | s = Stream[1, 2, 3] 179 | assert_equal('Stream[...]', s.inspect) 180 | assert_equal(1, s[0]) 181 | assert_equal('Stream[1, ...]', s.inspect) 182 | assert_equal(2, s[1]) 183 | assert_equal('Stream[1, 2, ...]', s.inspect) 184 | assert_equal(3, s[2]) 185 | assert_equal('Stream[1, 2, 3, ...]', s.inspect) 186 | assert_equal(nil, s[3]) 187 | assert_equal('Stream[1, 2, 3]', s.inspect) 188 | s = Stream[1, 2, 3] 189 | assert_equal(2, s[1]) 190 | assert_equal('Stream[?, 2, ...]', s.inspect) 191 | s = Stream[Stream[1, 2], Stream[3, 4]] 192 | assert_equal(Stream[1, 2], s[0]) 193 | assert_equal(Stream[3, 4], s[1]) 194 | assert_equal(nil, s[2]) 195 | assert_equal('Stream[Stream[1, 2], Stream[3, 4]]', 196 | s.inspect) 197 | end 198 | 199 | def test_to_s 200 | s = Stream[] 201 | assert_equal('Stream[...]', s.to_s) 202 | assert_equal(nil, s[0]) 203 | assert_equal('Stream[]', s.to_s) 204 | s = Stream[1] 205 | assert_equal('Stream[...]', s.to_s) 206 | assert_equal(1, s[0]) 207 | assert_equal('Stream[1, ...]', s.to_s) 208 | assert_equal(nil, s[1]) 209 | assert_equal('Stream[1]', s.to_s) 210 | s = Stream["foo"] 211 | assert_equal("foo", s[0]) 212 | assert_equal('Stream["foo", ...]', s.to_s) 213 | assert_equal(nil, s[1]) 214 | assert_equal('Stream["foo"]', s.to_s) 215 | s = Stream[1, 2, 3] 216 | assert_equal('Stream[...]', s.to_s) 217 | assert_equal(1, s[0]) 218 | assert_equal('Stream[1, ...]', s.to_s) 219 | assert_equal(2, s[1]) 220 | assert_equal('Stream[1, 2, ...]', s.to_s) 221 | assert_equal(3, s[2]) 222 | assert_equal('Stream[1, 2, 3, ...]', s.to_s) 223 | assert_equal(nil, s[3]) 224 | assert_equal('Stream[1, 2, 3]', s.to_s) 225 | s = Stream[1, 2, 3] 226 | assert_equal(2, s[1]) 227 | assert_equal('Stream[?, 2, ...]', s.to_s) 228 | s = Stream[Stream[1, 2], Stream[3, 4]] 229 | assert_equal(Stream[1, 2], s[0]) 230 | assert_equal(Stream[3, 4], s[1]) 231 | assert_equal(nil, s[2]) 232 | assert_equal('Stream[Stream[1, 2], Stream[3, 4]]', 233 | s.to_s) 234 | end 235 | 236 | def test_length 237 | assert_equal(0, Stream[].length) 238 | assert_equal(1, Stream[1].length) 239 | assert_equal(3, Stream[1, 2, 3].length) 240 | assert_equal(100, Stream.from(1).take(100).length) 241 | end 242 | 243 | def test_plus 244 | assert_equal(Stream[], Stream[] + Stream[]) 245 | assert_equal(Stream[1, 2, 3], Stream[] + Stream[1, 2, 3]) 246 | assert_equal(Stream[1, 2, 3], Stream[1, 2, 3] + Stream[]) 247 | assert_equal(Stream[1, 2, 3], Stream[1] + Stream[2, 3]) 248 | assert_equal(Stream[1, 2, 3], Stream[1, 2] + Stream[3]) 249 | end 250 | 251 | def test_flatten 252 | assert_equal(Stream[], Stream[].flatten) 253 | assert_equal(Stream[1], Stream[Stream[1]].flatten) 254 | assert_equal(Stream[Stream[1]], 255 | Stream[Stream[Stream[1]]].flatten) 256 | assert_equal(Stream[1, 2, 3], Stream[Stream[1, 2], Stream[3]].flatten) 257 | assert_equal(Stream[1, 2, 3], 258 | Stream[Stream[1], Stream[2], Stream[3]].flatten) 259 | end 260 | 261 | def test_map 262 | assert_equal(Stream[], Stream[].map(&:to_s)) 263 | assert_equal(Stream["1", "2", "3"], Stream[1, 2, 3].map(&:to_s)) 264 | end 265 | 266 | def test_reverse 267 | assert_equal(Stream[], Stream[].reverse) 268 | assert_equal(Stream[1], Stream[1].reverse) 269 | assert_equal(Stream[2, 1], Stream[1, 2].reverse) 270 | assert_equal(Stream[3, 2, 1], Stream[1, 2, 3].reverse) 271 | end 272 | 273 | def test_intersperse 274 | assert_equal(Stream[], Stream[].intersperse(0)) 275 | assert_equal(Stream[1], Stream[1].intersperse(0)) 276 | assert_equal(Stream[1, 0, 2], Stream[1, 2].intersperse(0)) 277 | assert_equal(Stream[1, 0, 2, 0, 3], Stream[1, 2, 3].intersperse(0)) 278 | assert_equal(Stream[1, 0, 2, 0, 3], 279 | Stream.from(1).intersperse(0).take(5)) 280 | end 281 | 282 | def test_intercalate 283 | assert_equal(Stream[], Stream[].intercalate(Stream[0])) 284 | assert_equal(Stream[1], Stream[Stream[1]].intercalate(Stream[0])) 285 | xs = Stream[Stream[1, 2], Stream[3, 4], Stream[5, 6]]. 286 | intercalate(Stream[0]) 287 | assert_equal(Stream[1, 2, 0, 3, 4, 0, 5, 6], xs) 288 | xs = Stream.from(1, 2).map { |x| Stream[x, x + 1] }. 289 | intercalate(Stream[0]).take(8) 290 | assert_equal(Stream[1, 2, 0, 3, 4, 0, 5, 6], xs) 291 | end 292 | 293 | def test_find 294 | assert_equal(nil, Stream[].find(&:odd?)) 295 | assert_equal(1, Stream[1, 2, 3, 4, 5].find(&:odd?)) 296 | assert_equal(2, Stream[1, 2, 3, 4, 5].find(&:even?)) 297 | assert_equal(1, Stream.from(1).find(&:odd?)) 298 | assert_equal(2, Stream.from(1).find(&:even?)) 299 | end 300 | 301 | def test_filter 302 | assert_equal(Stream[], Stream[].filter(&:odd?)) 303 | assert_equal(Stream[1, 3, 5], Stream[1, 2, 3, 4, 5].filter(&:odd?)) 304 | assert_equal(Stream[2, 4], Stream[1, 2, 3, 4, 5].filter(&:even?)) 305 | end 306 | 307 | def test_take 308 | assert_equal(Stream[], Stream[].take(1)) 309 | assert_equal(Stream[], Stream[1, 2, 3].take(0)) 310 | assert_equal(Stream[], Stream[1, 2, 3].take(-1)) 311 | assert_equal(Stream[1], Stream[1, 2, 3].take(1)) 312 | assert_equal(Stream[1, 2], Stream[1, 2, 3].take(2)) 313 | assert_equal(Stream[1, 2, 3], Stream[1, 2, 3].take(3)) 314 | assert_equal(Stream[1, 2, 3], Stream[1, 2, 3].take(4)) 315 | assert_equal(Stream[], Stream.from(1).take(0)) 316 | assert_equal(Stream[1, 2, 3], Stream.from(1).take(3)) 317 | assert_equal(Stream[0, 2, 4], Stream.from(0, 2).take(3)) 318 | end 319 | 320 | def test_take_while 321 | assert_equal(Stream[], Stream[].take_while { true }) 322 | assert_equal(Stream[], Stream[1, 2, 3].take_while { |x| x < 1 }) 323 | assert_equal(Stream[1], Stream[1, 2, 3].take_while { |x| x < 2 }) 324 | assert_equal(Stream[1, 2], Stream[1, 2, 3].take_while { |x| x < 3 }) 325 | assert_equal(Stream[1, 2, 3], 326 | Stream[1, 2, 3].take_while { |x| x < 4 }) 327 | end 328 | 329 | def test_drop 330 | assert_equal(Stream[], Stream[].drop(1)) 331 | assert_equal(Stream[1, 2, 3], Stream[1, 2, 3].drop(0)) 332 | assert_equal(Stream[1, 2, 3], Stream[1, 2, 3].drop(-1)) 333 | assert_equal(Stream[2, 3], Stream[1, 2, 3].drop(1)) 334 | assert_equal(Stream[3], Stream[1, 2, 3].drop(2)) 335 | assert_equal(Stream[], Stream[1, 2, 3].drop(3)) 336 | assert_equal(Stream[], Stream[1, 2, 3].drop(4)) 337 | assert_equal(Stream[6, 7, 8], Stream.from(1).drop(5).take(3)) 338 | end 339 | 340 | def test_drop_while 341 | assert_equal(Stream[], Stream[].drop_while { false }) 342 | assert_equal(Stream[1, 2, 3], 343 | Stream[1, 2, 3].drop_while { |x| x < 1 }) 344 | assert_equal(Stream[2, 3], Stream[1, 2, 3].drop_while { |x| x < 2 }) 345 | assert_equal(Stream[3], Stream[1, 2, 3].drop_while { |x| x < 3 }) 346 | assert_equal(Stream[], Stream[1, 2, 3].drop_while { |x| x < 4 }) 347 | end 348 | 349 | def test_unfoldr 350 | xs = Stream.unfoldr(3) { |x| x == 0 ? nil : [x, x - 1] } 351 | assert_equal(Stream[3, 2, 1], xs) 352 | end 353 | 354 | def test_s_unfoldr 355 | xs = Stream.unfoldr("foo,bar,baz") { |x| 356 | if x.empty? 357 | nil 358 | else 359 | y = x.slice(/([^,]*),?/, 1) 360 | [y, $'] 361 | end 362 | } 363 | assert_equal(Stream["foo", "bar", "baz"], xs) 364 | nats = Stream.unfoldr(0) { |x| [x, x + 1] } 365 | assert_equal(Stream[0, 1, 2], nats.take(3)) 366 | assert_equal(Stream[0, 1, 2, 3, 4], nats.take(5)) 367 | end 368 | 369 | def test_zip 370 | s1 = Stream.from(0) 371 | s2 = Stream.from(0, 2) 372 | s3 = Stream.from(0, 3) 373 | assert_equal(Stream[[0, 0, 0], [1, 2, 3], [2, 4, 6]], 374 | s1.zip(s2, s3).take(3)) 375 | 376 | s1 = Stream[0, 1, 2] 377 | s2 = Stream.from(0, 2) 378 | s3 = Stream.from(0, 3) 379 | assert_equal(Stream[[0, 0, 0], [1, 2, 3], [2, 4, 6]], 380 | s1.zip(s2, s3)) 381 | end 382 | 383 | def test_zip_with 384 | s1 = Stream.from(0) 385 | s2 = Stream.from(0, 2) 386 | s3 = Stream.from(0, 3) 387 | s = s1.zip_with(s2, s3) { |x, y, z| 388 | x + y + z 389 | } 390 | assert_equal(Stream[0, 6, 12, 18], s.take(4)) 391 | 392 | s1 = Stream[0, 1, 2, 3] 393 | s2 = Stream.from(0, 2) 394 | s3 = Stream.from(0, 3) 395 | s = s1.zip_with(s2, s3) { |x, y, z| 396 | x + y + z 397 | } 398 | assert_equal(Stream[0, 6, 12, 18], s) 399 | end 400 | 401 | def test_to_list 402 | assert_equal(List[], Stream[].to_list) 403 | assert_equal(List[1, 2, 3], Stream.from(1).take(3).to_list) 404 | end 405 | end 406 | end 407 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $VERBOSE = true 2 | 3 | require "test/unit" 4 | 5 | def with_tailcall_optimization 6 | if defined?(RubyVM) 7 | old_compile_option = RubyVM::InstructionSequence.compile_option 8 | RubyVM::InstructionSequence.compile_option = { 9 | :tailcall_optimization => true, 10 | :trace_instruction => false 11 | } 12 | begin 13 | yield 14 | ensure 15 | RubyVM::InstructionSequence.compile_option = old_compile_option 16 | end 17 | else 18 | yield 19 | end 20 | end 21 | --------------------------------------------------------------------------------