package Math::SimpleHisto::XS::Named; use strict; use warnings; use Math::SimpleHisto::XS; our $VERSION = '0.02'; use vars qw($AUTOLOAD); use Carp qw(croak); sub new { my $class = shift; my %param = @_; my $names = $param{names}; croak("Need list of bin names!") if not $names or not ref($names) eq 'ARRAY'; my $nbins = @$names; my $hist = Math::SimpleHisto::XS->new( nbins => $nbins, min => 0, max => $nbins, ); my %namehash; my $i = 0; $namehash{$_} = $i++ for @$names; my $self = bless( { # careful about the cloning logic below and dump() hist => $hist, names => [@$names], namehash => \%namehash, } => $class ); return $self; } # Generate bin-number-as-first-parameter delegation foreach my $methname (qw( bin_content set_bin_content )) { my $sub = sub { my $self = shift; my $name = shift; croak("Invalid bin name '$name'") if not exists $self->{namehash}{$name}; my $ibin = $self->{namehash}{$name}; return $self->{hist}->$methname($ibin, @_); }; SCOPE: { no strict 'refs'; *{"$methname"} = $sub; } } # generate cloning delegation foreach my $methname (qw(clone new_alike)) { my $sub = sub { my $self = shift; my $clone = bless({ %$self, names => [@{$self->{names}}], namehash => {%{$self->{namehash}}}, hist => $self->{hist}->$methname(@_), } => ref($self)); return $clone; }; SCOPE: { no strict 'refs'; *{"$methname"} = $sub; } } # Generate methods that croak because they make no sense # on named bins foreach my $methname (qw( find_bin min max binsize width new_from_bin_range new_alike_from_bin_range integral rand bin_center bin_lower_boundary bin_upper_boundary bin_centers bin_lower_boundaries bin_upper_boundaries )) { my $sub = sub { croak("The '$methname' method makes little sense for named bins"); }; SCOPE: { no strict 'refs'; *{"$methname"} = $sub; } } sub fill { my $self = shift; croak("Need at least one argument") if not @_; my ($x, $w) = @_; my $hist = $self->{hist}; my $namehash = $self->{namehash}; if (ref($x) eq 'ARRAY') { $x = [map $namehash->{$_}, @$x]; } else { $x = $namehash->{$x}; } return $hist->fill($x, defined($w) ? ($w) : ()); } *fill_by_bin = \&fill; sub get_bin_names { return @{ $_[0]->{names} }; } # Not a fan, but unless I find an elegant way to attach more data to the # XS object, I can't think of anything else. Retrofitting XS::Object::Magic # to the SimpleHisto implementation is too annoying. sub AUTOLOAD { my $self = $_[0]; my $methname = $AUTOLOAD; $methname =~ /^(.*)::([^:]+)$/ or die "Should not happen"; (my $class, $methname) = ($1, $2); my $hist = $self->{hist}; if ($hist->can($methname)) { my $delegate = sub { my $self = shift; return $self->{hist}->$methname(@_); }; SCOPE: { no strict 'refs'; *{"$methname"} = $delegate; } goto &$methname; } croak(qq{Can't locate object method "$methname" via package "$class"}); } sub dump { my $self = shift; my $type = lc(shift); my $hist_dump = $self->{hist}->dump($type); my $rv = $Math::SimpleHisto::XS::JSON->encode({ %$self, hist => $hist_dump, class => ref($self), histclass => ref($self->{hist}) }); return $rv; } sub new_from_dump { my $class = shift; my $type = lc(shift); my $data = shift; my $struct = $Math::SimpleHisto::XS::JSON->decode($data); $class = delete $struct->{class}; my $hclass = delete $struct->{histclass}; $struct->{hist} = $hclass->new_from_dump($type, delete $struct->{hist}); return bless($struct => $class); } # Can't simply be delegated, eventhough the implementation is the same :( sub STORABLE_freeze { my $self = shift; my $cloning = shift; my $serialized = $self->dump('simple'); return $serialized; } # Can't simply be delegated, eventhough the implementation is the same :( sub STORABLE_thaw { my $self = shift; my $cloning = shift; my $serialized = shift; my $new = ref($self)->new_from_dump('simple', $serialized); $$self = $$new; $new = undef; # need to care about DESTROY here, normally } sub DESTROY {} 1; __END__ =head1 NAME Math::SimpleHisto::XS::Named - Named histograms for Math::SimpleHisto::XS =head1 SYNOPSIS use Math::SimpleHisto::XS::Named; my $hist = Math::SimpleHisto::XS::Named->new( names => [qw(boys girls)], ); $hist->fill('boys', 12); $hist->fill($_) for map $_->gender, @kids; =head1 DESCRIPTION B This module provides histograms with named bins. It is built on top of L and attempts to provide the same interface as far as it makes sense to support. The following documentation covers only the differences between the two modules, so a basic familiarity with C is required. It is important to not attempt to use a histogram with named bins by looking at its internal coordinates or bin numbering. =head1 API DIFFERENCES TO Math::SimpleHisto::XS =head2 Constructors The regular constructor, C requires one named parameter: C, an array reference of bin names. The C, C methods work the same as with C, but the C, and C methods are B implemented for named histograms. =head2 Filling Histograms The C method normally takes any of the following parameters: =over 2 =item * A single coordinate to fill into the histogram =item * A single coordinate followed by a single weight =item * An array reference containing coordinates to fill into the histogram =item * Two array references of the same array length, the first of which contains coordinates, the second of which contains the respective weights =back The C method has been overridden in such a way that wherever the interface normally calls for coordinates, you need to pass in bin names instead. Effectively, that means C works much like C for named histograms. =head2 Additional Methods This class provides a C method which returns a list of bin names in storage order. =head2 Unimplemented Methods Apart from the aforementioned C and C methods, the following are not implemented, generally, because they do not apply to named bins: C, C, C, C, C, C, C C, C, C, C, C, C =head2 Methods With Bin Number Parameters Methods that normally take a bin number as the first parameter, require a bin name instead. These are: C, C, C. =head2 Serialization This class implements the C and C methods of the C interface by wrapping the histogram dump in JSON which contains the additional information. If you always stick to using C and C, then this is an implementation details that should not matter to your code. The C freeze/thaw hooks are delegated to the C implementation and should work as is. The serialization wrapping does not currently handle dumping/loading dumps in the same backwards compatible way that C does. Since there are no version-incompatibilities in the C code yet, this is not currently an issue and will be addressed when the first incompatibility pops up. =head1 SEE ALSO This module is built on top of L. =head1 AUTHOR Steffen Mueller, Esmueller@cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright (C) 2011,2012 by Steffen Mueller This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.1 or, at your option, any later version of Perl 5 you may have available. =cut