NAME
Set::DynamicGroups - Manage groups of items dynamically
VERSION
version 0.014
SYNOPSIS
use Set::DynamicGroups;
my $set = Set::DynamicGroups->new();
$set->add(group_name => 'member1');
$set->add(green => ['junior', 'french peas']);
$set->add(blue => {include => 'madame blueberry'});
$set->add(other => {not_in => [qw(blue green)]});
my @members = $set->group('group_name');
# or
my $all = $set->groups();
DESCRIPTION
An instance of Set::DynamicGroups
can manage a list of groups and the items (members) of those groups. It takes in various definitions of groups (rules about how to build the member list (see "GROUP SPECIFICATION")) and will return the list of items contained in any named groups.
The module was specifically designed to allow groups to be defined dynamically by rules based on other groups. For instance you can define one group as a list of all the items included in two other groups. You can also say that one group will be composed of any known members not in some other group.
METHODS
new
my $set = Set::DynamicGroups->new();
Constructor.
Takes no arguments.
add
$set->add(group_name => $group_spec);
Add items to the specified group.
See "GROUP SPECIFICATION" for details on the possible values of $group_spec
.
add_items
$set->add_items(qw(bob larry));
$set->add_items('archibald', [qw(jimmy jerry)]);
Add the provided items to the full list of known items. Arguments can be strings or array references (which will be flattened).
This is useful to include items that you know are available but may not be explicitly included in other groups. Then groups defined by exclusions will base their members off of all known items.
Items that are specified in group definitions do not need to be specified separately.
Aliased as add_members
.
group
@items = $set->group($group_name);
Return a list of the items in the specified group.
This is a convenience method that calls "groups" with the provided group name and returns a list (rather than a hash of arrayrefs).
The above example is equivalent to:
@items = @{ $set->groups($group_name)->{$group_name} };
except that it will croak
if the specified group does not exist.
groups
$set->groups(); # returns {groupname => \@items, ...}
$set->groups(@group_names);
Return a hashref of each group and the items contained.
Sending a list of group names will restrict the hashref to just those groups (instead of all).
The keys are group names and the values are arrayrefs of items.
See "DEPENDENCY RESOLUTION" for a discussion on the way members are determined for mutually dependent groups.
items
my @items = $set->items();
Return a list of all known items.
This includes any items specified explicitly with "add_items" as well all items explicitly include
d in group specifications.
Aliased as members
.
normalize
$norm_spec = $set->normalize($group_spec);
Used internally to normalize group specifications.
Upgrades a string to an arrayref. Upgrades an arrayref to a hash. Renames aliases to the canonical keys.
set
$set->set(group_name => $group_spec);
Set a group specification to the provided value (resetting any previous specifications).
This is a shortcut for removing any previous specifications and then calling "add".
set_items
$set->set_items(@items);
Set the full list of items to the provided items.
This is a shortcut for removing any previous items and then calling "add_items".
Aliased as set_members
.
GROUP SPECIFICATION
A group specification can be in one of the following formats:
- string
-
A single member; This is converted to an arrayref with one element.
- arrayref
-
An array of items. This is converted into a hashref with the
include
key. - hashref
-
Possible options:
include
(oritems
(ormembers
))-
An arrayref of items to include in the group
exclude
(ornot
)-
An arrayref of items to exclude from the group
include_groups
(orin
)-
An arrayref of groups whose items will be included
exclude_groups
(ornot
)-
An arrayref of groups whose items will be excluded
Each option can be a an arrayref or a string which will be converted to an arrayref with a single element.
Specifications that only have
exclude
and/orexclude_groups
will first be filled with all known items. (This is where "add_items" comes in.)
DEPENDENCY RESOLUTION
The main impetus for the design of this module was the desire to define groups dependent on the definition of other groups.
This appears to work for the limited test cases I have come up with.
However, mutually dependent groups present a problem.
(If you're not dealing with mutually dependent groups feel free to skip this section.)
In order to avoid infinite recursion when determining a group's members a dependency resolution strategy is needed.
I have not determined a canonical strategy, but imagine that multiple could be argued for, and perhaps an option/attribute on the object would be the most useful.
I do not have a use-case for mutually dependent groups, so I have put little thought (and even less code) into it.
What follows is the discussion I've had so far (with the two plush penguins on my desk):
Possible strategies:
die / croak / stop
croak
if a mutual dependency is found.Simple, but possibly not always the most helpful.
each / more
Try to determine each group's members independently of any other groups. This often results in groups getting more members (than less).
b => {in => 'c'} c => {not_in => 'b', include => 'cat'} # result: # b => ['cat'] # c => ['cat']
Why? If we start with
b
:b
will try to resolvec
c
will includecat
and then try to resolveb
Since
b
is already in the stack it cannot be resolved and returns[]
c
finishes as['cat']
b
included['cat']
(fromc
)b
finishes as['cat']
Then C will try to resolve itself independently:
c
will includecat
and then try to resolveb
b
will try to resolvec
Since
c
is already in the stack it cannot be resolved and returns[]
b
finished as[]
c
included['cat']
and excluded[]
(fromb
)c
finishes as['cat']
It may not seem quite right that
b
andc
end up equaling each other, but honestly what would you expect from those definitions (besides infinite recursion)?once? / less?
Determine each group once rather than restarting for each group. This may involve passing the entire stack of resolutions-thus-far instead of just the names currently being resolved. I haven't really determined if this could work reliably or what exactly would happen.
includes_first
First do the includes (the easy part) for each group, then go through them all again and try to resolve groups from what we have thus far.
This might turn out differently than
each
, though I have not contemplated the actual implementation.hard
Try hard to determine the members for each group. Start with the
include
s, then make a stack of all the groups and process each group... If a group finishes successfully (rather than exiting early to avoid infinite recursion) remove it from the stack. Keep looping over the stack attempting to process each group until all have been removed or until a full loop through the stack removes none.Then resort to one of the other strategies to resolve any remaining groups.
The current implementation is each
(more
) because that is what I determined to be happening at my first attempt to stop the infinite recursion.
If you have ideas on strategies, implementations, or test cases feel free to send me your thoughts.
As always, patches are welcome.
BUGS AND LIMITATIONS
Possibly a lot if you get really complex with group dependencies. See "DEPENDENCY RESOLUTION" for the current discussion on the topic.
Currently everything is calculated upon request. This may be an important part of one of the dependency resolution strategies, but if at any time it is not, then it's merely inefficient.
RATIONALE
I searched for other "grouping" modules on CPAN but found none that supported basing one group off of another. Unsatisfied by the API of the modules I looked at, I borrowed their namespace and created this implementation.
SEE ALSO
SUPPORT
Perldoc
You can find documentation for this module with the perldoc command.
perldoc Set::DynamicGroups
Websites
The following websites have more information about this module, and may be of help to you. As always, in addition to those websites please use your favorite search engine to discover more resources.
Search CPAN
The default CPAN search engine, useful to view POD in HTML format.
RT: CPAN's Bug Tracker
The RT ( Request Tracker ) website is the default bug/issue tracking system for CPAN.
CPAN Ratings
The CPAN Ratings is a website that allows community ratings and reviews of Perl modules.
CPAN Testers
The CPAN Testers is a network of smokers who run automated tests on uploaded CPAN distributions.
CPAN Testers Matrix
The CPAN Testers Matrix is a website that provides a visual overview of the test results for a distribution on various Perls/platforms.
CPAN Testers Dependencies
The CPAN Testers Dependencies is a website that shows a chart of the test results of all dependencies for a distribution.
Bugs / Feature Requests
Please report any bugs or feature requests by email to bug-set-dynamicgroups at rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Set-DynamicGroups. You will be automatically notified of any progress on the request by the system.
Source Code
http://github.com/rwstauner/Set-DynamicGroups
git clone http://github.com/rwstauner/Set-DynamicGroups
AUTHOR
Randy Stauner <rwstauner@cpan.org>
COPYRIGHT AND LICENSE
This software is copyright (c) 2010 by Randy Stauner.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.