Skip to content

Commit

Permalink
started to fill out Lazy.pm
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephen R. Scaffidi committed Mar 30, 2012
1 parent b0dac65 commit a44b7ba
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 93 deletions.
111 changes: 20 additions & 91 deletions dist.ini
@@ -1,133 +1,62 @@
name    = Set-CrossProduct-Lazy
author = Stephen R. Scaffidi <sscaffid@akamai.com>
license = Perl_5
copyright_holder = Akamai Technologies
name    = Set-CrossProduct-Lazy
author = Stephen R. Scaffidi <sscaffidi@cpan.org>
license = Perl_5
copyright_holder = Stephen R. Scaffidi
copyright_year   = 2012


;; only pretend to cut a release. comment this out
;; when you want to really do it.
;[FakeRelease]


;; gather all the files for distribution
[Git::GatherDir]
; exclude these because they're generated by other plugins
; or only needed for cutting a release
exclude_filename = Build.PL
exclude_filename = Makefile.PL
exclude_filename = dist.ini
exclude_filename = weaver.ini
exclude_filename = README.mkdn


;; use the basic pluginbundle, with some customizations
[@Filter]
-bundle = @Basic
; 'cause it's overridden by Git::GatherDir above
-remove = GatherDir
; 'cause this isn't destined for the CPAN.
;-remove = UploadToCPAN


;; automagically generate boring boilerplate POD, and
;; weave it in with the good stuff. see weaver.ini as well.
[PodWeaver]


;; use the plugins in the git bundle, except the removed ones.
[@Filter]
-bundle = @Git
; don't auto-commit
-remove = Git::Commit
; don't auto-push
-remove = Git::Push
; allow a release even if these files aren't committed
allow_dirty = dist.ini


;;;; While I like semantic versioning, I'm sticking with
;;;; normal versions for now.
;;[Author::YANICK::NextSemanticVersion]
;; Some generated files *should* be checked in, copy them
;; to the dist root after generating
[CopyFilesFromBuild]
move = README.mkdn
copy = Build.PL
copy = Makefile.PL


;; automatically increment the version on release, based on a
;; previous git tag with a version.
[Git::NextVersion]
[ChangelogFromGit]


;; automagically determine prerequisite modules for the dist
[PodWeaver]
[Git::NextVersion]
[AutoPrereqs]


;; put info about this dist.ini into META.{yml,json}
[MetaConfig]


;; produce a newer-style META.json
[MetaJSON]


;; validate the META file
[MetaTests]


;; write a Build.PL
[ModuleBuild]


;; add a $DIST var to each package
[PkgDist]


;; add a $VERSION var to each package
[PkgVersion]


;; automatically write pod coverage tests
[PodCoverageTests]


;; automagically write pod syntax tests
[PodSyntaxTests]


;; add version pod section to each module
[PodVersion]


;; add github info to meta if origin points there
[GithubMeta]


;; create a fat-packer archive of the dist
;[FatPacker]
;script = scripts/edctool


;; Add a test to make sure the module compiles
[Test::Compile]


;; Make a markdown version of README to make GitHub happy
[ReadmeMarkdownFromPod]


;; Some generated files *should* be checked in, copy them
;; to the dist root after generating
[CopyFilesFromBuild]
move = README.mkdn
;copy = Makefile.PL
;copy = Build.PL


;; put dist archives into a 'release' directory
[ArchiveRelease]


;;;; These seem to be broken... I need to find an alternative...
;;;; Also, I would like to be able to mix in manual-edits with
;;;; auto-generated entries. Time to write it myself? :-)
;; create changelog from git commit messages
;;[ChangelogFromGit]
;;[GitFmtChanges]


;; only pretend to cut a release. comment this out
;; when you want to really do it.
[FakeRelease]


152 changes: 150 additions & 2 deletions lib/Set/CrossProduct/Lazy.pm
@@ -1,5 +1,153 @@
use strict;
use warnings;
package Set::CrossProduct::Lazy;
package Set::CartesianProduct::Lazy;

