File Coverage

blib/lib/Tie/Math.pm
Criterion Covered Total %
statement 68 79 86.0
branch 8 8 100.0
condition 6 6 100.0
subroutine 14 20 70.0
pod 0 1 0.0
total 96 114 84.2


line stmt bran cond sub pod time code
1             package Tie::Math;
2              
3 1     1   920 use strict;
  1         2  
  1         61  
4              
5             require Exporter;
6 1     1   6 use vars qw(@EXPORT @Variables @EXPORT_OK @ISA $VERSION);
  1         2  
  1         127  
7              
8             @ISA = qw(Exporter);
9              
10             @EXPORT = qw(f N);
11              
12             # @Variables is defined below.
13             @EXPORT_OK = @Variables;
14              
15             $VERSION = '0.10';
16              
17             # Need lvalue subroutines.
18 1     1   37 use 5.006;
  1         16  
  1         47  
19              
20 1     1   6 use constant DEBUG => 0;
  1         2  
  1         427  
21              
22              
23             # Alas, I can't use Tie::StdHash and Tie::Hash is too bloody slow.
24             # So I'll just copy the meat of Tie::StdHash in here and do a little
25             # s///
26 0     0   0 sub STORE { $_[0]->{hash}->{$_[1]} = $_[2] }
27 0     0   0 sub FIRSTKEY { my $a = scalar keys %{$_[0]->{hash}}; each %{$_[0]->{hash}} }
  0         0  
  0         0  
  0         0  
28 0     0   0 sub NEXTKEY { each %{$_[0]->{hash}} }
  0         0  
29 0     0   0 sub EXISTS { exists $_[0]->{hash}->{$_[1]} }
30 0     0   0 sub DELETE { delete $_[0]->{hash}->{$_[1]} }
31 0     0   0 sub CLEAR { %{$_[0]->{hash}} = () }
  0         0  
