1 /*
2  * Copyright (c) 2017 Helmut Neemann
3  * Use of this source code is governed by the GPL v3 license
4  * that can be found in the LICENSE file.
5  */
6 package de.neemann.digital.gui.components.karnaugh;
7 
8 import de.neemann.digital.analyse.expression.*;
9 import de.neemann.digital.lang.Lang;
10 
11 import java.util.ArrayList;
12 import java.util.HashSet;
13 import java.util.Iterator;
14 import java.util.List;
15 
16 /**
17  * Creates the covers needed to draw a kv map
18  */
19 public class KarnaughMap implements Iterable<KarnaughMap.Cover> {
20 
21     private final ArrayList<Cell> cells;
22     private final List<Variable> vars;
23     private final ArrayList<Cover> covers;
24     private final Header headerLeft;
25     private final Header headerRight;
26     private final Header headerBottom;
27     private final Header headerTop;
28 
29     /**
30      * Creates a new instance
31      *
32      * @param vars the variables used
33      * @param expr the expression
34      * @throws KarnaughException KarnaughException
35      */
KarnaughMap(List<Variable> vars, Expression expr)36     public KarnaughMap(List<Variable> vars, Expression expr) throws KarnaughException {
37         this(vars, expr, new MapLayout(vars.size()));
38     }
39 
40     /**
41      * Creates a new instance
42      *
43      * @param vars      the variables used
44      * @param expr      the expression
45      * @param mapLayout the layout mode of the map
46      * @throws KarnaughException KarnaughException
47      */
KarnaughMap(List<Variable> vars, Expression expr, MapLayout mapLayout)48     public KarnaughMap(List<Variable> vars, Expression expr, MapLayout mapLayout) throws KarnaughException {
49         this.vars = vars;
50         cells = new ArrayList<>();
51         covers = new ArrayList<>();
52 
53         switch (vars.size()) {
54             case 2:  // create the needed KV cells
55                 for (int row = 0; row < 2; row++)
56                     for (int col = 0; col < 2; col++)
57                         cells.add(new Cell(row, col));
58 
59                 boolean leftMode = mapLayout.getInvert(0);
60                 headerLeft = new Header(mapLayout.get(0), !leftMode, leftMode).toRows(2, this);
61                 boolean topMode = mapLayout.getInvert(1);
62                 headerTop = new Header(mapLayout.get(1), !topMode, topMode).toCols(2, this);
63                 headerRight = null;
64                 headerBottom = null;
65                 break;
66             case 3:
67                 for (int row = 0; row < 2; row++)
68                     for (int col = 0; col < 4; col++)
69                         cells.add(new Cell(row, col));
70 
71                 leftMode = mapLayout.getInvert(0);
72                 headerLeft = new Header(mapLayout.get(0), !leftMode, leftMode).toRows(4, this);
73                 topMode = mapLayout.getInvert(1);
74                 headerTop = new Header(mapLayout.get(1), !topMode, !topMode, topMode, topMode).toCols(2, this);
75                 boolean bottomMode = mapLayout.getInvert(2);
76                 headerBottom = new Header(mapLayout.get(2), !bottomMode, bottomMode, bottomMode, !bottomMode).toCols(2, this);
77                 headerRight = null;
78                 break;
79             case 4:
80                 for (int row = 0; row < 4; row++)
81                     for (int col = 0; col < 4; col++)
82                         cells.add(new Cell(row, col));
83 
84                 leftMode = mapLayout.getInvert(0);
85                 headerLeft = new Header(mapLayout.get(0), !leftMode, !leftMode, leftMode, leftMode).toRows(4, this);
86                 boolean rightMode = mapLayout.getInvert(1);
87                 headerRight = new Header(mapLayout.get(1), !rightMode, rightMode, rightMode, !rightMode).toRows(4, this);
88                 topMode = mapLayout.getInvert(2);
89                 headerTop = new Header(mapLayout.get(2), !topMode, !topMode, topMode, topMode).toCols(4, this);
90                 bottomMode = mapLayout.getInvert(3);
91                 headerBottom = new Header(mapLayout.get(3), !bottomMode, bottomMode, bottomMode, !bottomMode).toCols(4, this);
92                 break;
93             default:
94                 throw new KarnaughException(Lang.get("err_toManyVars"));
95         }
96         for (Cell c : cells)   // set the row index of the bool table to the cells
97             c.createBoolTableRow();
98 
99         addExpression(expr);   // create the covers
100     }
101 
102     /**
103      * Returns the cell at the given position
104      *
105      * @param row the row
106      * @param col the column
107      * @return the cell at this position
108      */
getCell(int row, int col)109     public Cell getCell(int row, int col) {
110         for (Cell cell : cells)
111             if (cell.is(row, col)) return cell;
112         throw new RuntimeException("cell not found");
113     }
114 
115     /**
116      * @return all cells
117      */
getCells()118     public ArrayList<Cell> getCells() {
119         return cells;
120     }
121 
addExpression(Expression expr)122     private void addExpression(Expression expr) throws KarnaughException {
123         if (expr instanceof Not || expr instanceof Variable) {
124             addCover(expr);
125         } else if (expr instanceof Operation.And) {
126             addCover(((Operation.And) expr).getExpressions());
127         } else if (expr instanceof Operation.Or) {
128             for (Expression and : ((Operation.Or) expr).getExpressions())
129                 if (and instanceof Operation.And)
130                     addCover(((Operation.And) and).getExpressions());
131                 else if (and instanceof Not || and instanceof Variable)
132                     addCover(and);
133                 else
134                     throw new KarnaughException(Lang.get("err_invalidExpression"));
135         } else if (!(expr instanceof Constant))
136             throw new KarnaughException(Lang.get("err_invalidExpression"));
137     }
138 
addCover(Expression expr)139     private void addCover(Expression expr) throws KarnaughException {
140         addCoverToCells(new Cover().add(getVarOf(expr)));
141     }
142 
addCover(ArrayList<Expression> expressions)143     private void addCover(ArrayList<Expression> expressions) throws KarnaughException {
144         Cover cover = new Cover();
145         for (Expression expr : expressions)
146             cover.add(getVarOf(expr));
147         addCoverToCells(cover);
148     }
149 
addCoverToCells(Cover cover)150     private void addCoverToCells(Cover cover) {
151         covers.add(cover);
152 
153         HashSet<Integer> insetsUsed = new HashSet<>();
154         for (Cell cell : cells)
155             cell.addCoverToCell(cover, insetsUsed);
156         for (int i = 0; i < 8; i++)
157             if (!insetsUsed.contains(i)) {
158                 cover.inset = i;
159                 break;
160             }
161     }
162 
getVarOf(Expression expression)163     private VarState getVarOf(Expression expression) throws KarnaughException {
164         String name = null;
165         boolean invert = false;
166         if (expression instanceof Variable) {
167             name = ((Variable) expression).getIdentifier();
168             invert = false;
169         } else if (expression instanceof Not) {
170             Expression ex = ((Not) expression).getExpression();
171             if (ex instanceof Variable) {
172                 name = ((Variable) ex).getIdentifier();
173                 invert = true;
174             }
175         }
176         if (name == null) throw new KarnaughException(Lang.get("err_invalidExpression"));
177 
178         int var = vars.indexOf(new Variable(name));
179         if (var < 0) throw new KarnaughException(Lang.get("err_invalidExpression"));
180 
181         return new VarState(var, invert);
182     }
183 
184     @Override
iterator()185     public Iterator<Cover> iterator() {
186         return covers.iterator();
187     }
188 
189     /**
190      * @return the number of covers
191      */
size()192     public int size() {
193         return covers.size();
194     }
195 
196     /**
197      * @return the rows of the table
198      */
getRows()199     public int getRows() {
200         return headerLeft.size();
201     }
202 
203     /**
204      * @return the cols of the table
205      */
getColumns()206     public int getColumns() {
207         return headerTop.size();
208     }
209 
210     /**
211      * @return the left header
212      */
getHeaderLeft()213     public Header getHeaderLeft() {
214         return headerLeft;
215     }
216 
217     /**
218      * @return the right header
219      */
getHeaderRight()220     public Header getHeaderRight() {
221         return headerRight;
222     }
223 
224     /**
225      * @return the bottom header
226      */
getHeaderBottom()227     public Header getHeaderBottom() {
228         return headerBottom;
229     }
230 
231     /**
232      * @return the top header
233      */
getHeaderTop()234     public Header getHeaderTop() {
235         return headerTop;
236     }
237 
238     /**
239      * a single cell in the kv map
240      */
241     public static final class Cell {
242         private final int row;
243         private final int col;
244         private final ArrayList<VarState> minTerm; // min term  of the cell
245         private final ArrayList<Cover> covers;
246         private int boolTableRow;
247 
Cell(int row, int col)248         private Cell(int row, int col) {
249             this.row = row;
250             this.col = col;
251             minTerm = new ArrayList<>();
252             covers = new ArrayList<>();
253         }
254 
add(VarState varState)255         private void add(VarState varState) {
256             minTerm.add(varState);
257         }
258 
is(int row, int col)259         private boolean is(int row, int col) {
260             return (this.row == row) && (this.col == col);
261         }
262 
addCoverToCell(Cover cover, HashSet<Integer> insetsUsed)263         private void addCoverToCell(Cover cover, HashSet<Integer> insetsUsed) {
264             for (VarState s : minTerm)
265                 if (cover.contains(s.not()))
266                     return;
267 
268             for (Cover c : covers)
269                 insetsUsed.add(c.inset);
270 
271             covers.add(cover);
272             cover.incCellCount();
273         }
274 
contains(Cover cover)275         private boolean contains(Cover cover) {
276             return covers.contains(cover);
277         }
278 
createBoolTableRow()279         private void createBoolTableRow() {
280             int tableCols = minTerm.size();
281             boolTableRow = 0;
282             for (VarState i : minTerm) {
283                 if (!i.invert)
284                     boolTableRow += (1 << (tableCols - i.num - 1));
285             }
286         }
287 
288         /**
289          * @return the row
290          */
getRow()291         public int getRow() {
292             return row;
293         }
294 
295         /**
296          * @return the column
297          */
getCol()298         public int getCol() {
299             return col;
300         }
301 
302         /**
303          * @return the row in the truth table this cell belongs to
304          */
getBoolTableRow()305         public int getBoolTableRow() {
306             return boolTableRow;
307         }
308 
isVarInMinTerm(int var, boolean invert)309         boolean isVarInMinTerm(int var, boolean invert) {
310             return minTerm.contains(new VarState(var, invert));
311         }
312     }
313 
314     private static final class VarState {
315         private final int num;        // number of the variable in the vars list
316         private final boolean invert; // true if variable is inverted
317 
VarState(int num, boolean invert)318         private VarState(int num, boolean invert) {
319             this.num = num;
320             this.invert = invert;
321         }
322 
323         @Override
equals(Object o)324         public boolean equals(Object o) {
325             if (this == o) return true;
326             if (o == null || getClass() != o.getClass()) return false;
327 
328             VarState varState = (VarState) o;
329 
330             if (num != varState.num) return false;
331             return invert == varState.invert;
332         }
333 
334         @Override
hashCode()335         public int hashCode() {
336             int result = num;
337             result = 31 * result + (invert ? 1 : 0);
338             return result;
339         }
340 
not()341         private VarState not() {
342             return new VarState(num, !invert);
343         }
344     }
345 
346     /**
347      * a cover in the kv table
348      */
349     public final class Cover {
350         private final ArrayList<VarState> varStates;
351         private Pos pos;
352         private int cellCount;
353         private int inset = 0;
354 
Cover()355         private Cover() {
356             varStates = new ArrayList<>();
357         }
358 
add(VarState varState)359         private Cover add(VarState varState) {
360             varStates.add(varState);
361             return this;
362         }
363 
contains(VarState s)364         private boolean contains(VarState s) {
365             return varStates.contains(s);
366         }
367 
368         /**
369          * @return the position of the cover. Caution: Returns a bounding box!
370          */
getPos()371         public Pos getPos() {
372             if (pos == null) {
373                 int rowMin = Integer.MAX_VALUE;
374                 int rowMax = Integer.MIN_VALUE;
375                 int colMin = Integer.MAX_VALUE;
376                 int colMax = Integer.MIN_VALUE;
377                 for (Cell c : cells) {
378                     if (c.contains(this)) {
379                         if (c.row > rowMax) rowMax = c.row;
380                         if (c.row < rowMin) rowMin = c.row;
381                         if (c.col > colMax) colMax = c.col;
382                         if (c.col < colMin) colMin = c.col;
383                     }
384                 }
385                 int width = colMax - colMin + 1;
386                 int height = rowMax - rowMin + 1;
387                 pos = new Pos(rowMin, colMin, width, height);
388             }
389             return pos;
390         }
391 
incCellCount()392         private void incCellCount() {
393             cellCount++;
394         }
395 
396         /**
397          * @return the size of a cover
398          */
getSize()399         public int getSize() {
400             return cellCount;
401         }
402 
403         /**
404          * @return the inset of this cover;
405          */
getInset()406         public int getInset() {
407             return inset;
408         }
409 
410         /**
411          * @return true if cover is split, thus the cover is wrapping around the border
412          */
isDisconnected()413         public boolean isDisconnected() {
414             return getPos().width * getPos().height > cellCount;
415         }
416 
417         /**
418          * @return covers only the edges
419          */
onlyEdges()420         public boolean onlyEdges() {
421             return getPos().width * getPos().height == 16 && cellCount == 4;
422         }
423 
424         /**
425          * @return true if disconnected cover is vertical divided
426          */
isVerticalDivided()427         public boolean isVerticalDivided() {
428             Pos p = getPos();
429             if (p.width * p.height == 16 && cellCount == 8)
430                 return getCell(1, 0).contains(this);
431             else
432                 return p.getWidth() > p.getHeight();
433         }
434     }
435 
436     /**
437      * The position of the cover.
438      * If a cover is wrapping around the borders the bounding box is returned!
439      */
440     public static final class Pos {
441         private final int row;
442         private final int col;
443         private final int width;
444         private final int height;
445 
Pos(int row, int col, int width, int height)446         private Pos(int row, int col, int width, int height) {
447             this.row = row;
448             this.col = col;
449             this.width = width;
450             this.height = height;
451         }
452 
453 
454         /**
455          * @return the row
456          */
getRow()457         public int getRow() {
458             return row;
459         }
460 
461         /**
462          * @return the column
463          */
getCol()464         public int getCol() {
465             return col;
466         }
467 
468         /**
469          * @return the width of the cover
470          */
getWidth()471         public int getWidth() {
472             return width;
473         }
474 
475         /**
476          * @return the height of the cover
477          */
getHeight()478         public int getHeight() {
479             return height;
480         }
481 
482     }
483 
484     /**
485      * Defines the variables in the borders of the KV map
486      */
487     public static final class Header {
488         private final int var;
489         private final boolean[] invert;
490 
Header(int var, boolean... invert)491         private Header(int var, boolean... invert) {
492             this.var = var;
493             this.invert = invert;
494         }
495 
496         /**
497          * @return the variable
498          */
getVar()499         public int getVar() {
500             return var;
501         }
502 
503         /**
504          * @return the size
505          */
size()506         public int size() {
507             return invert.length;
508         }
509 
510         /**
511          * Returns the variable state
512          *
513          * @param i the index of the row column
514          * @return true if inverted variable
515          */
getInvert(int i)516         public boolean getInvert(int i) {
517             return invert[i];
518         }
519 
520         /**
521          * Initializes the table according to the selected header.
522          *
523          * @param cols the number columns in the table
524          * @param kmap the k-map to use
525          * @return this for chained calls
526          */
toRows(int cols, KarnaughMap kmap)527         public Header toRows(int cols, KarnaughMap kmap) {
528             for (int row = 0; row < invert.length; row++)
529                 for (int col = 0; col < cols; col++)
530                     kmap.getCell(row, col).add(new VarState(var, invert[row]));
531             return this;
532         }
533 
534         /**
535          * Initializes the table according to the selected header.
536          *
537          * @param rows the number rows in the table
538          * @param kmap the k-map to use
539          * @return this for chained calls
540          */
toCols(int rows, KarnaughMap kmap)541         public Header toCols(int rows, KarnaughMap kmap) {
542             for (int col = 0; col < invert.length; col++)
543                 for (int row = 0; row < rows; row++)
544                     kmap.getCell(row, col).add(new VarState(var, invert[col]));
545             return this;
546         }
547 
548     }
549 }
550