File Coverage

blib/lib/Logic/TruthTable/Util.pm
Criterion Covered Total %
statement 62 63 98.4
branch 10 12 83.3
condition 1 3 33.3
subroutine 11 11 100.0
pod 6 6 100.0
total 90 95 94.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Logic::TruthTable::Util - provide utility functions to Logic::TruthTable
4              
5             =cut
6              
7             package Logic::TruthTable::Util;
8              
9 4     4   71730 use strict;
  4         17  
  4         140  
10 4     4   22 use warnings;
  4         8  
  4         128  
11 4     4   93 use 5.016001;
  4         14  
12              
13 4     4   21 use Carp;
  4         8  
  4         257  
14 4     4   26 use Exporter;
  4         20  
  4         2916  
15             our @ISA = qw(Exporter);
16              
17             our %EXPORT_TAGS = (
18             all => [ qw(
19             push_minterm_columns
20             push_maxterm_columns
21             var_column
22             shift_terms
23             rotate_terms
24             reverse_terms
25             ) ],
26             );
27              
28             our @EXPORT_OK = (
29             @{$EXPORT_TAGS{all}},
30             );
31              
32             our $VERSION = 1.02;
33              
34             =head1 DESCRIPTION
35              
36             This module provides various utilities designed for (but not limited to)
37             creating or manipulating term lists for Logic::TruthTable.
38              
39             =cut
40              
41             =head2 FUNCTIONS
42              
43             =head3 push_minterm_columns()
44              
45             =head3 push_maxterm_columns()
46              
47             push_minterm_columns($idx, $dir, \@colx, \@coly, \@colz);
48              
49             or
50              
51             push_maxterm_columns($idx, $dir, \@colx, \@coly, \@colz);
52              
53             Often the outputs to be simulated by boolean expressions are values that
54             are split across more than one column. For example, say that you want
55             to model a function to direct a pointer that uses the eight
56             L<cardinal and ordinal |https://en.wikipedia.org/wiki/Points_of_the_compass#Compass_point_names>
57             compass directions, from North (value 0) to NorthWest (value 7).
58              
59             Numbering these directions takes three bits, which means you'd need three
60             columns to represent them.
61              
62             To make it easier to create these columns, C<push_minterm_columns()>
63             (or, if you prefer, C<push_maxterm_columns()>) will take a value
64             from your function and, for each set bit (or if using maxterms,
65             unset bit), will push the minterm (or maxterm) onto each array
66             corresponding to its column.
67              
68             For example, if the value of row 20 is 5 (in binary C<0b101>),
69             then a call to C<push_minterm_columns(20, 5, \@x, \@y, \@z);>
70             will push 20 onto array variables C<@x>, and C<@z>, while a call to
71             C<push_maxterm_columns(20, 5, \@x, \@y, \@z);> will push a 20 onto
72             array variable C<@y> only.
73              
74             Bit values past the available columns will simply be dropped, while
75             excess columns will either never have terms pushed on them
76             (C<push_minterm_columns()>) or always have terms pushed on them
77             (C<push_maxterm_columns()>).
78              
79             For example:
80              
81             #
82             # Each column gets its own term list.
83             # The don't-care terms will be common across
84             # all columns.
85             #
86             my(@col2, @col1, @col0, @dontcares);
87              
88             #
89             # For each cell, return a direction.
90             #
91             for my $idx (0..63)
92             {
93             my $dir = sp_moveto($idx);
94              
95             #
96             # In this example, a cell that cannot be exited cannot
97             # be entered either, so mark it as a don't-care.
98             #
99             if ($dir < 0 or $dir > 7)
100             {
101             push @dontcares, $idx;
102             }
103             else
104             {
105             #
106             # For any set bit in $dir, push $idx onto the corresponding
107             # column list.
108             #
109             push_minterm_columns($idx, $dir, \@col2, \@col1, \@col0);
110             }
111             }
112              
113             You will then have the minterms available for each column of your
114             truth table.
115              
116             my $dir_table = Logic::TruthTable->new(
117             title => "Sandusky Path",
118             width => 6,
119             vars => [qw(a b c d e f)],
120             functions => [qw(m2 m1 m0)],
121             columns => [
122             {
123             minterms => \@col2,
124             dontcares => \@dontcares,
125             },
126             {
127             minterms => \@col1,
128             dontcares => \@dontcares,
129             },
130             {
131             minterms => \@col0,
132             dontcares => \@dontcares,
133             } ],
134             );
135              
136             =cut
137              
138             sub push_minterm_columns
139             {
140 121     121 1 1265 my($idx, $val, @colrefs) = @_;
141              
142 121         164 my $ncols = $#colrefs;
143 121         172 my $bit = 1 << $ncols;
144              
145             #
146             # Slice the bits across the columns.
147             #
148 121         197 for my $j (0..$ncols)
149             {
150 547 100       916 push @{ $colrefs[$j] }, $idx if ($val & $bit);
  191         314  
151 547         831 $bit >>= 1;
152             }
153             }
154              
155             sub push_maxterm_columns
156             {
157 8     8 1 1398 my($idx, $val, @colrefs) = @_;
158              
159 8         14 my $ncols = $#colrefs;
160 8         12 my $bit = 1 << $ncols;
161              
162             #
163             # Slice the bits across the columns.
164             #
165 8         15 for my $j (0..$ncols)
166             {
167 16 100       30 push @{ $colrefs[$j] }, $idx unless ($val & $bit);
  8         14  
168 16         32 $bit >>= 1;
169             }
170             }
171              
172             =head3 var_column()
173              
174             Return the list of terms that correspond to the set bits of a
175             variable's column.
176              
177             my @terms = var_column($width, $col);
178              
179             For example, in a three-variable table
180              
181             x y z | f
182             -----------|--
183             0 | 0 0 0 |
184             1 | 0 0 1 |
185             2 | 0 1 0 |
186             3 | 0 1 1 |
187             4 | 1 0 0 |
188             5 | 1 0 1 |
189             6 | 1 1 0 |
190             7 | 1 1 1 |
191              
192             column 2 (the x column) has terms (4, 5, 6, 7) set, while column 0
193             (the z column) has terms (1, 3, 5, 7) set.
194              
195             =cut
196              
197             sub var_column
198             {
199 1     1 1 1081 my($width, $col) = @_;
200              
201 1 50       5 croak "Column $col doesn't exist in a $width-column table." if ($col >= $width);
202              
203             #
204             # A 'block' is a sequence of ones in the column.
205             # 'blocklen' is the number of ones, blocks is the number of
206             # those sequences.
207             #
208             # So in a set of four variable columns, column one's ones come
209             # in eight sets of two ones.
210             #
211 1         15 my $blocklen = 1 << $col;
212 1         2 my $blocks = 1 << ($width - $col - 1);
213 1         2 my @terms;
214              
215 1         4 for my $n (0 .. $blocks - 1)
216             {
217 2         6 push @terms, map { (2 * $n + 1) * $blocklen + $_} (0 .. $blocklen - 1);
  8         18  
218             }
219              
220 1         4 return @terms;
221             }
222              
223             =head3 reverse_terms()
224              
225             Reverses the list of terms by index. For example, within a four-bit range:
226              
227             $width = 4; # values range 0 .. 15.
228             @terms = (1, 3, 6, 8, 13, 14);
229             @terms = reverse_terms($width, \@terms);
230              
231             The values in C<@terms> will become (14, 12, 9, 7, 2, 1).
232              
233             =cut
234              
235             sub reverse_terms
236             {
237 1     1 1 545 my($width, $tref) = @_;
238 1         3 my $last = (1 << $width) - 1;
239              
240 1         2 return map {$last - $_} @{ $tref };
  4         10  
  1         2  
241             }
242              
243             =head3 rotate_terms()
244              
245             Rotates the list of terms by index. For example, within a four-bit range:
246              
247             $width = 4; # values range 0 .. 15.
248             $shift = 5; # term 0 becomes term 5, term 1 becomes term 6
249             @terms = (1, 3, 7, 9, 13, 15);
250             @terms = rotate_terms($width, \@terms, $shift);
251              
252             The values in C<@terms> will become will be (6, 8, 12, 14, 2, 4),
253             with the last two list items rotated around to the beginning, having
254             been rotated past what C<$width> allows. A negative-valued shift rotates
255             the terms backward.
256              
257             =cut
258              
259             sub rotate_terms
260             {
261 3     3 1 1730 my($width, $tref, $shift) = @_;
262 3         8 my $length = 1 << $width;
263              
264 3         6 $shift %= $length;
265              
266 3         10 $shift += $length while ($shift < 0);
267              
268 3 100       8 return @{ $tref } if ($shift == 0);
  1         5  
269              
270 2         4 return map {($shift + $_) % $length} @{ $tref };
  10         27  
  2         5  
271             }
272              
273             =head3 shift_terms()
274              
275             Shifts the list of terms by index. For example, within a four-bit range:
276              
277             $width = 4; # values range 0 .. 15.
278             $shift = 5; # term 0 becomes term 5, term 1 becomes term 6
279             @terms = (1, 3, 7, 9, 13, 15);
280             @terms = shift_terms($width, \@terms, $shift);
281              
282             The values in C<@terms> will become (6, 8, 12, 14), with the last
283             two list items dropped, having been shifted past what C<$width> allows.
284             A negative-valued shift shifts the terms downward.
285              
286             =cut
287              
288             sub shift_terms
289             {
290 3     3 1 1604 my($width, $tref, $shift) = @_;
291 3         7 my $length = 1 << $width;
292              
293 3 50 33     22 if ($shift >= $length or $shift <= -$length)
    100          
294             {
295 0         0 return ();
296             }
297             elsif ($shift > 0)
298             {
299 1         2 return map {$shift + $_} grep {$_ < ($length - $shift)} @{ $tref };
  3         9  
  4         8  
  1         3  
300             }
301             else
302             {
303 2         6 return map {$shift + $_} grep {$_ >= -$shift} @{ $tref };
  8         19  
  9         23  
  2         4  
304             }
305             }
306              
307             =head1 SEE ALSO
308              
309             L<Logic::TruthTable::Convert81>
310              
311             =head1 AUTHOR
312              
313             John M. Gamble C<< <jgamble@cpan.org> >>
314              
315             =cut
316              
317             1;
318              
319             __END__
320