File Coverage

blib/lib/Algorithm/QuineMcCluskey.pm
Criterion Covered Total %
statement 178 193 92.2
branch 41 52 78.8
condition 7 9 77.7
subroutine 19 21 90.4
pod 7 12 58.3
total 252 287 87.8


line stmt bran cond sub pod time code
1             package Algorithm::QuineMcCluskey;
2              
3 15     15   73000 use strict;
  15         131  
  15         428  
4 15     15   76 use warnings;
  15         32  
  15         371  
5 15     15   331 use 5.016001;
  15         53  
6              
7 15     15   8458 use Moose;
  15         7102334  
  15         101  
8 15     15   121964 use namespace::autoclean;
  15         124614  
  15         63  
9              
10 15     15   982 use Carp;
  15         39  
  15         1095  
11              
12 15     15   7484 use Algorithm::QuineMcCluskey::Util qw(:all);
  15         46  
  15         2225  
13 15     15   124 use List::Util qw(uniqnum);
  15         32  
  15         872  
14 15     15   109 use List::Compare::Functional qw(get_complement is_LequivalentR);
  15         32  
  15         36916  
15              
16             extends 'Logic::Minimizer';
17              
18             #
19             # Vaguely consistent Smart-Comment rules:
20             # 3 pound signs for the code in BUILD() and generate_*() functions.
21             #
22             # 4 pound signs for code that manipulates prime/essentials/covers hashes:
23             # row_dominance().
24             #
25             # 5 pound signs for the solve() and recurse_solve() code, and the remels()
26             # calls.
27             #
28             # The ::Format package is only needed for Smart Comments -- comment or uncomment
29             # in concert with Smart::Comments as needed.
30             #
31             #use Algorithm::QuineMcCluskey::Format qw(arrayarray hasharray chart);
32             #use Smart::Comments ('###', '#####');
33              
34             #
35             # Attributes inherited from Logic::Minimizer are width, minterms, maxterms,
36             # dontcares, columnstring, title, dc, vars, primes, essentials, covers,
37             # group_symbols, order_by, and minonly.
38             #
39              
40             #
41             # The '_bits' fields are the terms' bitstring fields, and are
42             # internal attributes. No setting them at object creation.
43             #
44             has 'dc_bits' => (
45             isa => 'ArrayRef[Str]', is => 'rw', required => 0,
46             init_arg => undef,
47             predicate => 'has_dc_bits'
48             );
49             has 'min_bits' => (
50             isa => 'ArrayRef[Str]', is => 'rw', required => 0,
51             init_arg => undef,
52             predicate => 'has_min_bits'
53             );
54             has 'max_bits' => (
55             isa => 'ArrayRef[Str]', is => 'rw', required => 0,
56             init_arg => undef,
57             predicate => 'has_max_bits'
58             );
59              
60             our $VERSION = 1.01;
61              
62             sub BUILD
63             {
64 36     36 0 286961 my $self = shift;
65 36         1100 my $w = $self->width;
66 36         357 my $last_idx = (1 << $w) - 1;
67 36         73 my @terms;
68              
69             #
70             # Catch errors involving minterms, maxterms, and don't-cares.
71             #
72 36         187 $self->catch_errors();
73              
74             #
75             # We've gotten past the error-checking. Create the object.
76             #
77 36 100       19097 if ($self->has_columnstring)
78             {
79 1         29 my($min_ref, $max_ref, $dc_ref) =
80             $self->list_to_terms(split(//, $self->columnstring));
81              
82             ### min_ref: $min_ref
83             ### max_ref: $max_ref
84             ### don't cares: $dc_ref
85              
86 1 50       73 $self->minterms($min_ref) if (scalar @{$min_ref} );
  1         32  
87 1 50       66 $self->dontcares($dc_ref) if (scalar @{$dc_ref} );
  1         4  
88             }
89              
90 36 100       1166 if ($self->has_minterms)
91             {
92 31         204 @terms = sort(uniqnum(@{$self->minterms}));
  31         700  
93              
94             my @bitstrings = map {
95 31         745 substr(unpack("B32", pack("N", $_)), -$w)
  272         990  
96             } @terms;
97              
98 31         1137 $self->min_bits(\@bitstrings);
99             ### Min terms binary: $self->min_bits()
100             }
101 36 100       1152 if ($self->has_maxterms)
102             {
103 5         38 @terms = sort(uniqnum(@{$self->maxterms}));
  5         116  
104              
105             my @bitstrings = map {
106 5         125 substr(unpack("B32", pack("N", $_)), -$w)
  43         159  
107             } @terms;
108              
109 5         189 $self->max_bits(\@bitstrings);
110             ### Max terms binary: $self->max_bits()
111             }
112              
113 36 100       1189 if ($self->has_dontcares)
114             {
115 19         151 my @dontcares = sort(uniqnum(@{$self->dontcares}));
  19         454  
116              
117             my @bitstrings = map {
118 19         435 substr(unpack("B32", pack("N", $_)), -$w)
  136         451  
119             } @dontcares;
120              
121 19         674 $self->dc_bits(\@bitstrings);
122             ### Don't-cares binary: $self->dc_bits()
123             }
124              
125 36 50       1218 $self->title("$w-variable truth table") unless ($self->has_title);
126              
127 36         349 return $self;
128             }
129              
130             sub complement_terms
131             {
132 2     2 0 4 my $self = shift;
133 2         48 my @bitlist = (0 .. (1 << $self->width) - 1);
134 2 50       67 my @termlist = @{$self->dontcares} if ($self->has_dontcares);
  0         0  
135              
136 2 50       69 if ($self->has_minterms)
137             {
138 2         12 push @termlist, @{ $self->minterms };
  2         45  
139             }
140             else
141             {
142 0         0 push @termlist, @{ $self->maxterms };
  0         0  
143             }
144              
145 2         44 return get_complement([\@termlist, \@bitlist]);
146             }
147              
148             #
149             # Build another Quine-McCluskey object that's the complement
150             # of the existing object.
151             #
152             sub complement
153             {
154 1     1 1 848 my $self = shift;
155 1         3 my %term;
156              
157 1 50       47 $term{dontcares} = [@{$self->dontcares}] if ($self->has_dontcares);
  0         0  
158              
159 1 50       36 if ($self->has_minterms)
160             {
161 1         9 $term{minterms} = [$self->complement_terms()];
162             }
163             else
164             {
165 0         0 $term{maxterms} = [$self->complement_terms()];
166             }
167              
168 1         358 my $title = "Complement of '" . $self->title() . "'";
169              
170             return Algorithm::QuineMcCluskey->new(
171             title => $title,
172             width => $self->width,
173             dc => $self->dc,
174 1         32 vars => [ @{$self->vars} ],
  1         53  
175             %term
176             );
177             }
178              
179             #
180             # Build another Quine-McCluskey object that's the dual
181             # of the existing object.
182             #
183             sub dual
184             {
185 1     1 1 639 my $self = shift;
186 1         32 my $last = (1 << $self->width) - 1;
187 1         9 my %term;
188              
189 1 50       45 $term{dontcares} = [@{$self->dontcares}] if ($self->has_dontcares);
  0         0  
190              
191 1         22 my @dualterms = sort map {$last - $_} $self->complement_terms();
  3         263  
192              
193 1 50       39 if ($self->has_minterms)
194             {
195 1         10 $term{minterms} = [@dualterms];
196             }
197             else
198             {
199 0         0 $term{maxterms} = [@dualterms];
200             }
201              
202 1         25 my $title = "Dual of '" . $self->title() . "'";
203              
204             return Algorithm::QuineMcCluskey->new(
205             title => $title,
206             width => $self->width,
207             dc => $self->dc,
208 1         31 vars => [ @{$self->vars} ],
  1         51  
209             %term
210             );
211             }
212              
213             sub all_bit_terms
214             {
215 30     30 0 60 my $self = shift;
216 30         57 my @terms;
217              
218 30 100       1055 push @terms, ($self->has_min_bits)? @{$self->min_bits}: @{$self->max_bits};
  26         781  
  4         136  
219 30 100       1089 push @terms, @{ $self->dc_bits } if ($self->has_dc_bits);
  19         564  
220 30         269 return sort @terms;
221             }
222              
223             sub minmax_bit_terms
224             {
225 30     30 0 297 my $self = shift;
226              
227 30 100       1206 return ($self->has_min_bits)? @{$self->min_bits}: @{$self->max_bits};
  26         785  
  4         124  
228             }
229              
230             sub generate_primes
231             {
232 30     30 1 724 my $self = shift;
233 30         72 my @bits;
234             my %implicant;
235              
236             #
237             # Separate into bins based on number of 1's (the weight).
238             #
239             ### generate_primes() group the bit terms
240             #
241 30         120 for ($self->all_bit_terms())
242             {
243 429         1065 my $setcount = scalar(() = $_ =~ m/1/g);
244 429         654 push @{$bits[0][ $setcount ]}, $_;
  429         959  
245             }
246              
247             #
248             # Now for each level, we look for terms that can be absorbed into
249             # simpler product terms (for example, _ab_c + ab_c can be simplified
250             # to b_c).
251             #
252             # Level 0 consists of the fundemental
253             # product terms; level 1 consists of pairs of fundemental terms
254             # that have a variable in common; level 2 consists of pairs of pairs
255             # that have a variable in common; and so on until we're out of
256             # levels (number of variables) or cannot find any more products
257             # with terms in common.
258             #
259 30         785 for my $level (0 .. $self->width)
260             {
261             #
262             # Skip if we haven't generated data for this level.
263             #
264 125 100       559 last unless ref $bits[$level];
265              
266             #
267             ### Level: $level
268             ### grouped by bit count: $bits[$level]
269             #
270             # Find pairs with Hamming distance of 1 (i.e., a weight
271             # difference of 1).
272             #
273 99         170 for my $low (0 .. $#{ $bits[$level] })
  99         265  
274             {
275 485         752 my %nextlvlimp;
276              
277             #
278             # These nested for-loops get all permutations
279             # of adjacent sets.
280             #
281 485         658 for my $lv (@{ $bits[$level][$low] })
  485         939  
282             {
283             #
284             # Initialize the implicant as unused.
285             #
286 1471   100     3715 $implicant{$lv} //= 0;
287              
288             #
289             # Skip ahead if there are no terms at
290             # this level.
291             #
292 1471 100       3219 next unless ref $bits[$level][$low + 1];
293              
294 1131         1584 for my $hv (@{ $bits[$level][$low + 1] })
  1131         2022  
295             {
296             #
297             # Initialize the implicant.
298             #
299 9080   100     19218 $implicant{$hv} //= 0;
300              
301             #
302             # If there are matching terms, save
303             # the new implicant at the next 'level',
304             # creating the level if it doesn't exist.
305             #
306 9080         17351 my $hd1pos = hammingd1pos($lv, $hv);
307 9080 100       20144 if ($hd1pos != -1)
308             {
309 1472         2186 my $new = $lv; # or $hv
310 1472         34337 substr($new, $hd1pos, 1) = $self->dc;
311              
312             #
313             ### Compared: $lv
314             ### to : $hv
315             ### saving : $new
316             #
317             # Save the new implicant to the
318             # next level, then mark the two
319             # values as used.
320             #
321 1472         10383 $nextlvlimp{$new} = 1;
322 1472         2320 $implicant{$lv} = 1;
323 1472         2848 $implicant{$hv} = 1;
324             }
325             }
326             }
327              
328             #
329             # Push those next-level implicants to the next level,
330             # assuming we found any.
331             #
332 485 100       1147 push @{ $bits[$level + 1][$low + 1] }, keys %nextlvlimp if (%nextlvlimp);
  177         1194  
333             }
334             }
335              
336             #
337             ### generate_primes() implicant hash (we use the unmarked entries
338             ### [i.e., prime => 0] ) : %implicant
339             #
340              
341             #
342             # For each unmarked (value == 0) implicant, match it against the
343             # minterms (or maxterms). The resulting hash of arrays is our
344             # set of prime implicants.
345             #
346 30         59 my %p;
347 30         108 my @bit_terms = $self->minmax_bit_terms();
348              
349 30         362 for my $unmarked (grep { !$implicant{$_} } keys %implicant)
  1471         2447  
350             {
351 227         790 my @matched = maskedmatch($unmarked, @bit_terms);
352 227 100       794 $p{$unmarked} = [@matched] if (@matched);
353             }
354              
355             #
356             ### generate_primes() -- prime implicants: hasharray(\%p)
357             #
358 30         1383 return \%p;
359             }
360              
361             sub generate_covers
362             {
363 28     28 1 499 my $self = shift;
364              
365 28         824 return [ $self->recurse_solve($self->get_primes, 0) ];
366             }
367              
368             sub generate_essentials
369             {
370 0     0 1 0 my $self = shift;
371              
372 0         0 return [sort find_essentials($self->get_primes) ];
373             }
374              
375             sub solve
376             {
377 28     28 1 2851 my $self = shift;
378 28         885 my $c = $self->get_covers();
379              
380             ### solve -- get_covers() returned: arrayarray($c)
381              
382 28         3661 return $self->to_boolean($c->[0]);
383             }
384              
385             sub all_solutions
386             {
387 0     0 1 0 my $self = shift;
388 0         0 my $c = $self->get_covers();
389              
390             ### all_solutions -- get_covers() returned: arrayarray($c)
391              
392 0         0 return map {$self->to_boolean($_)} @$c;
  0         0  
393             }
394              
395             #
396             # recurse_solve
397             #
398             # Recursive divide-and-conquer solver
399             #
400             # "To reduce the complexity of the prime implicant chart:
401             #
402             # 1. Select all the essential prime impliciants. If these PIs cover all
403             # minterms, stop; otherwise go the second step.
404             #
405             # 2. Apply Rules 1 and 2 to eliminate redundant rows and columns from
406             # the PI chart of non-essential PIs. When the chart is thus reduced,
407             # some PIs will become essential (i.e., some columns will have a single
408             # 'x'. Go back to step 1."
409             #
410             # Introduction To Logic Design, by Sajjan G. Shiva, page 129.
411             #
412             sub recurse_solve
413             {
414 54     54 0 649 my $self = shift;
415 54         94 my %primes = %{ $_[0] };
  54         253  
416 54         128 my $level = $_[1];
417 54         164 my @prefix;
418             my @covers;
419 54         0 my @essentials;
420              
421             #
422             ##### recurse_solve() level: $level
423             ##### recurse_solve() called with
424             ##### primes: "\n" . chart(\%primes, $self->width)
425             #
426 54         232 my @essentials_next = find_essentials(\%primes);
427              
428             #
429             ##### Begin prefix/essentials loop.
430             #
431             do
432 54         109 {
433             #
434             ##### recurse_solve() do loop, essentials: @essentials
435             #
436             # Remove the essential prime implicants from
437             # the prime implicants table.
438             #
439 99         9064 @essentials = @essentials_next;
440              
441             #
442             ##### Purging prime hash of: "[" . join(", ", sort @essentials) . "]"
443             #
444 99         364 purge_elements(\%primes, @essentials);
445 99         236 push @prefix, @essentials;
446              
447             ##### recurse_solve() @prefix now: "[" . join(", ", sort @prefix) . "]"
448              
449             #
450             # Now eliminate dominated rows and columns.
451             #
452             # Rule 1: A row dominated by another row can be eliminated.
453             # Rule 2: A column that dominated another column can be eliminated.
454             #
455             #### Looking for rows dominated by other rows
456             #### primes table: "\n" . chart(\%primes, $self->width)
457 99         294 my @rows = row_dominance(\%primes, 1);
458 99         251 delete $primes{$_} for (@rows);
459             #### row_dominance returns rows for removal: "[" . join(", ", @rows) . "]"
460             #### primes now: "\n" . chart(\%primes, $self->width)
461              
462 99         317 my %cols = transpose(\%primes);
463 99         287 my @cols = row_dominance(\%cols, 0);
464 99         353 remels(\%primes, @cols);
465             #### row_dominance returns cols for removal: "[" . join(", ", @cols) . "]"
466             #### primes now: "\n" . chart(\%primes, $self->width)
467              
468 99         288 @essentials_next = find_essentials(\%primes);
469              
470             ##### recurse_solve() essentials after purge/dom: @essentials
471              
472             } until (is_LequivalentR([
473             [ @essentials] => [ @essentials_next ]
474             ]));
475              
476 54 100       9525 return [ reverse sort @prefix ] unless (keys %primes);
477              
478             #
479             # Find a term (there may be more than one) that has the least
480             # number of prime implicants covering it, and a list of those
481             # prime implicants. Use that list to figure out the best set
482             # to cover the rest of the terms.
483             #
484             ##### recurse_solve() Primes after loop
485             ##### primes: "\n" . chart(\%primes, $self->width)
486             #
487 36         111 my($term, @ta) = covered_least(\%primes);
488              
489             #
490             ##### Least Covered term: $term
491             ##### Covered by: @ta
492             #
493             # Make a copy of the section of the prime implicants
494             # table that don't cover that term.
495             #
496             my %r = map {
497 36         98 $_ => [ grep { $_ ne $term } @{ $primes{$_} } ]
  117         184  
  144         409  
  117         191  
498             } keys %primes;
499              
500             #
501             # For each such cover, recursively solve the table with that column
502             # removed and add the result(s) to the covers table after adding
503             # back the removed term.
504             #
505 36         91 for my $ta (@ta)
506             {
507 74         135 my (@c, @results);
508 74         209 my %reduced = %r;
509              
510             #
511             # Use this prime implicant -- delete its row and columns
512             #
513             ##### Purging reduced hash of: $ta
514             #
515 74         252 purge_elements(\%reduced, $ta);
516              
517 74 100 100     367 if (keys %reduced and scalar(@c = $self->recurse_solve(\%reduced, $level + 1)))
518             {
519             #
520             ##### recurse_solve() at level: $level
521             ##### returned (in loop): arrayarray(\@c)
522             #
523 26         53 @results = map { [ reverse sort (@prefix, $ta, @$_) ] } @c;
  76         262  
524             }
525             else
526             {
527 48         177 @results = [ reverse sort (@prefix, $ta) ]
528             }
529              
530 74         228 push @covers, @results;
531              
532             #
533             ##### Covers now at: arrayarray(\@covers)
534             #
535             }
536              
537             #
538             ##### Weed out the duplicated and expensive solutions.
539             #
540 36         107 @covers = uniqels @covers;
541              
542 36 50 33     1405 if ($self->minonly and scalar @covers > 1)
543             {
544 36         402 my @weededcovers = shift @covers;
545 36         64 my $mincost = matchcount(join('', @{$weededcovers[0]}), "[01]");
  36         145  
546              
547 36         114 for my $c (@covers)
548             {
549 88         253 my $cost = matchcount(join('', @$c), "[01]");
550              
551             #
552             ##### Cover: join(',', @$c)
553             ##### Cost: $cost
554             #
555 88 100       219 next if ($cost > $mincost);
556              
557 80 50       159 if ($cost < $mincost)
558             {
559 0         0 $mincost = $cost;
560 0         0 @weededcovers = ();
561             }
562 80         208 push @weededcovers, $c;
563             }
564 36         96 @covers = @weededcovers;
565             }
566              
567             #
568             ##### Covers is: arrayarray(\@covers)
569             ##### after the weeding out.
570             #
571             # Return our covers table to be treated similarly one level up.
572             #
573 36         569 return @covers;
574             }
575              
576             1;
577             __END__
578              
579             =head1 NAME
580              
581             Algorithm::QuineMcCluskey - solve Quine-McCluskey set-cover problems
582              
583             =head1 SYNOPSIS
584              
585             use Algorithm::QuineMcCluskey;
586              
587             #
588             # Five-bit, 12-minterm Boolean expression test with don't-cares
589             #
590             my $q = Algorithm::QuineMcCluskey->new(
591             width => 5,
592             minterms => [ 0, 5, 7, 8, 10, 11, 15, 17, 18, 23, 26, 27 ],
593             dontcares => [ 2, 16, 19, 21, 24, 25 ]
594             );
595              
596             my $result = $q->solve();
597              
598             or
599              
600             use Algorithm::QuineMcCluskey;
601              
602             my $q = Algorithm::QuineMcCluskey->new(
603             width => 5,
604             columnstring => '10-0010110110001-11-0-01--110000'
605             );
606              
607             my $result = $q->solve();
608              
609             In either case C<$result> will be C<"(AC') + (A'BDE) + (B'CE) + (C'E')">.
610              
611             The strings that represent the covered terms are also viewable:
612              
613              
614             use Algorithm::QuineMcCluskey;
615              
616             #
617             # Five-bit, 12-minterm Boolean expression test with don't-cares
618             #
619             my $q = Algorithm::QuineMcCluskey->new(
620             width => 4,
621             minterms => [ 1, 6, 7, 8, 11, 13, 15],
622             );
623              
624             my @covers = $q->get_covers();
625              
626             print $q->solve(), "\n", join(", ", @covers[0];
627              
628             Will print out
629              
630             '0001', '011-', '1-11', '1000', '11-1'
631             (A'B'C'D) + (A'BC) + (ACD) + (AB'C'D') + (ABD)
632              
633             =head1 DESCRIPTION
634              
635             This module minimizes
636             L<Boolean expressions|https://en.wikipedia.org/wiki/Boolean_algebra> using the
637             L<Quine-McCluskey algorithm|https://en.wikipedia.org/wiki/Quine%E2%80%93McCluskey_algorithm>.
638              
639             =head2 Object Methods
640              
641             =head3 new([<attribute> => value, ...])
642              
643             Creates the QuineMcCluskey object. The attributes are:
644              
645             =over 4
646              
647             =item 'width'
648              
649             The number of variables (columns) in the Boolean expression.
650              
651             This is a required attribute.
652              
653             =item 'minterms'
654              
655             An array reference of terms representing the 1-values of the
656             Boolean expression.
657              
658             =item 'maxterms'
659              
660             An array reference of terms representing the 0-values of the
661             Boolean expression. This will also indicate that you want the
662             expression in product-of-sum form, instead of the default
663             sum-of-product form.
664              
665             =item 'dontcares'
666              
667             An array reference of terms representing the don't-care-values of the
668             Boolean expression. These represent inputs that simply shouldn't happen
669             (e.g., numbers 11 through 15 in a base 10 system), and therefore don't
670             matter to the result.
671              
672             =item 'columnstring'
673              
674             Present the entire list of values of the boolean expression as a single
675             string. The values are ordered from left to right in the string. For example,
676             a simple two-variable AND equation would have a string "0001".
677              
678             =item 'dc'
679              
680             I<Default value: '-'>
681              
682             Change the representation of the don't-care character. The don't-care character
683             is used both in the columnstring, and internally as a place holder for
684             eliminated variables in the equation. Some of those internals
685             may be examined via other methods.
686              
687             =item 'title'
688              
689             A title for the problem you are solving.
690              
691             =item 'vars'
692              
693             I<Default value: ['A' .. 'Z']>
694              
695             The variable names used to form the equation. The names will be taken from
696             the leftmost first:
697              
698             my $f1 = Algorithm::QuineMcCluskey->new(
699             width => 4,
700             maxterms => [1 .. 11, 13, 15],
701             vars => ['w' .. 'z']
702             );
703              
704             The names do not have to be single characters, e.g.:
705              
706             vars => ['a1', 'a0', 'b1', 'b0']
707              
708             =back
709              
710             =head3 solve()
711              
712             Returns a string of the Boolean equation.
713              
714             For now, the form of the equation is set by the choice of terms used
715             to create the object. If you use the C<minterms> attribute, the equation
716             will be returned in sum-of-product form. If you use the C<maxterms>
717             attribute, the equation will be returned in product-of-sum form.
718              
719             Using the columnstring attribute is the same as using the C<minterms>
720             attribute as far as solve() is concerned.
721              
722             It is possible that there will be more than one equation that solves the
723             boolean expression. Therefore solve() can return a different (but equally
724             valid) equation on separate runs. You can have the full list of possible
725             equations by using L</all_solutions()>. Likewise, the terms that describe
726             the solution (before they are converted with the variable names) are
727             returned with L</get_covers()>, described below.
728              
729             =head3 all_solutions()
730              
731             Return an array of strings that represent the Boolean equation.
732              
733             It is often the case that there will be more than one equation that
734             covers the terms.
735              
736             my @sol = $q->all_solutions();
737              
738             print " ", join("\n ", @sol), "\n";
739              
740             The first equation in the list will be the equation returned by L</solve>.
741              
742              
743             =head3 complement()
744              
745             Returns a new object that's the complement of the existing object:
746              
747             my $qc = $q->complement();
748             print $qc->solve(), "\n";
749              
750             Prints
751              
752             (ABC) + (A'B'D'E) + (BD'E) + (CE')
753              
754             =head3 dual()
755              
756             Returns a new object that's the dual of the existing object:
757              
758             my $qd = $q->dual();
759             print $qd->solve(), "\n";
760              
761             Prints
762              
763             (ABCE') + (A'B'C') + (B'DE') + (C'E)
764              
765             =head3 get_primes()
766              
767             Returns the prime implicants of the boolean expression, as a hash
768             reference. The keys of the hash are the prime implicants, while
769             the values of the hash are arrays of the terms each implicant covers.
770              
771             use Algorithm::QuineMcCluskey;
772              
773             my $q = Algorithm::QuineMcCluskey->new(
774             width => 4,
775             minterms => [0, 2, 3, 4, 5, 10, 12, 13, 14, 15]
776             );
777              
778             #
779             # Remember, get_primes() returns a hash reference.
780             #
781             my $prime_ref = $q->get_primes();
782             print join(", ", sort keys %{$prime_ref}), "\n";
783              
784             prints
785              
786             -010, -10-, 0-00, 00-0, 001-, 1-10, 11--
787              
788             See the function L<Algorithm::QuineMcCluskey::Format/"chart()">
789             for an example of the prime implicant/term chart.
790              
791             =head3 get_essentials()
792              
793             Returns the essential prime implicants of the boolean expression, as an array
794             reference. The array elements are the prime implicants that are essential,
795             that is, the only ones that happen to cover certain terms in the expression.
796              
797             use Algorithm::QuineMcCluskey;
798              
799             my $q = Algorithm::QuineMcCluskey->new(
800             width => 4,
801             minterms => [0, 2, 3, 4, 5, 10, 12, 13, 14, 15]
802             );
803              
804             my $ess_ref = $q->get_essentials();
805             print join(", ", @{$ess_ref}), "\n";
806              
807             prints
808              
809             -10-, 001-, 11--
810              
811             =head3 get_covers()
812              
813             Returns all of the reduced implicant combinations that cover the booelan
814             expression.
815              
816             The implicants are in a form that combines 1, 0, and the don't-care character
817             (found and set with the C<dc> attribute). These are used by L</solve()> to
818             create a boolean equation that solves the set of C<minterms> or C<maxterms>.
819              
820             It is possible that there will be more than one equation that solves a boolean
821             expression. The solve() method returns a minimum set, and all_solutions()
822             show the complete set of solutions found by the algorithm.
823              
824             But if you want to see how the solutions match up with their associated
825             covers, you may use this:
826              
827             use Algorithm::QuineMcCluskey;
828              
829             my $q = Algorithm::QuineMcCluskey->new(
830             width => 4,
831             minterms => [0, 2, 3, 4, 5, 10, 12, 13, 14, 15]
832             );
833              
834             #
835             # get_covers() returns an array ref of arrays.
836             #
837             my $covers = $q->get_covers();
838              
839             for my $idx (0 .. $#{$covers})
840             {
841             my @cvs = sort @{$covers->[$idx]};
842              
843             #
844             # The raw ones, zeroes, and dont-care characters.
845             #
846             print "'", join("', '", @cvs), "' => ";
847              
848             #
849             # And the resulting boolean equation.
850             #
851             print $q->to_boolean(\@cvs), "\n";
852             }
853              
854             prints
855              
856             '-010', '-10-', '00-0', '001-', '11--' => (B'CD') + (BC') + (A'B'D') + (A'B'C) + (AB)
857             '-10-', '00-0', '001-', '1-10', '11--' => (BC') + (A'B'D') + (A'B'C) + (ACD') + (AB)
858             '-010', '-10-', '0-00', '001-', '11--' => (B'CD') + (BC') + (A'C'D') + (A'B'C) + (AB)
859             '-10-', '0-00', '001-', '1-10', '11--' => (BC') + (A'C'D') + (A'B'C) + (ACD') + (AB)
860              
861              
862             Note the use of the method to_boolean() in the loop. This is the method
863             solve() uses to create its equation, by passing it the first of the list
864             of covers.
865              
866             =head3 to_columnstring()
867              
868             Return a string made up of the function's column's values. Position 0 in
869             the string is the 0th row of the column, and so on. The string will consist
870             of zeros, ones, and the don't-care character (which by default is '-').
871              
872             my $column = $self->to_columnstring();
873              
874             =head1 AUTHOR
875              
876             Darren M. Kulp B<darren@kulp.ch>
877              
878             John M. Gamble B<jgamble@cpan.org> (current maintainer)
879              
880             =cut
881              
882             =head1 SEE ALSO
883              
884             =over 3
885              
886             =item
887              
888             Introduction To Logic Design, by Sajjan G. Shiva, 1998.
889              
890             =item
891              
892             Discrete Mathematics and its Applications, by Kenneth H. Rosen, 1995
893              
894             =back
895              
896             =head1 LICENSE AND COPYRIGHT
897              
898             Copyright (c) 2006 Darren Kulp. All rights reserved. This program is
899             free software; you can redistribute it and/or modify it under the same
900             terms as Perl itself.
901              
902             See L<http://dev.perl.org/licenses/> for more information.
903              
904             =cut
905              
906             1;