File Coverage

lib/Math/String/Charset.pm
Criterion Covered Total %
statement 419 469 89.3
branch 187 240 77.9
condition 75 111 67.5
subroutine 42 46 91.3
pod 27 33 81.8
total 750 899 83.4


line stmt bran cond sub pod time code
1             #############################################################################
2             # Math/String/Charset.pm -- package which defines a charset for Math/String
3             #
4             # Copyright (C) 1999-2008 by Tels. All rights reserved.
5             #############################################################################
6              
7             package Math::String::Charset;
8 6     6   37909 use base Exporter;
  6         13  
  6         880  
9             @EXPORT_OK = qw/analyze/;
10              
11             BEGIN
12             {
13 6     6   151 *analyze = \&study;
14             }
15              
16 6     6   34 use vars qw($VERSION);
  6         19  
  6         377  
17             $VERSION = '1.17'; # Current version of this package
18             require 5.008003; # requires this Perl version or later
19              
20 6     6   31 use strict;
  6         12  
  6         201  
21 6     6   8867 use Math::BigInt;
  6         134918  
  6         42  
22              
23 6     6   81552 use vars qw/$die_on_error $CALC/;
  6         16  
  6         423  
24             $die_on_error = 1; # set to 0 to not die
25              
26 6     6   5191 use Math::String::Charset::Nested;
  6         20  
  6         422  
27 6     6   4903 use Math::String::Charset::Grouped;
  6         18  
  6         759  
28              
29             # following hash values are used:
30             # _clen : length of one character (all chars must have same len unless sep)
31             # _start : contains array of all valid start characters
32             # _ones : list of one-character strings (cross of _end and _start)
33             # _end : contains hash (for easier lookup) of all valid end characters
34             # _order : = 1 (1 = simple, 2 = nested)
35             # _type : = 0 (0 = simple, 1 = grouped, 2 = wordlist)
36             # _error : error message or ""
37             # _count : array of count of different strings with length x
38             # _sum : array of starting number for strings with length x
39             # _sum[x] = _sum[x-1]+_count[x-1]
40             # _cnt : number of elements in _count and _sum (as well as in _scnt & _ssum)
41             # _cnum : number of characters in _ones as BigInt (for speed)
42             # _minlen: minimum string length (anything shorter is invalid), default -inf
43             # _maxlen: maximum string length (anything longer is invalid), default undef
44             # _scale : optional output/input scale
45              
46             # simple ones:
47             # _sep : separator string (undef for none)
48             # _map : mapping character to number
49              
50             # See the other Charset package files for the keys the higher-order charsets use.
51              
52             my $ONE = Math::BigInt->bone();
53              
54             BEGIN
55             {
56             # this will fail if Math::BigInt is loaded with a different lib afterwards!
57 6   50 6   47 $CALC = Math::BigInt->config()->{lib} || 'Math::BigInt::Calc';
58             }
59              
60             #############################################################################
61              
62             sub new
63             {
64 248     248 1 1607 my $class = shift;
65 248   50     1677 $class = ref($class) || $class || __PACKAGE__;
66              
67 248         754 my $self = bless {}, $class;
68              
69 248         321 my $value;
70 248 50       577 if (!ref($_[0]))
71             {
72 0         0 $value = [ @_ ];
73             }
74             else
75             {
76 248         370 $value = shift;
77             }
78 248 100       1131 if (ref($value) !~ /^(ARRAY|HASH)$/)
79             {
80             # got an object, so make copy
81 2         9 foreach my $k (keys %$value)
82             {
83 30 100       66 if (ref($value->{$k}) eq 'ARRAY')
    100          
84             {
85 8         9 $self->{$k} = [ @{$value->{$k}} ];
  8         33  
86             }
87             elsif (ref($value->{$k}) eq 'HASH')
88             {
89 4         4 foreach my $j (keys %{$value->{k}})
  4         15  
90             {
91 0         0 $self->{$k}->{$j} = $value->{$k}->{$j};
92             }
93             }
94             else
95             {
96 18         30 $self->{$k} = $value->{$k};
97             }
98             }
99 2         7 return $self;
100             }
101              
102             # convert ARRAY ref into HASH ref in the same go
103 246         651 $value = $self->_check_params($value);
104              
105             # print "new $class type $self->{_type} order $self->{_order} $self->{_error}\n";
106              
107 246 100       754 if ($self->{_error} eq '')
108             {
109             # now route request for initialization to subclasses if we are in baseclass
110 227 100       470 if ($class eq 'Math::String::Charset')
111             {
112 210 100       650 return Math::String::Charset::Grouped->new($value)
113             if ($self->{_type} == 1);
114 202 50 33     611 if (($self->{_type} == 2) && ($self->{_order} == 1))
115             {
116 0         0 require Math::String::Charset::Wordlist;
117 0         0 return Math::String::Charset::Wordlist->new($value);
118             }
119 202 100       482 return Math::String::Charset::Nested->new($value)
120             if ($self->{_order} == 2);
121             }
122 211         497 $self->_strict_check($value);
123 211         710 $self->_initialize($value);
124             }
125 230 50 66     1179 die ($self->{_error}) if $die_on_error && $self->{_error} ne '';
126 230         917 $self;
127             }
128              
129             #############################################################################
130             # private, initialize self
131              
132             sub _strict_check
133             {
134             # a per class check, to be overwritten by subclasses
135 194     194   259 my $self = shift;
136 194         237 my $value = shift;
137              
138 194         521 my $class = ref($self);
139 194 50       442 return $self->{_error} = "Wrong type '$self->{_type}' for $class"
140             if $self->{_type} != 0;
141 194 50       446 return $self->{_error} = "Wrong order'$self->{_order}' for $class"
142             if $self->{_order} != 1;
143 194         552 foreach my $key (keys %$value)
144             {
145 216 50       1124 return $self->{_error} = "Illegal parameter '$key' for $class"
146             if $key !~ /^(start|minlen|maxlen|sep|scale)$/;
147             }
148             }
149              
150             sub _check_params
151             {
152             # check params
153 246     246   550 my $self = shift;
154 246         287 my $value = shift;
155              
156 246         655 $self->{_error} = ""; # no error
157 246         628 $self->{_count} = [ ];
158              
159             # convert array ref to hash
160 246 100       912 $value = { start => $value } if (ref($value) eq 'ARRAY');
161              
162             # from 1st take clen
163 246         1005 $self->{_clen} = $value->{charlen};
164 246         483 $self->{_sep} = $value->{sep};
165            
166 246 100 66     723 return $self->{_error} = "Can not have both 'sep' and 'charlen' in new()"
167             if ((exists $value->{charlen}) && (exists $value->{sep}));
168              
169 245         541 $self->{_order} = $value->{order};
170 245         447 $self->{_type} = $value->{type};
171              
172 245 100       510 $self->{_scale} = Math::BigInt->new($value->{scale})
173             if exists $value->{scale};
174              
175 245 100 100     865 return $self->{_error} = "Can not have both 'bi' and 'sets' in new()"
176             if ((exists $value->{sets}) && (exists $value->{bi}));
177            
178 244 100       567 if (!defined $self->{_type})
179             {
180 229         295 $self->{_type} = 0;
181 229 100       611 $self->{_type} = 1 if exists $value->{sets};
182             }
183            
184 244 100       597 if (!defined $self->{_order})
185             {
186 239         290 $self->{_order} = 1;
187 239 100       541 $self->{_order} = 2 if exists $value->{bi};
188             }
189              
190 244 100 100     672 return $self->{_error} = "Illegal type '$self->{_type}' used with 'bi'"
191             if ((exists $value->{bi}) && ($self->{_type} != 0));
192              
193 243 100 100     836 return $self->{_error} = "Illegal type '$self->{_type}' used with 'sets'"
194             if ((exists $value->{sets}) && ($self->{_type} == 0));
195              
196 240 100 100     1331 return $self->{_error} = "Illegal type '$self->{_type}'"
197             if (($self->{_type} < 0) || ($self->{_type} > 2));
198              
199 234 100 100     677 return $self->{_error} =
200             "Illegal combination of type '$self->{_type}' and order '$self->{_order}'"
201             if (($self->{_type} == 1) && ($self->{_order} != 1));
202              
203 233 100       688 if ($self->{_order} == 1)
204             {
205 213 100       582 return $self->{_error} =
206             "Illegal combination of order '$self->{_order}' and 'end'"
207             if defined $value->{end};
208            
209 212 50       752 return $self->{_error} =
210             "Illegal combination of order '$self->{_order}' and 'bi'"
211             if defined $value->{bi};
212             }
213              
214 232 100 66     980 return $self->{_error} = "Illegal order '$self->{_order}'"
215             if (($self->{_order} < 1) || ($self->{_order} > 2));
216              
217 229         393 $self->{_sep} = $value->{sep}; # sep char or undef
218 229 100 100     784 return $self->{_error} = "Field 'sep' must not be empty"
219             if (defined $self->{_sep} && $self->{_sep} eq '');
220              
221 228         374 $self->{_minlen} = $value->{minlen};
222 228         718 $self->{_maxlen} = $value->{maxlen};
223 228 100       1011 $self->{_minlen} = Math::BigInt->binf('-') if !defined $self->{_minlen};
224 228 100       8029 $self->{_maxlen} = Math::BigInt->binf() if !defined $self->{_maxlen};
225 228 100       6470 return $self->{_error} = 'Maxlen is smaller than minlen!'
226             if ($self->{_minlen} > $self->{_maxlen});
227              
228 227         5194 $value;
229             }
230              
231             sub _initialize
232             {
233             # init only for simple charsets, the rest is done in subclass
234 194     194   207 my $self = shift;
235 194         353 my $value = shift;
236              
237 194         8712 $self->{_start} = [ ];
238 194 50       449 $self->{_start} = [ @{$value->{start}} ] if defined $value->{start};
  194         1606  
239              
240 194 100       644 $self->{_clen} = CORE::length($self->{_start}->[0])
241             if !defined $self->{_sep};
242            
243 194         331 $self->{_ones} = $self->{_start};
244              
245             # XXX TODO: remove
246             # foreach (@{$self->{_start}}) { $self->{_end}->{$_} = 1; }
247            
248             # some more tests for validity
249 194 100       433 if (!defined $self->{_sep})
250             {
251 177         194 foreach (@{$self->{_start}})
  177         362  
252             {
253 2742 100       5805 $self->{_error} = "Illegal char '$_', length not $self->{_clen}"
254             if CORE::length($_) != $self->{_clen};
255             }
256             }
257             # initialize array of counts for len of 0..1
258 194         478 $self->{_cnt} = 1; # cached amount of class-sizes
259 194         351 $self->{_count}->[0] = 1; # '' is one string
260 194         230 $self->{_count}->[1] = Math::BigInt->new (scalar @{$self->{_ones}}); # 1
  194         771  
261              
262             # init _sum array
263 194         7277 $self->{_sum}->[0] = 0;
264 194         325 $self->{_sum}->[1] = 1;
265 194         669 $self->{_sum}->[2] = $self->{_count}->[1] + 1;
266              
267             # from _ones, make mapping name => number
268 194         29220 my $i = 1;
269 194         593 foreach (@{$self->{_ones}})
  194         455  
270             {
271 2806         6521 $self->{_map}->{$_} = $i++;
272             }
273 194         305 $self->{_cnum} = Math::BigInt->new( scalar @{$self->{_ones}} );
  194         919  
274              
275             # _end contains entries for all valid end characters, and since these are the
276             # same than in _map, we can reuse _map to save memory and construction time
277              
278 194         5897 $self->{_end} = $self->{_map};
279              
280 194 50 33     682 return $self->{_error} = "Empty charset!"
281             if ($self->{_cnum}->is_zero() && $self->{_minlen} > 0);
282              
283 194         2719 $self;
284             }
285              
286             sub scale
287             {
288 5     5 1 98 my $self = shift;
289              
290 5 100       33 $self->{_scale} = Math::BigInt->new($_[0]) if @_ > 0;
291 5         79 $self->{_scale};
292             }
293              
294             sub zero
295             {
296             # return the string representing zero. If no minlen is defined, this is
297             # simple '', otherwise the first string of the first class after minlen which
298             # is not empty
299 0     0 0 0 my $self = shift;
300              
301 0 0       0 return $self->{_zero} if defined $self->{_zero}; # already known?
302              
303 0 0       0 return '' if $self->{_minlen} > 0;
304 0         0 my $i = $self->{_minlen};
305 0         0 while ($self->class($i) == 0) { $i++; }
  0         0  
306 0         0 $self->{_minlen} = $i; # adjust minlen
307 0         0 $self->{_zero} = $self->first($i);
308 0         0 $self->{_zero};
309             }
310              
311             sub one
312             {
313             # return the string representing one. If no minlen is defined, this is
314             # simple the first string with length(1), otherwise the first string of the
315             # first class after minlen which is not empty
316 0     0 0 0 my $self = shift;
317              
318 0 0       0 return '' if $self->{_minlen} > 0;
319 0         0 my $i = $self->{_minlen};
320 0         0 while ($self->class($i) == 0) { $i++; }
  0         0  
321 0         0 $self->{_minlen} = $i; # adjust minlen
322 0         0 $self->first($i)->next();
323             }
324              
325             sub copy
326             {
327             # for speed reasons, do not make a copy of a charset, but share it instead
328 3     3 1 8 my ($c,$x);
329 3 50       12 if (@_ > 1)
330             {
331             # if two arguments, the first one is the class to "swallow" subclasses
332 0         0 ($c,$x) = @_;
333             }
334             else
335             {
336 3         7 $x = shift;
337 3         7 $c = ref($x);
338             }
339 3 50       9 return unless ref($x); # only for objects
340              
341 3         6 my $self = {}; bless $self,$c;
  3         9  
342 3         24 foreach my $k (keys %$x)
343             {
344 53 50       393 if (ref($x->{$k}) eq 'SCALAR')
    100          
    100          
    100          
345             {
346 0         0 $self->{$k} = \${$x->{$k}};
  0         0  
347             }
348             elsif (ref($x->{$k}) eq 'ARRAY')
349             {
350 13         14 $self->{$k} = [ @{$x->{$k}} ];
  13         67  
351             }
352             elsif (ref($x->{$k}) eq 'HASH')
353             {
354             # only one level deep!
355 8         10 foreach my $h (keys %{$x->{$k}})
  8         24  
356             {
357 78         135 $self->{$k}->{$h} = $x->{$k}->{$h};
358             }
359             }
360             elsif (ref($x->{$k}))
361             {
362 14         19 my $c = ref($x->{$k});
363 14         40 $self->{$k} = $c->new($x->{$k}); # no copy() due to deep rec
364             }
365             else
366             {
367             # simple scalar w/o reference
368 18         35 $self->{$k} = $x->{$k};
369             }
370             }
371 3         64 $self;
372             }
373              
374             sub count
375             {
376             # Return count of all possible strings described by in charset as positive
377             # bigint. Returns 'inf' if no maxlen is defined, because there should be no
378             # upper bound on how many strings are possible.
379             # if maxlen is defined, forces a calculation of all possible class() values
380             # and may therefore be slow on the first call, also caches possible lot's of
381             # values.
382 0     0 1 0 my $self = shift;
383 0         0 my $count = Math::BigInt->bzero();
384              
385 0 0       0 return $count->binf() if $self->{_maxlen}->is_inf();
386              
387 0         0 for (my $i = 0; $i < $self->{_maxlen}; $i++)
388             {
389 0         0 $count += $self->class($i);
390             }
391 0         0 $count;
392             }
393              
394             sub dump
395             {
396 3     3 0 4 my $self = shift;
397 3   50     7 my $indend = shift || '';
398            
399 3         4 my $txt = "type SIMPLE:\n";
400 3         5 $txt .= $indend . "start: " . join(' ',@{$self->{_start}}) . "\n";
  3         12  
401 3         4 my $e = $self->{_end};
402 3         28 $txt .= $indend . "end : " . join(' ', sort { $e->{$a} <=> $e->{$b} } keys %$e) . "\n";
  126         142  
403 3         6 $txt .= $indend . "ones : " . join(' ',@{$self->{_ones}}) . "\n";
  3         12  
404 3         12 $txt;
405             }
406              
407             sub error
408             {
409 48     48 1 1740 my $self = shift;
410            
411 48         230 $self->{_error};
412             }
413              
414             sub order
415             {
416             # return charset's order/class
417 3     3 1 6 my $self = shift;
418 3         12 $self->{_order};
419             }
420              
421             sub type
422             {
423             # return charset's type
424 2     2 1 3 my $self = shift;
425 2         9 $self->{_type};
426             }
427              
428             sub charlen
429             {
430             # return charset's length of one character
431 44     44 1 50 my $self = shift;
432 44         502 $self->{_clen};
433             }
434              
435             sub length
436             {
437             # return number of characters in charset
438 177     177 1 289 my $self = shift;
439              
440 177         166 scalar @{$self->{_ones}};
  177         743  
441             }
442              
443             sub _calc
444             {
445             # given count of len 1..x, calculate count for y (y > x) and all between
446             # x and y
447 56     56   97 my $self = shift;
448 56 50 50     181 my $max = shift || 1; $max = 1 if $max < 1;
  56         116  
449 56 50       136 return if $max <= $self->{_cnt};
450              
451 56         78 my $i = $self->{_cnt}; # last defined element
452 56         96 my $last = $self->{_count}->[$i];
453 56         69 my $size = Math::BigInt->new ( scalar @{$self->{_ones}} );
  56         295  
454 56         1914 while ($i <= $max)
455             {
456 113         342 $last = $last * $size;
457 113         23223 $self->{_count}->[$i+1] = $last;
458 113         936 $self->{_sum}->[$i+1] = $self->{_sum}->[$i] + $self->{_count}->[$i];
459 113         19143 $i++;
460             }
461 56         390 $self->{_cnt} = $i-1; # store new cache size
462             }
463              
464             sub class
465             {
466             # return number of all combinations with a certain length
467 38     38 1 269 my $self = shift;
468 38 50       53 my $len = shift; $len = 0 if !defined $len;
  38         92  
469 38         45 $len = abs(int($len));
470              
471 38 50 33     122 return 0 if $len < $self->{_minlen} || $len > $self->{_maxlen};
472              
473             # print "$len $self->{_minlen}\n";
474 38 50       6404 $len -= $self->{_minlen} if $self->{_minlen} > 0; # correct
475             # not known yet, so calculate and cache
476 38 100       4541 $self->_calc($len) if $self->{_cnt} < $len;
477 38         226 $self->{_count}->[$len];
478             }
479              
480             sub lowest
481             {
482             # return number of first string with $length characters
483             # equivalent to $charset->first($length)->num2str();
484 4     4 1 7 my $self = shift;
485 4   50     13 my $len = abs(int(shift || 1));
486            
487             # not known yet, so calculate and cache
488 4 50       11 $self->_calc($len) if $self->{_cnt} < $len;
489 4         30 $self->{_sum}->[$len];
490             }
491              
492             sub highest
493             {
494             # return number of first string with $length characters
495             # equivalent to $charset->first($length)->num2str();
496 4     4 1 727 my $self = shift;
497 4   50     11 my $len = abs(int(shift || 1));
498            
499 4         3 $len++;
500             # not known yet, so calculate and cache
501 4 100       13 $self->_calc($len) if $self->{_cnt} < $len;
502 4         12 $self->{_sum}->[$len]-1;
503             }
504              
505             sub norm
506             {
507             # normalize a string by removing separator char at front/end
508 115     115 1 273 my $self = shift;
509 115         168 my $str = shift;
510              
511 115 100       603 return $str if !defined $self->{_sep};
512              
513 11         101 $str =~ s/$self->{_sep}\z//; # remove at end
514 11         50 $str =~ s/^$self->{_sep}//; # remove at front
515 11         50 $str;
516             }
517              
518             sub is_valid
519             {
520             # check wether a string conforms to the given charset set
521 136     136 1 208 my $self = shift;
522 136         197 my $str = shift;
523              
524             # print "$str\n";
525 136 100       337 return 0 if !defined $str;
526 135 100       436 if ($str eq '')
527             {
528 12 100       49 return $self->{_minlen} <= 0 ? 1 : 0;
529             }
530              
531 123         374 my $int = Math::BigInt->bzero();
532 123         2749 my @chars;
533 123 100       430 if (defined $self->{_sep})
534             {
535 8         69 @chars = split /$self->{_sep}/,$str;
536 8 100       26 shift @chars if $chars[0] eq '';
537 8 50       27 pop @chars if $chars[-1] eq $self->{_sep};
538             }
539             else
540             {
541 115         156 my $i = 0; my $len = CORE::length($str); my $clen = $self->{_clen};
  115         1165  
  115         259  
542 115         252 while ($i < $len)
543             {
544 233         541 push @chars, substr($str,$i,$clen); $i += $clen;
  233         558  
545             }
546             }
547             # length okay?
548 123 100 100     442 return 0 if scalar @chars < $self->{_minlen} || scalar @chars > $self->{_maxlen};
549              
550             # valid start char?
551 121         36115 my $map = $self->{_map};
552             # XXX TODO: remove
553             # return 0 unless exists $map->{$chars[0]};
554 121         220 foreach (@chars)
555             {
556 235 100       1463 return 0 unless exists $map->{$_};
557             }
558 110         557 1;
559             }
560              
561             sub minlen
562             {
563 5     5 1 12 my $self = shift;
564              
565 5         23 $self->{_minlen};
566             }
567              
568             sub maxlen
569             {
570 2     2 1 5 my $self = shift;
571              
572 2         9 $self->{_maxlen};
573             }
574              
575             sub start
576             {
577             # this returns all the starting characters in a list, or in case of a simple
578             # charset, simple the charset
579             # in scalar context, returns length of starting set, for simple charsets this
580             # equals the length
581 11     11 1 18 my $self = shift;
582              
583 11 50       48 wantarray ? @{$self->{_start}} : scalar @{$self->{_start}};
  11         97  
  0         0  
584             }
585            
586             sub end
587             {
588             # this returns all the end characters in a list, or in case of a simple
589             # charset, simple the charset
590             # in scalar context, returns length of end set, for simple charsets this
591             # equals the length
592 3     3 1 5 my $self = shift;
593              
594 3 50       8 wantarray ? sort keys %{$self->{_end}} : scalar keys %{$self->{_end}};
  0         0  
  3         17  
595             }
596              
597             sub ones
598             {
599             # this returns all the one-char strings (in scalar context the count of them)
600 2     2 1 4 my $self = shift;
601              
602 2 50       5 wantarray ? @{$self->{_ones}} : scalar @{$self->{_ones}};
  2         13  
  0         0  
603             }
604              
605             sub num2str
606             {
607             # convert Math::BigInt/Math::String to string
608             # in list context return string and stringlen
609 106     106 0 247 my ($self,$x) = @_;
610              
611 106 100       297 $x = Math::BigInt->new($x) unless ref $x;
612              
613 106 50       822 return undef if $x->{sign} !~ /^[+-]$/;
614              
615 106         165 my $j = $self->{_cnum}; # nr of chars
616              
617 106 100       429 if ($self->{_minlen} <= $ONE)
618             {
619 105 100       2322 if ($x->is_zero())
620             {
621 7 50       118 return wantarray ? ('',0) : '';
622             }
623              
624             # single character?
625 98 100 66     1301 if ($x <= $j && $self->{_minlen} <= $ONE)
626             {
627 39         1828 my $c = $self->{_ones}->[$x->numify() - 1];
628 39 50       652 return wantarray ? ($c,1) : $c; # string len == 1
629             }
630             }
631              
632 60         2124 my $digits = $self->chars($x); my $d = $digits;
  60         81  
633              
634             # now treat the string as it were a zero-padded string of length $digits
635              
636             # length is not right (too short or too long)
637 60 100 66     182 if ($digits < $self->{_minlen} || $digits > $self->{_maxlen})
638             {
639 1 50       7 return wantarray ? (undef,0) : undef;
640             }
641              
642 59         11296 my $es=""; # result
643             # copy input, make positive number, correct to $digits and cater for 0
644 59         190 my $y = Math::BigInt->new($x); $y->babs();
  59         1698  
645             #print "fac $j y: $y new: ";
646 59         506 $y -= $self->{_sum}->[$digits];
647              
648             #print "y: $y\n";
649 59 100       6067 my $mod = 0; my $s = $self->{_sep}; $s = '' if !defined $s;
  59         351  
  59         138  
650 59         159 while (!$y->is_zero())
651             {
652             #print "bfore: y/fac: $y / $j \n";
653 84         1120 ($y,$mod) = $y->bdiv($j);
654 84         13631 $es = $self->{_ones}->[$mod] . $s . $es;
655             #print "after: div: $y rem: $mod \n";
656 84         1555 $digits --; # one digit done
657             }
658             # padd the remaining digits with the zero-symbol
659 59 100       903 $es = ($self->{_ones}->[0].$s) x $digits . $es if ($digits > 0);
660 59         478 $es =~ s/$s\z//; # strip last sep 'char'
661 59 50       441 wantarray ? ($es,$d) : $es;
662             }
663              
664             sub str2num
665             {
666             # convert Math::String to Math::BigInt (does not take scale into account)
667 122     122 0 560 my ($self,$str) = @_;
668              
669 122         340 my $int = Math::BigInt->bzero();
670 122         2420 my $i = CORE::length($str);
671              
672 122 100       309 return $int if $i == 0;
673 112         204 my $map = $self->{_map};
674 112   100     288 my $clen = $self->{_clen} || 0; # len of one char
675              
676 112 100       341 if ($i == $clen)
677             {
678 41         151 $int->{value} = $CALC->_new( $map->{$str} );
679 41         301 return $int;
680             }
681              
682 71         120 my $cnum = $self->{_cnum}; my $j;
  71         86  
683 71 50       148 if (ref($cnum))
684             {
685 71         118 $j = $cnum->{value};
686             }
687             else
688             {
689 0         0 $j = $CALC->_new($cnum);
690             }
691              
692 71 100       158 if (!defined $self->{_sep})
693             {
694             # first step (mul = 1):
695             # 0 + 1 * str => str
696 60         82 $i -= $clen;
697 60         299 $int->{value} = $CALC->_new( $map->{substr($str,$i,$clen)});
698 60         506 my $mul = $CALC->_copy($j);
699              
700             # other steps:
701 60         272 $i -= $clen;
702             # while ($i >= 0)
703 60         183 while ($i > 0)
704             {
705 21         80 $CALC->_add( $int->{value}, $CALC->_mul( $CALC->_copy($mul), $CALC->_new( $map->{substr($str,$i,$clen)} )));
706 21         770 $CALC->_mul( $mul , $j);
707 21         382 $i -= $clen;
708             # print "s2n $int j: $j i: $i m: $mul c: ",
709             # substr($str,$i+$clen,$clen),"\n";
710             }
711             # last step (no need to update $i or preserving/updating $mul)
712 60         223 $CALC->_add( $int->{value}, $CALC->_mul( $CALC->_copy($mul), $CALC->_new( $map->{substr($str,$i,$clen)} )));
713             }
714             else
715             {
716             # with sep char
717 11         38 my $mul = $CALC->_one();
718 11         245 my @chars = split /$self->{_sep}/, $str;
719 11 100       100 shift @chars if $chars[0] eq ''; # strip leading sep
720 11         24 foreach (reverse @chars)
721             {
722 28         186 $CALC->_add( $int->{value}, $CALC->_mul( $CALC->_copy($mul), $CALC->_new( $map->{$_} )));
723 28         1108 $CALC->_mul( $mul , $j);
724             }
725             }
726              
727 71         2483 $int;
728             }
729              
730             sub char
731             {
732             # return nth char from charset (see also map())
733 42     42 1 53 my $self = shift;
734 42   100     139 my $char = shift || 0;
735            
736 42 50       281 return undef if $char > scalar @{$self->{_ones}}; # dont create spurios elems
  42         128  
737 42         874 $self->{_ones}->[$char];
738             }
739              
740             sub map
741             {
742             # map char to number (see also char())
743 125     125 1 555 my ($self,$char) = @_;
744              
745 125 100 66     669 return undef unless defined $char && exists $self->{_map}->{$char};
746 117         427 $self->{_map}->{$char} - 1;
747             }
748              
749             sub chars
750             {
751             # return number of characters in output string
752 82     82 1 1188 my ($self,$x) = @_;
753            
754 82 50 66     208 return 0 if $x->is_zero() || $x->is_nan() || $x->is_inf();
      66        
755 81         2896 my $i = 1;
756 81         302 my $y = $x->as_number()->babs();
757              
758 81         2097 while ($y >= $self->{_sum}->[$i])
759             {
760 202 100       14639 $self->_calc($i) if $self->{_cnt} < $i;
761 202         1956 $i++;
762             }
763 81         2542 --$i; # correct for last ++
764             }
765              
766             sub first
767             {
768 18     18 1 85 my $self = shift;
769 18   100     53 my $count = abs(shift || 0);
770              
771 18 50       57 return if $count < $self->{_minlen};
772 18 50 33     2049 return if defined $self->{_maxlen} && $count > $self->{_maxlen};
773 18 100       1775 return '' if $count == 0;
774              
775 14   100     89 my $t = ($self->{_sep}||'') . $self->{_ones}->[0];
776 14         807 my $es = $t x $count;
777 14 100       76 $es =~ s/^$self->{_sep}// if defined $self->{_sep};
778 14         69 $es;
779             }
780              
781             sub last
782             {
783 18     18 1 31 my $self = shift;
784 18   100     58 my $count = abs(shift || 0);
785              
786 18 50       79 return if $count < $self->{_minlen};
787 18 50 33     2162 return if defined $self->{_maxlen} && $count > $self->{_maxlen};
788 18 100       1714 return '' if $count == 0;
789              
790 14   100     83 my $t = ($self->{_sep}||'') . $self->{_ones}->[-1];
791 14         25 my $es = $t x $count;
792 14 100       70 $es =~ s/^$self->{_sep}// if defined $self->{_sep};
793 14         70 $es;
794             }
795              
796             sub next
797             {
798             # take one string, and return the next string following it (without
799             # converting the string to it's number form first for speed reasons)
800 88     88 1 133 my ($self,$str) = @_;
801              
802 88 100       229 if ($str->{_cache} eq '') # 0 => 1
803             {
804 3         10 my $min = $self->{_minlen};
805             #$str->{_cache} = $self->first($min) and return if $min->is_positive();
806 3         8 $str->{_cache} = $self->{_ones}->[0];
807 3         8 return;
808             }
809              
810             # only the rightmost digit is adjusted. If this overflows, we simple
811             # invalidate the cache. The time saved by updating the cache would be to
812             # small to be of use, especially since updating the cache takes more time
813             # then. Also, if the cached isn't used later, we would have spent the
814             # update-time in vain.
815              
816             # simple charsets
817 85         91 my $char;
818 85         331 my $clen = $self->{_clen};
819 85         258 my $s = \$str->{_cache}; # ref to cache contents
820 85         143 my $sep = $self->{_sep};
821 85 100       157 if (defined $sep)
822             {
823             # split last part
824 28         151 $$s =~ /.*$sep(.*?)\z/; $char = $1;
  28         53  
825 28 100       98 $char = $$s unless $$s =~ /$sep/;
826             }
827             else
828             {
829             # extract last char
830 57         112 $char = substr($$s,-$clen,$clen);
831             }
832 85         174 my $old = $char; # for seperator replacement
833 85         201 $char = $self->{_map}->{$char}; # map is +1 by default
834 85 100       237 $char -=2 if $str->{sign} eq '-';
835 85 100 66     213 if ((!defined $char) || ($char >= @{$self->{_start}}) || ($char < 0))
  85   100     491  
836             {
837             # overflow
838 8         17 $str->{_cache} = undef; # invalidate cache
839 8         24 return;
840             }
841 77         139 $char = $self->{_start}->[$char]; # num 2 char
842 77 100       148 if (defined $sep)
843             {
844             # split last part and replace
845 22         426 $$s =~ s/$old\z/$char/;
846             }
847             else
848             {
849             # replace the last char
850 55         256 substr($$s,-$clen,$clen) = $char;
851             }
852             }
853              
854             sub prev
855             {
856 76     76 1 225 my ($self,$str) = @_;
857              
858 76 100       174 if ($str->{_cache} eq '') # 0 => -1
859             {
860 2         6 my $min = $self->{_minlen};
861 2 50 0     10 $str->{_cache} = undef, and return if $min->is_positive(); # >= 0;
862 2         22 $str->{_cache} = $self->{_ones}->[0];
863 2         7 return;
864             }
865              
866             # simple charsets
867 74         72 my $char;
868 74         93 my $clen = $self->{_clen};
869 74         124 my $s = \$str->{_cache};
870 74         111 my $sep = $self->{_sep};
871 74 100       266 if (defined $sep)
872             {
873             # split last part and replace
874 46         475 $$s =~ /.*$sep(.*?)\z/; $char = $1;
  46         83  
875 46 100       153 $char = $$s unless $$s =~ /$sep/;
876             }
877             else
878             {
879             # extract last char and replace
880 28         56 $char = substr($$s,-$clen,$clen);
881             }
882              
883 74         91 my $old = $char; # for seperator replacement
884 74 50 33     347 if ((defined $char) && (exists $self->{_map}->{$char}))
885             {
886 74         124 $char = $self->{_map}->{$char} - 1;
887 74 100       173 $char += $str->{sign} eq '-' ? 1 : -1;
888 74 100 100     181 if ($char < 0 || $char >= @{$self->{_start}})
  66         245  
889             {
890 15         29 $str->{_cache} = undef; # invalidate cache
891 15         47 return; # under or overflow
892             }
893             }
894             else
895             {
896 0         0 $str->{_cache} = undef; # invalidate cache
897 0         0 return; # underflow if char not defined
898             }
899 59         103 $char = $self->{_start}->[$char]; # map num back to char
900 59 100       232 if (defined $self->{_sep})
901             {
902 33         540 $$s =~ s/$old\z/$char/; # split last part and replace
903             }
904             else
905             {
906 26         108 substr($$s,-$clen,$clen) = $char; # simple replace
907             }
908             }
909              
910             sub merge
911             {
912             # merge yourself with another simple charset
913 0     0 0 0 my $self = shift;
914 0         0 my $other = shift;
915              
916             # TODO
917 0         0 $self;
918             }
919              
920             ###############################################################################
921              
922             sub study
923             {
924             # study a list of words and return a hash describing them
925             # study ( { order => $depth, words = \@words, sep => ''}, charlen => 1,
926             # hist => 1, );
927              
928 4     4 1 2195 my $arg;
929 4 50       13 if (ref $_[0] eq 'HASH')
930             {
931 0         0 $arg = shift;
932             }
933             else
934             {
935 4         18 $arg = { @_ };
936             }
937              
938 4   50     22 my $depth = abs($arg->{order} || $arg->{depth} || 1);
939 4   50     14 my $words = $arg->{words} || [];
940 4         8 my $sep = $arg->{sep};
941 4   50     21 my $charlen = $arg->{charlen} || 1;
942 4   50     17 my $cut = $arg->{cut} || 0;
943 4   50     15 my $hist = $arg->{hist} || 0;
944              
945 4 50 33     21 die "depth of study must be between 1..2" if ($depth < 1 || $depth > 2);
946 4         6 my $starts = {}; # word starts
947 4         8 my $ends = {}; # word ends
948 4         6 my $chars = {}; # for depth 1
949 4         7 my $bi = { }; my ($l,@chars,$x,$y,$word,$i);
  4         5  
950 4         9 foreach $word (@$words)
951             {
952             # count starting chars and ending chars
953 18         35 $starts->{substr($word,0,$charlen)} ++;
954 18         28 $ends->{substr($word,-$charlen,$charlen)} ++;
955 18         24 $l = CORE::length($word) / $charlen;
956 18 50       38 next if (int($l) != $l); # illegal word
957 18 50       33 if ($depth == 1)
958             {
959 0         0 for (my $i = 0; $i < $l; $i += $charlen)
960             {
961 0         0 $chars->{substr($word,$i,$charlen)} ++;
962             }
963 0         0 next; # next word
964             }
965 18         20 $l = $l - $depth + 1;
966 18         188 for ($i = 0; $i < $l; $i += $charlen)
967             {
968 90         395 $x = substr($word,$i,$charlen); $y = substr($word,$i+$charlen,$charlen);
  90         111  
969 90         592 $bi->{$x}->{$y} ++;
970             }
971             }
972 4         155 my $args = {};
973 4         9 my (@end,@start);
974 4         1788 foreach (sort { $starts->{$b} <=> $starts->{$a} } keys %$starts)
  10         362  
975             {
976 11         35 push @start, $_;
977             }
978 4         206 $args->{start} = \@start;
979 4         14 foreach (sort { $ends->{$b} <=> $ends->{$a} } keys %$ends)
  10         185  
980             {
981 11         2382 push @end, $_;
982             }
983 4         13 $args->{end} = \@end;
984 4 50       9 if ($depth > 1)
985             {
986 4         7 my @sorted;
987 4         15 foreach my $c (keys %$bi)
988             {
989 35         43 my $bc = $bi->{$c};
990 20 50       94 $args->{bi}->{$c} = [
991 35         146 sort { $bc->{$b} <=> $bc->{$a} or $a cmp $b } keys %$bc
992             ];
993             }
994             }
995             else
996             {
997 0         0 my @chars = ();
998 0         0 foreach (sort { $chars->{$b} <=> $chars->{$a} } keys %$chars)
  0         0  
999             {
1000 0         0 push @chars, $_;
1001             }
1002 0         0 $args->{chars} = \@chars;
1003             }
1004 4 50       14 if ($hist != 0)
1005             {
1006             # return histogram
1007 0 0       0 if ($depth > 1)
1008             {
1009 0         0 $args->{hist} = $bi;
1010             }
1011             else
1012             {
1013 0         0 $args->{hist} = $chars;
1014             }
1015             }
1016 4         52 $args;
1017             }
1018              
1019             __END__