Subject: | support for multilevel categories |
Hi Bill,
List::Categorize is great, but sometimes I need multi-level categories.
So here is a proposal for adding this functionality.
Best regards, Laurent Dami
===========================================
3207f1f6674b27eb73d4b09f81a7dda7956b4f4f
lib/List/Categorize.pm | 43 ++++++++++++++++++++++++-----------------
--
t/categorize.t | 29 ++++++++++++++++++++++++++---
2 files changed, 50 insertions(+), 22 deletions(-)
diff --git a/lib/List/Categorize.pm b/lib/List/Categorize.pm
index fd4fdc4..e08a370 100644
--- a/lib/List/Categorize.pm
+++ b/lib/List/Categorize.pm
@@ -66,18 +66,21 @@ sub categorize (&@)
# Execute the categorizer subroutine to determine the category
# for this element.
#
- my $category = $coderef->();
+ my @categories = $coderef->();
- # If a category was returned, use it as a key in the %sublists
- # hash, and add the current element to the list referenced by
- # that key.
+ # If categories were returned, use them as a sequence of keys
+ # in the %sublists hash, and add the current element to the
list
+ # referenced by the last key.
#
- # If the categorizer didn't return a value (or returned undef),
+ # If the categorizer didn't return a list (or returned undef),
# then leave this element out of %sublists entirely.
#
- if (defined $category)
+ my $sublist = \%sublists;
+ while (@categories && defined $categories[0])
{
- push @{ $sublists{ $category } }, $_;
+ my $categ = shift @categories;
+ if (@categories) { $sublist = $sublist->{$categ} ||= {}; }
+ else { push @{$sublist->{$categ}}, $_; }
}
}
@@ -111,16 +114,17 @@ This documentation describes List::Categorize
version 0.01.
# Transform the element before placing it in the hash.
$_ = ucfirst $_;
- # Use the first letter of the element as the category.
- substr($_, 0, 1);
+ # Use the first letter of the element as the category,
+ # then the first 2 letters as a second-level category
+ substr($_, 0, 1), substr($_, 0, 2);
- } qw( apple banana antelope bear canteloupe coyote );
+ } qw( apple banana antelope bear canteloupe coyote ananas );
# %capitalized now contains
# (
- # A => [ 'Apple', 'Antelope' ],
- # B => [ 'Banana', 'Bear' ],
- # C => [ 'Canteloupe', 'Coyote' ]
+ # A => { An => ['Antelope', 'Ananas'], Ap => ['Apple'], },
+ # B => { Ba => ['Banana'], Be => ['Bear'], },
+ # C => { Ca => ['Canteloupe'], Co => ['Coyote'] },
# )
=head1 DESCRIPTION
@@ -139,12 +143,13 @@ Nothing by default.
my %hash = categorize { $_ > 10 ? 'Big' : 'Little' } @list;
C<categorize> creates a hash by running BLOCK for each element in LIST.
-The block returns a hash key (the "category") for the current
-element. (If it returns C<undef> for a list element, that element is
-not placed in the resulting hash.)
+The block returns a list of hash keys (the "categories") for the
current
+element. (If it returns and empty list or a list starting with
C<undef>,
+the corresponding element is not placed in the resulting hash.)
-The resulting hash contains a key for each category, and each key
refers to a
-list of the elements that correspond to that category.
+The resulting hash contains a key for each category or subcategory.
+At the bottom leve, each key refers to a
+list of the elements that correspond to that sequence of categories.
Within the block, $_ refers to the current list element. Elements can
be
modified before they're placed in the target hash by modifying the $_
@@ -154,7 +159,7 @@ variable:
# %hash now contains ( List => [ 'ONE', 'TWO', 'THREE' ] )
-NOTE: The categorizer should return a string, or C<undef>. Other values
+NOTE: The categorizer should return list of strings, or C<undef>.
Other values
are reserved for future use, and may cause unpredictable results in the
current version.
diff --git a/t/categorize.t b/t/categorize.t
index 857668b..1d94cb9 100644
--- a/t/categorize.t
+++ b/t/categorize.t
@@ -1,4 +1,4 @@
-#! perl -T
+#! perl # -T
#
# categorize.t
#
@@ -8,7 +8,7 @@
use strict;
use warnings;
-use Test::More tests => 14;
+use Test::More tests => 15;
use Test::Deep;
use Test::NoWarnings;
@@ -65,6 +65,7 @@ test_sublist_element_transform();
test_collapsing_sublist_element_transform();
test_ignored_elements();
test_categorizer_args();
+test_multilevel_categories();
# Test::NoWarnings automatically inserts an additional test
# that makes sure no warnings were emitted during testing.
@@ -395,7 +396,6 @@ sub test_list_of_lists
# will be a list of lists.)
#
my %sublists_by_length = categorize { scalar @{ $_ } } @source;
-
cmp_deeply(\%sublists_by_length, \%expected_by_length,
'List of lists to hash of sublists by sublist length'
);
@@ -576,4 +576,27 @@ sub test_categorizer_args
);
}
+
+sub test_multilevel_categories {
+ my %expected = (
+ A => { An => ['Antelope', 'Ananas'], Ap => ['Apple'], },
+ B => { Ba => ['Banana'], Be => ['Bear'], },
+ C => { Ca => ['Canteloupe'], Co => ['Coyote'] },
+ );
+
+ my %sublists = categorize {
+ $_ = ucfirst $_;
+
+ # Use the first letter of the element as the category,
+ # then the first 2 letters as a second-level category
+ substr($_, 0, 1), substr($_, 0, 2);
+
+ } qw( apple banana antelope bear canteloupe coyote ananas );
+
+
+ cmp_deeply(\%sublists, \%expected,
+ 'Multilevel categories'
+ );
+}
+
# end categorize.t