File Coverage

lib/Math/String/Charset.pm
Criterion Covered Total %
statement 408 457 89.2
branch 186 240 77.5
condition 73 109 66.9
subroutine 40 44 90.9
pod 27 33 81.8
total 734 883 83.1


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