Skip to content

Instantly share code, notes, and snippets.

@hoehrmann
Created March 31, 2013 00:24
Show Gist options
  • Save hoehrmann/5278941 to your computer and use it in GitHub Desktop.
Save hoehrmann/5278941 to your computer and use it in GitHub Desktop.
package Acme::IEnumerable::IGrouping;
use Modern::Perl;
use base qw/Acme::IEnumerable/;
1;
package Acme::IEnumerable::List;
use Modern::Perl;
use base qw/Acme::IEnumerable/;
1;
package Acme::IEnumerable;
use Modern::Perl;
use Carp;
sub _create {
bless {
_new => $_[0],
}, __PACKAGE__;
}
sub new { $_[0]->{_new}->() }
sub range {
my ($class, $from) = @_;
return _create sub {
return sub {
state $counter = $from // 0;
return \($counter++);
};
};
}
sub take {
my ($self, $count) = @_;
return _create sub {
return sub {
state $left = $count;
return unless $left;
$left--;
state $base = $self->new();
my $item = $base->();
return unless ref $item;
return $item;
};
};
}
sub take_while {
my ($self, $predicate) = @_;
return _create sub {
return sub {
state $base = $self->new();
my $item = $base->();
return unless ref $item;
local $_ = $$item;
return unless $predicate->($_);
};
};
}
sub group_by {
my ($self, $key_extractor) = @_;
return _create sub {
my $base = $self->new;
my %temp;
while (1) {
my $item = $base->();
last unless ref $item;
local $_ = $$item;
my $key = $key_extractor->($_);
push @{ $temp{$key} }, $item;
}
# TODO: Should value be another IEnumerable? Should this return
# an IGrouping for each group instead of a hash reference?
my @temp = map { { key => $_, value => $temp{$_} } } keys %temp;
return sub {
return unless @temp;
return \(pop @temp);
}
};
}
sub skip {
my ($self, $count) = @_;
return _create sub {
return sub {
state $base = $self->new();
state $left = $count;
while ($left) {
my $item = $base->();
return unless ref $item;
$left--;
}
return $base->();
};
};
}
sub skip_while {
my ($self, $predicate) = @_;
return _create sub {
return sub {
state $base = $self->new();
state $skip = 1;
while ($skip) {
my $item = $base->();
return unless ref $item;
local $_ = $$item;
$skip &= !! $predicate->($_);
return $item unless $skip;
}
return $base->();
};
}
}
sub element_at {
my ($self, $index) = @_;
croak "Index out of range for element_at" if $index < 0;
my $base = $self->new();
while (1) {
my $item = $base->();
croak "Index out of range for element_at" unless ref $item;
return $$item unless $index--;
}
...
}
sub first {
$_[0]->element_at(0);
}
sub first_or_default {
my ($self, $default) = @_;
my $base = $self->new();
my $item = $base->();
return $default unless ref $item;
return $$item;
}
sub last_or_default {
my ($self, $default) = @_;
my $base = $self->new();
my $item = $base->();
return $default unless ref $item;
while (1) {
my $next = $base->();
return $$item unless ref $next;
$item = $next;
}
}
sub count {
my ($self, $predicate) = @_;
$predicate //= sub { 1 };
my $base = $self->new();
while (1) {
state $counter = 0;
my $item = $base->();
return $counter unless ref $item;
local $_ = $$item;
$counter += !! $predicate->($_);
}
...
}
sub select {
my ($self, $projection) = @_;
return _create sub {
return sub {
state $base = $self->new();
my $item = $base->();
return unless ref $item;
local $_ = $$item;
return \($projection->($_));
};
};
}
sub where {
my ($self, $predicate) = @_;
return _create sub {
return sub {
state $base = $self->new();
while (1) {
my $item = $base->();
return unless ref $item;
local $_ = $$item;
next unless $predicate->($_);
return $item;
}
};
};
}
sub aggregate {
my $self = shift;
my $base = $self->new();
my ($func, $seed);
if (@_ == 1) {
$func = shift;
my $item = $base->();
croak unless ref $item;
$seed = $$item;
} elsif (@_ == 2) {
$seed = shift;
$func = shift;
} else {
...
}
while (1) {
my $item = $base->();
return $seed unless ref $item;
$seed = $func->($seed, $$item);
}
...
}
sub min {
my ($self) = @_;
return $self->aggregate(sub {
$_[0] < $_[1] ? $_[0] : $_[1]
});
}
sub max {
my ($self) = @_;
return $self->aggregate(sub {
$_[0] > $_[1] ? $_[0] : $_[1]
});
}
sub all {
my ($self, $predicate) = @_;
my $base = $self->new();
while (1) {
my $item = $base->();
return 1 unless ref $item;
local $_ = $$item;
return 0 unless $predicate->($_);
}
...
}
sub any {
my ($self, $predicate) = @_;
$predicate //= sub { 1 };
my $base = $self->new();
while (1) {
my $item = $base->();
return 0 unless ref $item;
local $_ = $$item;
return 1 if $predicate->($_);
}
...
}
sub sum {
my $self = shift;
return $self->aggregate(0, sub { $_[0] + $_[1] });
}
sub to_list {
my $self = shift;
my @result;
my $enum = $self->new();
for (my $item = $enum->(); ref $item; $item = $enum->()) {
push @result, $$item;
}
@result;
}
sub to_array {
to_list(@_);
}
1;
package main;
use Modern::Perl;
use YAML::XS;
use Data::Dumper;
my $v1 = Acme::IEnumerable->range(1);
my $v2 = $v1;
my $i1 = $v1->new();
my $i2 = $v2->new();
say ${ $i1->() };
say ${ $i1->() };
say ${ $i1->() };
say "";
say ${ $i2->() };
say ${ $i2->() };
say ${ $i2->() };
say Dumper $i2;
say join '',
Acme::IEnumerable
->range(1)
->select(sub { $_ ** 3 })
->select(sub { sprintf "%6.0f\n", $_ })
->take(20)
->to_list;
say "---";
say Acme::IEnumerable
->range(1)
->take(100)
->aggregate(sub { $_[0] + $_[1] });
say join "\n", Acme::IEnumerable
->range(1)
->take(20)
->group_by(sub { $_ & 1 })
->select(sub { $_->{key} })
->to_list;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment