File Coverage

blib/lib/Bio/MUST/Core/IdList.pm
Criterion Covered Total %
statement 110 110 100.0
branch 21 22 95.4
condition 12 16 75.0
subroutine 20 20 100.0
pod 8 8 100.0
total 171 176 97.1


line stmt bran cond sub pod time code
1             package Bio::MUST::Core::IdList;
2             # ABSTRACT: Id list for selecting specific sequences
3             $Bio::MUST::Core::IdList::VERSION = '0.212670';
4 17     17   11130 use Moose;
  17         46  
  17         145  
5 17     17   88152 use namespace::autoclean;
  17         44  
  17         200  
6              
7 17     17   1691 use autodie;
  17         45  
  17         149  
8 17     17   94237 use feature qw(say);
  17         49  
  17         1525  
9              
10             # use Smart::Comments;
11              
12 17     17   152 use Carp;
  17         39  
  17         1460  
13              
14 17     17   118 use Bio::MUST::Core::Types;
  17         52  
  17         533  
15 17     17   119 use Bio::MUST::Core::Constants qw(:files);
  17         36  
  17         3579  
16 17     17   130 use aliased 'Bio::MUST::Core::SeqId';
  17         370  
  17         124  
17 17     17   3860 use aliased 'Bio::MUST::Core::Ali';
  17         46  
  17         68  
