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