File Coverage

blib/lib/List/Search.pm
Criterion Covered Total %
statement 39 39 100.0
branch 10 10 100.0
condition n/a
subroutine 11 11 100.0
pod 6 6 100.0
total 66 66 100.0


line stmt bran cond sub pod time code
1 4     4   155020 use strict;
  4         9  
  4         159  
2 4     4   19 use warnings;
  4         8  
  4         371  
3              
4             package List::Search;
5              
6             our $VERSION = '0.3';
7              
8 4     4   22 use vars qw(@ISA @EXPORT_OK);
  4         9  
  4         3296  
9             @ISA = qw(Exporter);
10             @EXPORT_OK = qw(
11             list_search nlist_search custom_list_search
12             list_contains nlist_contains custom_list_contains
13             );
14              
15             =head1 NAME
16              
17             List::Search - fast searching of sorted lists
18              
19             =head1 SYNOPSIS
20              
21             use List::Search qw( list_search nlist_search custom_list_search );
22              
23             # Create a list to search
24             my @list = sort qw( bravo charlie delta );
25              
26             # Search for a value, returns the index of first match
27             print list_search( 'alpha', \@list ); # 0
28             print list_search( 'charlie', \@list ); # 1
29             print list_search( 'zebra', \@list ); # -1
30              
31             # Search numerically
32             my @numbers = sort { $a <=> $b } ( 10, 20, 100, 200, );
33             print nlist_search( 20, \@numbers ); # 2
34              
35             # Search using some other comparison
36             my $cmp_code = sub { lc( $_[0] ) cmp lc( $_[1] ) };
37             my @custom_list = sort { $cmp_code->( $a, $b ) } qw( FOO bar BAZ bundy );
38             print list_search_generic( $cmp_code, 'foo', \@custom_list );
39              
40             =head1 DESCRIPTION
41              
42             This module lets you quickly search a sorted list. It will return the index of
43             the first entry that matches, or if there is no exact matches then the first
44             entry that is greater than the search key.
45              
46             For example in the list C searching for
47             C will return C<1> as C<$list[1] eq 'dave'>. Searching for C
48             will also return C<1> as C is the first entry that is greater than
49             C.
50              
51             If there are none of the entries match then C<-1> is returned. You can either
52             check for this or use it as an index to get the last values in the list.
53             Whichever approach you choose will depend on what you are trying to do.
54              
55             The actual searching is done using a binary search which is very fast.
56              
57             =head1 METHODS
58              
59             =head2 list_search
60              
61             my $idx = list_search( $key, \@sorted_list );
62              
63             Searches the list using C as the comparison operator. Returns the index
64             of the first entry that is equal to or greater than C<$key>. If there is no
65             match then returns C<-1>.
66              
67             =cut
68              
69             sub list_search {
70 13     13 1 2169 my ( $key, $array_ref ) = @_;
71 13         37 return custom_list_search( \&_alpha_sort, $key, $array_ref );
72             }
73              
74             =head2 nlist_search
75              
76             my $idx = nlist_search( $key, \@sorted_list );
77              
78             Searches the list using C=E> as the comparison operator. Returns the
79             index of the first entry that is equal to or greater than C<$key>. If there is
80             no match then returns C<-1>.
81              
82             =cut
83              
84             sub nlist_search {
85 33     33 1 12342 my ( $key, $array_ref ) = @_;
86 33         80 return custom_list_search( \&_numeric_sort, $key, $array_ref );
87             }
88              
89             =head2 custom_list_search
90              
91             WARNING: I intend to change this method so that it accepts a block in the same
92             way that C does. This means that you will be able to use $a and $b as
93             expected. Until then take care with this one : )
94              
95             my $cmp_sub = sub { $_[0] cmp $_[1] };
96             my $idx = custom_list_search( $cmp_sub, $key, \@sorted_list );
97              
98             Searches the list using the subroutine to compare the values. Returns the
99             index of the first entry that is equal to or greater than C<$key>. If there is
100             no match then returns C<-1>.
101              
102             NOTE - the list must have been sorted using the same comparison, ie:
103              
104             my @sorted_list = sort { $cmp_sub->( $a, $b ) } @list;
105              
106             =cut
107              
108             sub custom_list_search {
109 83     83 1 2223 my ( $cmp_code, $key, $array_ref ) = @_;
110              
111 83         121 my $max_index = scalar(@$array_ref) - 1;
112 83 100       466 return -1 if $max_index < 0;
113              
114 82         129 my $low = 0;
115 82         88 my $mid = undef;
116 82         86 my $high = $max_index;
117              
118 82         165 while ( $low <= $high ) {
119 276         427 $mid = int( $low + ( ( $high - $low ) / 2 ) );
120 276         353 my $mid_val = $array_ref->[$mid];
121              
122 276         442 my $cmp_result = $cmp_code->( $key, $mid_val );
123              
124 276 100       611 if ( $cmp_result > 0 ) {
125 142         326 $low = $mid + 1;
126             }
127             else {
128 134         891 $high = $mid - 1;
129             }
130             }
131              
132             # Look at the values here and work out what to return.
133              
134             # Perhaps there are no matches in the array
135 82 100       295 return -1 if $cmp_code->( $key, $array_ref->[-1] ) == 1;
136              
137             # Perhaps $mid is just before the best match
138 79 100       173 return $mid + 1 if $cmp_code->( $key, $array_ref->[$mid] ) == 1;
139              
140             # $mid is correct
141 59         176 return $mid;
142             }
143              
144             =head2 list_contains, nlist_contains, custom_list_contains
145              
146             my $bool = list_contains( $key, \@sorted_list ); # string sort
147             my $bool = nlist_contains( $key, \@sorted_list ); # number sort
148              
149             my $bool = custom_list_contains( $cmp_sub_ref, $key, \@sorted_list );
150              
151             Returns true if C<$key> was found in the list, false otherwise.
152              
153             =cut
154              
155             sub list_contains {
156 10     10 1 2211 my ( $key, $array_ref ) = @_;
157 10         29 return custom_list_contains( \&_alpha_sort, $key, $array_ref );
158             }
159              
160             sub nlist_contains {
161 22     22 1 6259 my ( $key, $array_ref ) = @_;
162 22         65 return custom_list_contains( \&_numeric_sort, $key, $array_ref );
163             }
164              
165             sub custom_list_contains {
166 32     32 1 48 my ( $code, $key, $array_ref ) = @_;
167              
168             # Get the index of the key
169 32         71 my $idx = custom_list_search( $code, $key, $array_ref );
170              
171             # Compare the key to the index
172 32         58 my $cmp_result = $code->( $key, $array_ref->[$idx] );
173              
174 32 100       343 return $cmp_result == 0 # is there a difference?
175             ? 1 # there was no difference, so $key is in array
176             : 0; # $key is not in array
177             }
178              
179 114     114   390 sub _alpha_sort { $_[0] cmp $_[1]; }
180 332     332   703 sub _numeric_sort { $_[0] <=> $_[1]; }
181              
182             =head1 AUTHOR
183              
184             Edmund von der Burg C<>
185              
186             L
187              
188             =head1 SEE ALSO
189              
190             For fast sorting of lists try L. For matching on not just the start
191             of the item try L. For matching in an unsorted
192             list try L.
193              
194             =head1 CREDITS
195              
196             Sean Woolcock submitted several bug fixes which were included in 0.3
197              
198             =head1 SVN ACCESS
199              
200             You can access the latest (possibly unstable) code here:
201              
202             L
203              
204             =head1 COPYRIGHT
205              
206             Copyright (C) 2007 Edmund von der Burg. All rights reserved.
207              
208             This module is free software; you can redistribute it and/or modify it under
209             the same terms as Perl itself. If it breaks you get to keep both pieces.
210              
211             THERE IS NO WARRANTY.
212              
213             =cut
214              
215             1;