File Coverage

blib/lib/Algorithm/QuineMcCluskey.pm
Criterion Covered Total %
statement 177 192 92.1
branch 41 52 78.8
condition 7 9 77.7
subroutine 19 21 90.4
pod 7 12 58.3
total 251 286 87.7


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