File Coverage

blib/lib/Silly/StringMaths.pm
Criterion Covered Total %
statement 63 65 96.9
branch 9 12 75.0
condition 3 3 100.0
subroutine 12 12 100.0
pod 9 9 100.0
total 96 101 95.0


line stmt bran cond sub pod time code
1             package Silly::StringMaths;
2              
3 1     1   568 use strict;
  1         2  
  1         25  
4 1     1   4 use Exporter;
  1         1  
  1         30  
5 1     1   4 use vars qw($VERSION @ISA @EXPORT_OK @EXPORT);
  1         4  
  1         944  
6             @ISA=qw(Exporter);
7             @EXPORT=();
8             @EXPORT_OK=qw(add subtract multiply divide exponentiate
9             normalise sign negative invert);
10              
11             $VERSION = '0.13';
12              
13             =head1 NAME
14              
15             Silly::StringMaths - Perl extension for doing maths with strings
16              
17             =head1 SYNOPSIS
18              
19             use Silly::StringMaths qw(add subtract multiply divide exponentiate);
20              
21             # Add two positive numbers - returns ABFOOR
22             print add("FOO", "BAR");
23              
24             # Add a generally positive number and a negative number
25             # - returns ot
26             print add("FNoRD", "yncft");
27              
28             # Subtract several numbers from a rather large one
29             # - returns accdeiiiiloopssu
30             print subtract("Supercalifragilisticepsialidocious",
31             "stupid", "made", "up", "word");
32              
33             # Multiply two negative numbers - returns AAACCCCCCEEELLLNNN
34             print multiply("cancel", "out");
35              
36             # Divide two numbers - returns AAA
37             print divide("EuropeanCommission", "France");
38              
39             # Confirm Pythagorus' theorum - returns nothing
40             print subtract(exponentiate("FETLA", "PI"),
41             exponentiate("TLA", "PI"),
42             exponentiate("ETLA", "PI"));
43              
44             =head1 DESCRIPTION
45              
46             Silly::StringMaths provides support for basic integer mathematics, using
47             strings rather than numbers. Upper-case letters are positive,
48             lower-case letters are negative, so ABCDEF would be 6 (but
49             WOMBAT would also be 6), whereas C would actually be
50             -8. Mixed-case is also possible, so Compaq is actually -5.
51             Most methods return a canonicalised version of the string -
52             e.g. C rather than C (mixed case removed,
53             the result sorted alphabetically).
54              
55             The behaviour of other characters is as yet undefined, but be
56             warned that non-alphabetical characters may be reserved for
57             floating point or imaginary numbers.
58              
59             Actual numbers (i.e. the characters 0 to 9) will I be used
60             by this module.
61              
62             =head1 BASIC METHODS
63              
64             =head2 add
65              
66             Takes an array of strings, returns the sum.
67              
68             =cut
69              
70             sub add {
71 18     18 1 55 my $base=shift;
72             # Go through our arguments in turn
73 18         41 while (my $addition=shift) {
74             # If there are any positive elements, add them on now
75 22         67 while ($addition =~ s/([A-Z])//) {
76 89         261 $base.=$1;
77             }
78             # If there are any negative elements, subtract:
79 22         71 while ($addition =~ s/([a-z])//) {
80             # First take away any positive letters
81 84 100       147 if ($base =~ /[A-Z]/) {
82 49         163 $base =~ s/[A-Z]//;
83             } else {
84             # Then add on negative ones
85 35         168 $base.=$1;
86             }
87             }
88             }
89             # Return a normalised (i.e. sorted and condensed) version of this
90 18         33 return Silly::StringMaths::normalise($base);
91             }
92              
93             =head2 subtract
94              
95             Takes a string, subtracts all other supplied strings from it and
96             returns the result.
97              
98             =cut
99              
100             sub subtract {
101 2     2 1 5 my ($base, @others)=@_;
102             # Find our base, subtract all other numbers by adding negative
103             # versions of them
104 2         6 foreach (@others) {
105 6         11 Silly::StringMaths::invert(\$_);
106             }
107 2         6 return Silly::StringMaths::add($base, @others);
108             }
109              
110             =head2 multiply
111              
112             Takes a string and multiplies it by all the other strings,
113             returning the resulting product.
114              
115             =cut
116              
117             sub multiply {
118             # Find our base number, normalise it
119 6     6 1 9 my $base=Silly::StringMaths::normalise(shift);
120 6         15 while (my $product=Silly::StringMaths::normalise(shift)) {
121             # If the argument is negative, invert the base number
122 6 100       13 if (Silly::StringMaths::negative($product)) {
123 2         4 Silly::StringMaths::invert(\$base);
124             }
125             # Now add on the base number as many times as we have extra letters
126             # (so remove one letter from the product first)
127 6         15 $product =~ s%.%%;
128 6         9 my $step=$base;
129 6         33 while ($product =~ s%.%%) {
130 11         22 $base=Silly::StringMaths::add($base, $step);
131             }
132             }
133 6         22 return $base;
134             }
135              
136             =head2 divide
137              
138             Takes a string, and divides it by all the other strings,
139             returning the result. Results are rounded down.
140              
141             =cut
142              
143             sub divide {
144             # Find our base number, normalise it
145 1     1 1 3 my $base=Silly::StringMaths::normalise(shift);
146             # Find the sign of this number, convert the base number to positive
147 1         4 my $sign=Silly::StringMaths::sign($base);
148 1 50       7 if (Silly::StringMaths::negative($sign)) {
149 1         2 $base=Silly::StringMaths::multiply($base, $sign);
150             }
151             # Step through our divisors
152 1         3 while (my $divisor=Silly::StringMaths::normalise(shift)) {
153             # If this divisor is negative, invert our sign
154 1 50       2 if (Silly::StringMaths::negative(Silly::StringMaths::sign($divisor))) {
155 1         2 Silly::StringMaths::invert(\$sign);
156 1         2 Silly::StringMaths::invert(\$divisor);
157             }
158             # Now find how many times we can remove our divisor
159             # First convert our divisor to a regexp that can remove itself from
160             # a number, then apply it as many times as possible, and insert
161             # that many As into the return value
162 1         5 $divisor =~ s%.%.%g;
163 1         12 $base="A"x($base =~ s%$divisor%%g);
164             }
165             # Multiply our (positive) base number by the stored sign, return it
166 1         3 return Silly::StringMaths::multiply($base, $sign);
167             }
168              
169             =head2 exponentiate
170              
171             Takes a number, raises it to the appropriate power, as specified by the
172             other arguments. Returns the result. (Note that some textual information
173             is lost here - the result will be either Cs or Cs).
174              
175             =cut
176              
177             sub exponentiate {
178 3     3 1 9 my $base=Silly::StringMaths::normalise(shift);
179 3         7 while (my $power=Silly::StringMaths::normalise(shift)) {
180             # Don't allow negative powers
181 3 50       6 if (Silly::StringMaths::negative($power)) {
182 0         0 warn "Cannot use negative power $power";
183 0         0 return undef;
184             }
185             # Find the number we multiply by (the original base number)
186 3         7 my $multiply=$base;
187             # Remove one
188 3         7 $power =~ s%.%%;
189             # For every remaining digit, multiply base by its original value
190 3         25 while ($power =~ s%.%%) {
191 3         7 $base=Silly::StringMaths::multiply($base, $multiply);
192             }
193             }
194 3         9 return $base;
195             }
196              
197             =head1 USEFUL TOOLBOX METHODS
198              
199             =head2 normalise
200              
201             Takes a string with, potentially, a mix of upper-case and lower-case
202             letters, and returns a sorted string that is unmistakeably either
203             positive or negative.
204              
205             =cut
206              
207             sub normalise {
208 71     71 1 91 my ($number)=@_;
209              
210             # If there's a mixture of upper and lower case, add this number to
211             # a null string to make the negatives and positives cancel out
212 71 100 100     248 if ($number =~ /[A-Z]/ && $number =~ /[a-z]/) {
213 3         9 $number=Silly::StringMaths::add(undef, $number);
214             }
215             # Return this string in sorted form
216 71         427 return join("", sort split("", $number));
217             }
218              
219             =head2 sign
220              
221             Returns the sign of a number as either 1, 0 or -1 (as a string,
222             obviously).
223              
224             =cut
225              
226             sub sign {
227             # Take the sign of a number by normalising it, then removing all but
228             # the first character, so we have 1 or -1
229 2     2 1 5 my $number=Silly::StringMaths::normalise(shift);
230 2         7 $number =~ s%^(.).*%$1%;
231 2         4 return $number;
232             }
233              
234             =head2 negative
235              
236             Returns whether the supplied string is negative or not
237              
238             =cut
239              
240             sub negative {
241 11     11 1 13 my ($number)=@_;
242 11         17 return (Silly::StringMaths::normalise($number) =~ /[a-z]/);
243             }
244              
245             =head2 invert
246              
247             Takes a I to a number, inverts it.
248              
249             =cut
250              
251             sub invert {
252 10     10 1 13 my ($number)=shift;
253 10         16 $$number = Silly::StringMaths::normalise($$number);
254 10         18 $$number =~ tr/A-Za-z/a-zA-Z/;
255 10         19 return $$number;
256             }
257              
258              
259             =head1 AUTHOR
260              
261             Sam Kington, sam@illuminated.co.uk
262              
263             =head1 SEE ALSO
264              
265             perl(1).
266              
267             =cut
268              
269              
270             1;
271