use Stream; package Stream; sub nmerge { my @streams = @_; my @nonEmptyStreams = grep !$_->is_empty, @streams; return @nonEmptyStreams[0] if scalar @nonEmptyStreams < 2; my @heads = map $_->head, @nonEmptyStreams; # now figure out what the smallest head value is # first initialize the minimal value for min my $min = shift @heads; for (@heads) { if ($min > $_) { $min = $_; } } Stream->new( $min, sub { &nmerge( map { $_->head == $min ? $_->tail : $_ } @nonEmptyStreams) }); } # Merge two streams of numbers in ascending order, discarding duplicates sub merge3 { my ($s1, $s2, $s3) = @_; return &merge($s2, $s3) if $s1->is_empty; return &merge($s1, $s3) if $s2->is_empty; return &merge($s1, $s2) if $s3->is_empty; my $h1 = $s1->head; my $h2 = $s2->head; my $h3 = $s3->head; if ($h1 > $h2) { Stream->new($h2, sub { &merge($s1, $s2->tail) }); } elsif ($h1 < $h2) { Stream->new($h1, sub { &merge($s1->tail, $s2) }); } else { # heads are equal Stream->new($h1, sub { &merge($s1->tail, $s2->tail) }); } } sub nmerge2 { my @streams = @_; my $min; my @minstreams; my @unminstreams; my $headstream; 1 while (($headstream = shift @_) && exists($headstream->{e})); return Stream->empty unless $headstream; $min = $headstream->{h}; @minstreams = ($headstream); foreach $s (@_) { next if exists($s->{e}); my $h; if (($h = $s->{h}) < $min) { $min = $h; push @unminstreams, @minstreams; @minstreams = ($s); } elsif ($h == $min) { push @minstreams, $s; } else { push @unminstreams, $s; } } Stream->new($min, sub { &nmerge2(@unminstreams, map {$_->tail} @minstreams) } ); } sub h1 { my $hamming; $hamming = Stream->new(1, sub { &merge($hamming->scale(2), &merge($hamming->scale(3), $hamming->scale(5) )) } ); } sub h2 { my $hamming; $hamming = Stream->new(1, sub { &nmerge($hamming->scale(2), $hamming->scale(3), $hamming->scale(5) ) } ); } sub h3 { my $hamming; $hamming = Stream->new(1, sub { &nmerge2($hamming->scale(2), $hamming->scale(3), $hamming->scale(5) ) } ); } package main; use Benchmark; # &Stream::h3->show; timethese(10, { orig => sub { &Stream::h1->take(1000) }, hsu => sub { &Stream::h2->take(1000) }, mjd => sub { &Stream::h3->take(1000) }, } );