code, tests, docs
[catagits/DOM-Tiny.git] / lib / DOM / Tiny / Collection.pm
CommitLineData
d6512b50 1package DOM::Tiny::Collection;
2
3use strict;
4use warnings;
5use Carp 'croak';
6use Exporter 'import';
7use List::Util;
8use Scalar::Util 'blessed';
9
10our @EXPORT_OK = ('c');
11
12sub TO_JSON { [@{shift()}] }
13
14sub c { __PACKAGE__->new(@_) }
15
16sub compact {
17 my $self = shift;
18 return $self->new(grep { defined && (ref || length) } @$self);
19}
20
21sub each {
22 my ($self, $cb) = @_;
23 return @$self unless $cb;
24 my $i = 1;
25 $_->$cb($i++) for @$self;
26 return $self;
27}
28
29sub first {
30 my ($self, $cb) = (shift, shift);
31 return $self->[0] unless $cb;
32 return List::Util::first { $_ =~ $cb } @$self if ref $cb eq 'Regexp';
33 return List::Util::first { $_->$cb(@_) } @$self;
34}
35
36sub flatten { $_[0]->new(_flatten(@{$_[0]})) }
37
38sub grep {
39 my ($self, $cb) = (shift, shift);
40 return $self->new(grep { $_ =~ $cb } @$self) if ref $cb eq 'Regexp';
41 return $self->new(grep { $_->$cb(@_) } @$self);
42}
43
44sub join {
45 join $_[1] // '', map {"$_"} @{$_[0]};
46}
47
48sub last { shift->[-1] }
49
50sub map {
51 my ($self, $cb) = (shift, shift);
52 return $self->new(map { $_->$cb(@_) } @$self);
53}
54
55sub new {
56 my $class = shift;
57 return bless [@_], ref $class || $class;
58}
59
60sub reduce {
61 my $self = shift;
62 @_ = (@_, @$self);
63 goto &List::Util::reduce;
64}
65
66sub reverse { $_[0]->new(reverse @{$_[0]}) }
67
68sub shuffle { $_[0]->new(List::Util::shuffle @{$_[0]}) }
69
70sub size { scalar @{$_[0]} }
71
72sub slice {
73 my $self = shift;
74 return $self->new(@$self[@_]);
75}
76
77sub sort {
78 my ($self, $cb) = @_;
79
80 return $self->new(sort @$self) unless $cb;
81
82 my $caller = caller;
83 no strict 'refs';
84 my @sorted = sort {
85 local (*{"${caller}::a"}, *{"${caller}::b"}) = (\$a, \$b);
86 $a->$cb($b);
87 } @$self;
88 return $self->new(@sorted);
89}
90
91sub tap {
92 my ($self, $cb) = (shift, shift);
93 $_->$cb(@_) for $self;
94 return $self;
95}
96
97sub to_array { [@{shift()}] }
98
99sub uniq {
100 my ($self, $cb) = (shift, shift);
101 my %seen;
102 return $self->new(grep { !$seen{$_->$cb(@_)}++ } @$self) if $cb;
103 return $self->new(grep { !$seen{$_}++ } @$self);
104}
105
106sub _flatten {
107 map { _ref($_) ? _flatten(@$_) : $_ } @_;
108}
109
110sub _ref { ref $_[0] eq 'ARRAY' || blessed $_[0] && $_[0]->isa(__PACKAGE__) }
111
1121;
113
114=encoding utf8
115
116=head1 NAME
117
118DOM::Tiny::Collection - Collection
119
120=head1 SYNOPSIS
121
122 use Mojo::Collection;
123
124 # Manipulate collection
125 my $collection = Mojo::Collection->new(qw(just works));
126 unshift @$collection, 'it';
127 say $collection->join("\n");
128
129 # Chain methods
130 $collection->map(sub { ucfirst })->shuffle->each(sub {
131 my ($word, $num) = @_;
132 say "$num: $word";
133 });
134
135 # Use the alternative constructor
136 use Mojo::Collection 'c';
137 c(qw(a b c))->join('/')->url_escape->say;
138
139=head1 DESCRIPTION
140
141L<Mojo::Collection> is an array-based container for collections.
142
143 # Access array directly to manipulate collection
144 my $collection = Mojo::Collection->new(1 .. 25);
145 $collection->[23] += 100;
146 say for @$collection;
147
148=head1 FUNCTIONS
149
150L<Mojo::Collection> implements the following functions, which can be imported
151individually.
152
153=head2 c
154
155 my $collection = c(1, 2, 3);
156
157Construct a new array-based L<Mojo::Collection> object.
158
159=head1 METHODS
160
161L<Mojo::Collection> implements the following methods.
162
163=head2 TO_JSON
164
165 my $array = $collection->TO_JSON;
166
167Alias for L</"to_array">.
168
169=head2 compact
170
171 my $new = $collection->compact;
172
173Create a new collection with all elements that are defined and not an empty
174string.
175
176 # "0, 1, 2, 3"
177 Mojo::Collection->new(0, 1, undef, 2, '', 3)->compact->join(', ');
178
179=head2 each
180
181 my @elements = $collection->each;
182 $collection = $collection->each(sub {...});
183
184Evaluate callback for each element in collection or return all elements as a
185list if none has been provided. The element will be the first argument passed
186to the callback and is also available as C<$_>.
187
188 # Make a numbered list
189 $collection->each(sub {
190 my ($e, $num) = @_;
191 say "$num: $e";
192 });
193
194=head2 first
195
196 my $first = $collection->first;
197 my $first = $collection->first(qr/foo/);
198 my $first = $collection->first(sub {...});
199 my $first = $collection->first($method);
200 my $first = $collection->first($method, @args);
201
202Evaluate regular expression/callback for, or call method on, each element in
203collection and return the first one that matched the regular expression, or for
204which the callback/method returned true. The element will be the first argument
205passed to the callback and is also available as C<$_>.
206
207 # Longer version
208 my $first = $collection->first(sub { $_->$method(@args) });
209
210 # Find first value that contains the word "mojo"
211 my $interesting = $collection->first(qr/mojo/i);
212
213 # Find first value that is greater than 5
214 my $greater = $collection->first(sub { $_ > 5 });
215
216=head2 flatten
217
218 my $new = $collection->flatten;
219
220Flatten nested collections/arrays recursively and create a new collection with
221all elements.
222
223 # "1, 2, 3, 4, 5, 6, 7"
224 Mojo::Collection->new(1, [2, [3, 4], 5, [6]], 7)->flatten->join(', ');
225
226=head2 grep
227
228 my $new = $collection->grep(qr/foo/);
229 my $new = $collection->grep(sub {...});
230 my $new = $collection->grep($method);
231 my $new = $collection->grep($method, @args);
232
233Evaluate regular expression/callback for, or call method on, each element in
234collection and create a new collection with all elements that matched the
235regular expression, or for which the callback/method returned true. The element
236will be the first argument passed to the callback and is also available as
237C<$_>.
238
239 # Longer version
240 my $new = $collection->grep(sub { $_->$method(@args) });
241
242 # Find all values that contain the word "mojo"
243 my $interesting = $collection->grep(qr/mojo/i);
244
245 # Find all values that are greater than 5
246 my $greater = $collection->grep(sub { $_ > 5 });
247
248=head2 join
249
250 my $stream = $collection->join;
251 my $stream = $collection->join("\n");
252
253Turn collection into string.
254
255 # Join all values with commas
256 $collection->join(', ')->say;
257
258=head2 last
259
260 my $last = $collection->last;
261
262Return the last element in collection.
263
264=head2 map
265
266 my $new = $collection->map(sub {...});
267 my $new = $collection->map($method);
268 my $new = $collection->map($method, @args);
269
270Evaluate callback for, or call method on, each element in collection and create
271a new collection from the results. The element will be the first argument
272passed to the callback and is also available as C<$_>.
273
274 # Longer version
275 my $new = $collection->map(sub { $_->$method(@args) });
276
277 # Append the word "mojo" to all values
278 my $mojoified = $collection->map(sub { $_ . 'mojo' });
279
280=head2 new
281
282 my $collection = Mojo::Collection->new(1, 2, 3);
283
284Construct a new array-based L<Mojo::Collection> object.
285
286=head2 reduce
287
288 my $result = $collection->reduce(sub {...});
289 my $result = $collection->reduce(sub {...}, $initial);
290
291Reduce elements in collection with callback, the first element will be used as
292initial value if none has been provided.
293
294 # Calculate the sum of all values
295 my $sum = $collection->reduce(sub { $a + $b });
296
297 # Count how often each value occurs in collection
298 my $hash = $collection->reduce(sub { $a->{$b}++; $a }, {});
299
300=head2 reverse
301
302 my $new = $collection->reverse;
303
304Create a new collection with all elements in reverse order.
305
306=head2 slice
307
308 my $new = $collection->slice(4 .. 7);
309
310Create a new collection with all selected elements.
311
312 # "B C E"
313 Mojo::Collection->new('A', 'B', 'C', 'D', 'E')->slice(1, 2, 4)->join(' ');
314
315=head2 shuffle
316
317 my $new = $collection->shuffle;
318
319Create a new collection with all elements in random order.
320
321=head2 size
322
323 my $size = $collection->size;
324
325Number of elements in collection.
326
327=head2 sort
328
329 my $new = $collection->sort;
330 my $new = $collection->sort(sub {...});
331
332Sort elements based on return value of callback and create a new collection
333from the results.
334
335 # Sort values case-insensitive
336 my $case_insensitive = $collection->sort(sub { uc($a) cmp uc($b) });
337
338=head2 tap
339
340 $collection = $collection->tap(sub {...});
341
342Alias for L<Mojo::Base/"tap">.
343
344=head2 to_array
345
346 my $array = $collection->to_array;
347
348Turn collection into array reference.
349
350=head2 uniq
351
352 my $new = $collection->uniq;
353 my $new = $collection->uniq(sub {...});
354 my $new = $collection->uniq($method);
355 my $new = $collection->uniq($method, @args);
356
357Create a new collection without duplicate elements, using the string
358representation of either the elements or the return value of the
359callback/method.
360
361 # Longer version
362 my $new = $collection->uniq(sub { $_->$method(@args) });
363
364 # "foo bar baz"
365 Mojo::Collection->new('foo', 'bar', 'bar', 'baz')->uniq->join(' ');
366
367 # "[[1, 2], [2, 1]]"
368 Mojo::Collection->new([1, 2], [2, 1], [3, 2])->uniq(sub{ $_->[1] })->to_array;
369
370=head1 BUGS
371
372Report any issues on the public bugtracker.
373
374=head1 AUTHOR
375
376Dan Book <dbook@cpan.org>
377
378=head1 COPYRIGHT AND LICENSE
379
380This software is Copyright (c) 2015 by Dan Book.
381
382This is free software, licensed under:
383
384 The Artistic License 2.0 (GPL Compatible)
385
386=head1 SEE ALSO
387
388L<Mojo::Collection>