18             with 'Bio::MUST::Core::Roles::Commentable',
19             'Bio::MUST::Core::Roles::Listable';
20              
21              
22             # public array
23             has 'ids' => (
24             traits => ['Array'],
25             is => 'ro',
26             isa => 'Bio::MUST::Core::Types::full_ids',
27             default => sub { [] },
28             coerce => 1,
29             writer => '_set_ids',
30             handles => {
31             count_ids => 'count',
32             all_ids => 'elements',
33             add_id => 'push',
34             },
35             );
36              
37              
38             # private hash for faster querying
39             has '_index_for' => (
40             traits => ['Hash'],
41             is => 'ro',
42             isa => 'HashRef[Str]',
43             init_arg => undef,
44             lazy => 1,
45             builder => '_build_index_for',
46             handles => {
47             count_indices => 'count',
48             is_listed => 'defined',
49             index_for => 'get',
50             set_index => 'set',
51             },
52             );
53              
54              
55             ## no critic (ProhibitUnusedPrivateSubroutines)
56              
57             # Note: we don't store SeqId objects in the list but dynamically build them
58             # to benefit from SeqId methods (e.g., auto-removal of first '_'). This is
59             # the most flexible approach without costing too much in CPU-time.
60              
61             sub _build_index_for {
62 15     15   41 my $self = shift;
63              
64             # build private hash from internal array
65 15         44 my $i = 0;
66 15         292 return { map { $_->full_id => $i++ } $self->all_seq_ids };
  325         8553  
67             }
68              
69             ## use critic
70              
71             after 'add_id' => sub {
72             my $self = shift;
73              
74             # check if there are indeed ids not yet in private hash
75             # Note: this might not be the case when adding ids in an empty list
76             my $n = $self->count_ids;
77             my $i = $self->count_indices;
78             return if $n == $i;
79              
80             # update private hash from internal array
81             $self->set_index(
82             map { $_->full_id => $i++ } ($self->all_seq_ids)[$i..$n-1]
83             );
84             return;
85             };
86              
87              
88             sub all_seq_ids {
89 30     30 1 77 my $self = shift;
90 30         1122 return map { SeqId->new( full_id => $_ ) } $self->all_ids;
  420         11415  
91             }
92              
93              
94              
95             sub negative_list {
96 5     5 1 18 my $self = shift;
97 5         12 my $listable = shift;
98              
99             # filter out seq ids that are in the original list
100 5         27 my @ids = map { $_->full_id } $listable->all_seq_ids;
  50         1272  
101 5         23 return $self->new( ids => [ grep { not $self->is_listed($_) } @ids ] );
  50         1718  
102             }
103              
104              
105             # IdList-based Ali factory methods
106              
107              
108             sub reordered_ali { ## no critic (RequireArgUnpacking)
109 4     4 1 37 return shift->_ali_from_list_(1, @_);
110             }
111              
112              
113              
114             sub filtered_ali { ## no critic (RequireArgUnpacking)
115 54     54 1 160 return shift->_ali_from_list_(0, @_);
116             }
117              
118              
119             sub _ali_from_list_ {
120 58     58   108 my $self = shift;
121 58         98 my $reorder = shift;
122 58         93 my $ali = shift;
123 58         104 my $lookup = shift; # optional IdList indexing the Ali
124              
125             # override passed lookup with internal lookup if available
126             # Note: this allows Stash lookups to be used transparently
127 58 100       319 $lookup = $ali->lookup if $ali->can('lookup');
128              
129             # TODO: warn for missing ids in Ali?
130              
131             # create new Ali object (extending header comment)
132             # TODO: allow custom comments
133 58 100       2314 my $new_ali = Ali->new(
134             comments => [ $ali->all_comments,
135             'built by ' . ($reorder ? 'reordered_ali' : 'filtered_ali')
136             ],
137             );
138              
139             # case 1: use lookup when available
140 58 100       149 if (defined $lookup) {
141             ### Using lookup...
142              
143             # get slot list from lookup
144             # Note: Since this list follows the list in $self it is 'reordered'.
145             # We thus sort it by ascending slot if the Ali order must be kept.
146 3         119 my @slots = $lookup->index_for($self->all_ids);
147 3 100       20 @slots = sort { $a <=> $b } @slots unless $reorder;
  7         15  
148              
149             # populate new Ali with deep copies of Seqs in slot list
150 3         92 $new_ali->add_seq( $ali->get_seq($_)->clone ) for @slots;
151             }
152              
153             # case 2: scan all seqs to find those that are listed
154             else {
155              
156             SEQ:
157 55         2002 for my $seq ($ali->all_seqs) {
158 4536 100       11464 next SEQ unless $self->is_listed($seq->full_id);
159              
160             # add Seq to new Ali honoring either IdList order...
161 67 100       193 if ($reorder) {
162 8         24 $new_ali->set_seq(
163             $self->index_for($seq->full_id), $seq->clone
164             );
165 8         26 next SEQ;
166             }
167              
168             # ...or original Ali order
169 59         190 $new_ali->add_seq($seq->clone);
170             }
171              
172             # when reordering an Ali, ensure that new Ali does not contain
173             # empty slots due to some missing ids in the original Ali
174             $new_ali->_set_seqs(
175 55 100   10   419 [ $new_ali->filter_seqs( sub { defined } ) ]
  10         361  
176             ) if $reorder;
177             }
178              
179 58         236 return $new_ali;
180             }
181              
182              
183             # I/O methods
184              
185              
186             sub load {
187 2     2 1 375 my $class = shift;
188 2         5 my $infile = shift;
189 2   100     10 my $args = shift // {}; # HashRef (should not be empty...)
190              
191 2   100     8 my $col = $args->{column} // 0;
192 2   66     13 my $sep = $args->{separator} // qr{\t}xms;
193              
194 2         12 open my $in, '<', $infile;
195              
196 2         2762 my $list = $class->new();
197              
198 2         4 my @ids;
199              
200             LINE:
201 2         1565 while (my $line = <$in>) {
202 12         24 chomp $line;
203              
204             # skip empty lines and process comment lines
205 12 100 66     88 next LINE if $line =~ $EMPTY_LINE
206             || $list->is_comment($line);
207              
208 10         61 my @fields = split $sep, $line;
209 10         62 push @ids, $fields[$col];
210             }
211              
212 2         86 $list->_set_ids( \@ids );
213              
214 2         47 return $list;
215             }
216              
217              
218              
219             sub load_lis {
220 1     1 1 95 my $class = shift;
221 1         3 my $infile = shift;
222              
223 1         7 open my $in, '<', $infile;
224              
225 1         282 my $list = $class->new();
226              
227 1         4 my $count;
228             my @ids;
229              
230             LINE:
231 1         599 while (my $line = <$in>) {
232 8         14 chomp $line;
233              
234             # skip empty lines and process comment lines
235 8 100 66     49 next LINE if $line =~ $EMPTY_LINE
236             || $list->is_comment($line);
237              
238             # read id count as first lone number if not yet defined
239 6 100 66     29 if (!defined $count && $line =~ $COUNT_LINE) {
240 1         3 $count = $line;
241 1         3 next LINE;
242             }
243              
244 5         28 push @ids, $line;
245             }
246              
247 1         39 $list->_set_ids( \@ids );
248              
249 1 50       38 carp '[BMC] Warning: id list size does not match id count in header!'
250             unless $list->count_ids == $count;
251              
252 1         41 return $list;
253             }
254              
255              
256              
257             sub store {
258 2     2 1 10 my $self = shift;
259 2         5 my $outfile = shift;
260              
261 2         19 open my $out, '>', $outfile;
262              
263 2         2530 print {$out} $self->header;
  2         46  
264 2         8 say {$out} join "\n", $self->all_ids;
  2         87  
265              
266 2         140 return;
267             }
268              
269              
270              
271             sub store_lis {
272 1     1 1 4 my $self = shift;
273 1         2 my $outfile = shift;
274              
275 1         4 open my $out, '>', $outfile;
276              
277 1         309 print {$out} $self->header;
  1         5  
278 1         5 say {$out} $self->count_ids;
  1         43  
279 1         3 say {$out} join "\n", $self->all_ids;
  1         37  
280              
281 1         43 return;
282             }
283              
284             __PACKAGE__->meta->make_immutable;
285             1;
286              
287             __END__
288              
289             =pod
290              
291             =head1 NAME
292              
293             Bio::MUST::Core::IdList - Id list for selecting specific sequences
294              
295             =head1 VERSION
296              
297             version 0.212670
298              
299             =head1 SYNOPSIS
300              
301             # TODO
302              
303             =head1 DESCRIPTION
304              
305             # TODO
306              
307             =head1 METHODS
308              
309             =head2 all_seq_ids
310              
311             =head2 negative_list
312              
313             =head2 reordered_ali
314              
315             =head2 filtered_ali
316              
317             =head2 load
318              
319             =head2 load_lis
320              
321             =head2 store
322              
323             =head2 store_lis
324              
325             =head1 AUTHOR
326              
327             Denis BAURAIN <denis.baurain@uliege.be>
328              
329             =head1 COPYRIGHT AND LICENSE
330              
331             This software is copyright (c) 2013 by University of Liege / Unit of Eukaryotic Phylogenomics / Denis BAURAIN.
332              
333             This is free software; you can redistribute it and/or modify it under
334             the same terms as the Perl 5 programming language system itself.
335              
336             =cut