File Coverage

blib/lib/List/Categorize.pm
Criterion Covered Total %
statement 31 31 100.0
branch 7 8 87.5
condition 4 4 100.0
subroutine 6 6 100.0
pod 1 1 100.0
total 49 50 98.0


line stmt bran cond sub pod time code
1             package List::Categorize;
2             #
3             # ABSTRACT: Categorize list items into a tree of named sublists.
4             #
5             # See documentation after __END__ below.
6             #
7              
8 3     3   196999 use strict;
  3         23  
  3         76  
9 3     3   16 use warnings;
  3         5  
  3         84  
10 3     3   75 use 5.006;
  3         13  
11              
12             ## Module Interface
13              
14 3     3   25 use base 'Exporter';
  3         15  
  3         358  
15 3     3   19 use Carp qw/croak/;
  3         4  
  3         865  
16              
17             our $VERSION = '0.04';
18             our @EXPORT = qw();
19             our @EXPORT_OK = qw(
20             categorize
21             );
22             our %EXPORT_TAGS = (
23             all => \@EXPORT_OK
24             );
25              
26              
27             ## Subroutines
28              
29             sub categorize (&@)
30             #
31             # Usage: %tree = categorize {BLOCK} @LIST
32             # Returns: a tree of lists
33             #
34             # Creates a tree by running a subroutine for each element in a
35             # list. That subroutine should return a list of hash keys (the
36             # "categories") for the current element, each key in the list
37             # corresponding to the next depth level within the tree. If the
38             # subroutine returns undef for a list element, that element is not
39             # placed in the resulting tree.
40             #
41             # The resulting tree contains a key for each first-level category, and
42             # each key refers to a sub-tree for the next-level category,
43             # etc. until reaching a leaf, which contains a list of the elements
44             # that correspond to that sequence of categories. If there is only one
45             # level of categories, the structure is just a hashref of lists of
46             # elements.
47             #
48             {
49             # Parameters
50             #
51 16     16 1 221636 my $coderef = shift;
52              
53             # @_ is used directly, in the loop below.
54              
55             # This is the tree that will be returned to the caller.
56             #
57 16         49 my %tree = ();
58              
59             # Iterate over the provided list, categorizing
60             # each element.
61             #
62 16         55 for my $element (@_)
63             {
64             # Localize $_, then copy the current element into it, so the
65             # categorizer subroutine can refer to $_ (in the same way as
66             # map, grep, and sort do).
67             #
68             # Copying the element keeps it from acting as an alias into the
69             # @_ list, so the categorizer can modify $_ without damaging the
70             # source list.
71             #
72 197         443 local $_ = $element;
73              
74             # Execute the categorizer subroutine to determine the categories
75             # for this element.
76             #
77 197         484 my @categories = $coderef->();
78              
79             # If categories were returned, use them as keys in %tree,
80             # and add the current element to the list referenced by
81             # those keys.
82             #
83             # If the categorizer didn't return a value (or returned undef),
84             # then leave this element out of %tree entirely.
85             #
86              
87 197         1715 my $node = \%tree;
88             CATEGORY:
89 197         571 while (@categories) {
90 185         401 my $categ = shift @categories;
91              
92             # if an undef is encountered, this element will be ignored
93 185 100       482 defined $categ or last CATEGORY;
94              
95 183 100       429 if (@categories) {
96             # other keys remaining, so create or retrieve an intermediate node
97 15   100     43 $node = $node->{$categ} ||= {};
98 15 100       196 ref $node ne 'ARRAY'
99             or croak "inconsistent use of category '$categ'";
100             }
101             else {
102             # add the element to a leaf
103 168   100     615 my $ref = ref $node->{$categ} || '';
104 168 50       445 $ref ne 'HASH'
105             or croak "inconsistent use of category '$categ'";
106 168         305 push @{$node->{$categ}}, $_;
  168         845  
107             }
108             }
109             }
110              
111 15         104 return %tree;
112             }
113              
114              
115             1;
116              
117             __END__