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; |