# ABSTRACT: lazily calculate the tuples of a cartesian-product

use List::Util qw( reduce );


=method new
Construct a new object. Takes the following arguments:
=for :list
* options
A hashref of options that modify the way the object works.
If you don't want to specify any options, simply omit this
argument.
* sets
A list of arrayrefs from which to compute the cartesian product.
You can list as many as you want.
For the options hash, the following keys are recognized:
=begin :list
= less_lazy
Makes the get method slightly faster, at the expense
of not being able to account for any modifications made
to the original input arrays. If you modify one of the
arrays used to consruct the object, the results of all
the other methods are B<undefined>. You might get the
wrong answer. You might trigger an exception, you might
get mutations that give you super-powers at the expense
of never being able to touch another human being without
killing them.
Some examples:
my $cpl = Set::CartesianProduct::Lazy->new(\@a, [qw(foo bar baz)], \@b);
my $cpl = Set::CartesianProduct::Lazy->new( { less_lazy => 1 }, \@a, \@b, \@c);
=end :list
=cut
sub new { ... }


=method get
Return the tuple at the given "position" in the cartesian product.
The positions, like array indices, are based at 0.
If called in scalar context, an arrayref is returned. If called in list
context, a list is returned.
If you ask for a position that exceeds the bounds of the array defining the
cartesian product the result will be an empty list or undef, depending on
whether this was called in scalar or list context.
=cut
sub get { ... }


=method count
Return the count of tuples that would be in the cartesian
product if it had been generated.
=cut
sub count { ... }


=method last_idx
Return the index of the last tuple that would be in the cartesian
product if it had been generated. This is just for conveniece
so you don't have to write code like this:
for my $i ( 0 .. $cpl->count - 1 ) { ... }
And you can do this instead:
for my $i ( 0 .. $cpl->last_idx ) { ... }
Which I feel is more readable.
=cut
sub last_idx { ... }


1 && q{a set in time saves nine};
__END__
=head1 SYNOPSIS
my @a = qw( foo bar baz bah );
my @b = qw( wibble wobble weeble );
my @c = qw( nip nop );
my $cpl = Set::CartesianProduct::Lazy->new( \@a, \@b, \@c );
my $tuple;
$tuple = $cpl->get(0); # [ qw( foo wibble nip ) ]
$tuple = $cpl->get(21); # [ qw( bah wobble nop ) ]
$tuple = $cpl->get(7); # [ qw( bar wobble nip ) ]
$cpl->count; # 24
$cpl->last_idx; # 23
=head1 DESCRIPTION
If you have some number of arrays, say like this:
@a = qw( foo bar baz bah );
@b = qw( wibble wobble weeble );
@c = qw( nip nop );
And you want all the combinations of one element from each array, like this:
@cp = (
[qw( foo wibble nip )],
[qw( foo wibble nop )],
[qw( foo wobble nip )],
[qw( foo wobble nop )],
[qw( foo weeble nip )],
# ...
[qw( bah wobble nop )],
[qw( bah weeble nip )],
[qw( bah weeble nop )],
)
What you want is a Cartesian Product (also called a Cross Product, but my
mathy friend insists that Cartesian is correct)
Yes, there are already a lot of other modules on the CPAN that do this.
I won't claim that this module does this calculation any better or faster,
but it does do it I<differently>, as far as I can tell.
Nothing else seemed to offer a specific feature - I needed to pick random
individual tuples from the Cartesian Product, I<without> iterating over
the whole set and I<without> calculating any tuples until they were
asked for. Bonus points for not making a copy of the original input sets.
I needed the calculation to be lazy, and I needed random-access with O(1)
retrieval time, even if that meant a slower implementation overall. And
I didn't want to use RAM unnecessarily, since the data I was working with
was of a significant size.
1;

0 comments on commit a44b7ba

Please sign in to comment.