32              
33              
34             =pod
35              
36             =head1 NAME
37              
38             Tie::Math - Hashes which represent mathematical functions.
39              
40              
41             =head1 SYNOPSIS
42              
43             use Tie::Math;
44             tie %fibo, 'Tie::Math', sub { f(n) = f(n-1) + f(n-2) },
45             sub { f(0) = 0; f(1) = 1 };
46              
47             # Calculate and print the fifth fibonacci number
48             print $fibo{5};
49              
50              
51             =head1 DESCRIPTION
52              
53             Defines hashes which represent mathematical functions, such as the
54             fibonacci sequence, factorials, etc... Functions can be expressed in
55             a manner which a math or physics student might find a bit more
56             familiar. It also automatically employs memoization.
57              
58             Multi-variable functions are supported. f() is simply passed two
59             variables (f(X,Y) for instance) and the hash is accessed in the same
60             way ($func{3,-4}).
61              
62             =over 4
63              
64             =item B
65              
66             tie %func, 'Tie::Math', \&function;
67             tie %func, 'Tie::Math', \&function, \&initialization;
68              
69             &function contains the definition of the mathematical function. Use
70             the f() subroutine and N index provided. So to do a simple
71             exponential function represented by "f(N) = N**2":
72              
73             tie %exp, 'Tie::Math', sub { f(N) = N**2 };
74              
75             &initialization contains any special cases of the function you need to
76             define. In the fibonacci example in the SYNOPSIS you have to define
77             f(0) = 1 and f(1) = 1;
78              
79             tie %fibo, 'Tie::Math', sub { f(N) = f(N-1) + f(N-2) },
80             sub { f(0) = 1; f(1) = 1; };
81              
82             The &initializaion routine is optional.
83              
84             Each calculation is "memoized" so that for each element of the array the
85             calculation is only done once.
86              
87             While the variable N is given by default, A through Z are all
88             available. Simply import them explicitly:
89              
90             # Don't forget to import f()
91             use Tie::Math qw(f X);
92              
93             There's no real difference which variable you use, its just there for
94             your preference. (NOTE: I had to use captial letters to avoid
95             clashing with the y// operator)
96              
97             =cut
98              
99             #'#
100              
101 1     1   7 use vars qw($Obj $Idx $IsInit @Curr_Idx %Vars);
  1         1  
  1         794  
102              
103             sub TIEHASH {
104 5     5   103 my($class, $func, $init) = @_;
105              
106 5         11 my $self = bless {}, $class;
107              
108 5         18 $self->{func} = $func;
109 5         10 $self->{hash} = {};
110              
111 5 100       31 if( defined $init ) {
112 2         5 local $Obj = $self;
113 2         3 local $IsInit = 1;
114 2         6 $init->();
115             }
116              
117 5         16 return $self;
118             }
119              
120              
121             sub _normal_idx {
122 36     36   98 return join $;, @_;
123             }
124              
125              
126             sub _split_idx {
127 15     15   77 return split /$;/, $_[0];
128             }
129              
130              
131             sub f : lvalue {
132 36     36 0 94 my(@idx) = @_;
133              
134 36         36 warn "f() got ", join(" ", @_), "\n" if DEBUG;
135              
136 36         54 my($norm_idx) = _normal_idx(@idx);
137 36         61 my($hash) = $Obj->{hash};
138              
139 36         30 warn "f() index - ", join(" ", @idx), "\n" if DEBUG;
140 36         32 warn "\$Idx - $Idx\n" if DEBUG;
141 36         30 warn "\$IsInit == $IsInit\n" if DEBUG;
142 36         33 select(undef,undef,undef,0.200) if DEBUG;
143              
144 36 100 100     187 unless( $IsInit || exists $hash->{$norm_idx} || $Idx eq $norm_idx )
      100        
145             {
146 7         7 warn "FETCHing $norm_idx\n" if DEBUG;
147 7         19 $Obj->FETCH($norm_idx);
148             }
149              
150             # Can't return an array element from an lvalue routine, but we
151             # can return a dereferenced reference to it!
152 36         74 my $tmp = \$hash->{$norm_idx};
153 36         37 warn "tmp is $$tmp\n" if DEBUG;
154 36         123 $$tmp;
155             }
156              
157              
158             # "variable" routines.
159             BEGIN {
160 1     1   7 no strict 'refs';
  1         2  
  1         197  
161              
162 1     1   10 @Variables = ('A'..'Z');
163              
164 1         3 foreach my $var (@Variables) {
165 26         360 *{$var} = sub () {
166 90 100   90   307 $Vars{$var} = shift @Curr_Idx if @Curr_Idx;
167 90         83 warn "$var() is $Vars{$var}\n" if DEBUG;
168 90         269 return $Vars{$var};
169             }
170 26         122 }
171             }
172              
173              
174             sub FETCH {
175 17     17   65 my($self, $idx) = @_;
176 17         22 my $hash = $self->{hash};
177              
178 17         16 warn "\@Curr_Idx == ", join "\n", _split_idx($idx), "\n" if DEBUG;
179              
180 17         39 my($call_pack) = caller;
181              
182 17         21 warn "FETCH() idx is $idx\n" if DEBUG;
183 17         16 warn "FETCH() calling pack is $call_pack\n" if DEBUG;
184              
185 17 100       41 unless( exists $hash->{$idx} ) {
186 15         14 warn "Generating ", join(" ", @Curr_Idx), "\n" if DEBUG;
187              
188 1     1   6 no strict 'refs';
  1         2  
  1         150  
189              
190             # Yes, LOCAL. I have to maintain my own stack.
191 15         27 local @Curr_Idx = _split_idx($idx);
192 15         20 local $Obj = $self;
193              
194             # This goes away once wantlvalue() is implemented.
195 15         17 local $IsInit = 0;
196              
197 15         19 local $Idx = $idx;
198 15         19 local %Vars;
199              
200 15         49 $self->{func}->(@Curr_Idx);
201             }
202              
203 17         71 return $hash->{$idx};
204             }
205              
206             =pod
207              
208             =head1 EXAMPLE
209              
210             Display a polynomial equation in a table.
211              
212             use Tie::Math;
213              
214             tie %poly, 'Tie::Math', sub { f(N) = N**2 + 2*N + 1 };
215              
216             print " f(N) = N**2 + 2*N + 1 where N == -3 to 3\n";
217             print "\t x \t poly\n";
218             for my $x (-3..3) {
219             printf "\t % 2d \t % 3d\n", $x, $poly{$x};
220             }
221              
222             This should display:
223              
224             f(N) = N**2 + 2*N + 1 where N == -3 to 3
225             x poly
226             -3 4
227             -2 1
228             -1 0
229             0 1
230             1 4
231             2 9
232             3 16
233              
234              
235             How about Pascal's Triangle!
236              
237             use Tie::Math qw(f X Y);
238              
239             my %pascal;
240             tie %pascal, 'Tie::Math', sub {
241             if( X <= Y and Y > 0 and X > 0 ) {
242             f(X,Y) = f(X-1,Y-1) + f(X,Y-1);
243             }
244             else {
245             f(X,Y) = 0;
246             }
247             },
248             sub {
249             f(1,1) = 1;
250             f(1,2) = 1;
251             f(2,2) = 1;
252             };
253              
254             #'#
255             $height = 5;
256             for my $y (1..$height) {
257             print " " x ($height - $y);
258             for my $x (1..$y) {
259             print $pascal{$x,$y};
260             }
261             print "\n";
262             }
263              
264             This should produce a nice neat little triangle:
265              
266             1
267             1 1
268             1 2 1
269             1 3 3 1
270             1 4 6 4 1
271              
272              
273             =head1 EFFICIENCY
274              
275             Memoization is automatically employed so no f(X) is calculated twice.
276             This radically increases efficiency in many cases.
277              
278              
279             =head1 BUGS, CAVAETS and LIMITATIONS
280              
281             Certain functions cannot be properly expressed. For example, the
282             equation defining a circle, f(X) = sqrt(1 - X**2), has two solutions
283             for each f(X).
284              
285             There's some horrific hacks in here to make up for the limitations of the
286             current lvalue subroutine implementation. Namely missing wantlvalue().
287              
288             This code use the experimental lvalue subroutine feature which will
289             hopefully change in the future.
290              
291             The interface is currently very alpha and will probably change in the
292             near future.
293              
294             This module BREAKS 5.6.0's DEBUGGER! Neat, eh?
295              
296             This module uses the old multidimensional hash emulation from the Perl
297             4 days. While this isn't currently a bad thing, it may eventually be
298             destined for the junk heap.
299              
300              
301             =head1 AUTHOR
302              
303             Michael G Schwern
304              
305              
306             =head1 TODO
307              
308             Easier ways to set boundries ie. "f(X,Y) = X + Y where X > 0 and Y > 1"
309              
310             =cut
311              
312             #'#
313              
314             1;