pimath 0.1.39 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/pimath.js +3127 -2865
  2. package/dist/pimath.js.map +1 -1
  3. package/package.json +16 -12
  4. package/src/algebra/equation.ts +558 -0
  5. package/src/algebra/equationSolver.ts +488 -0
  6. package/src/algebra/factor.ts +338 -0
  7. package/src/algebra/index.ts +11 -0
  8. package/src/algebra/linearSystem.ts +439 -0
  9. package/src/algebra/logicalset.ts +255 -0
  10. package/src/algebra/matrix.ts +474 -0
  11. package/src/algebra/monom.ts +977 -0
  12. package/src/algebra/operations.ts +23 -0
  13. package/src/algebra/polyFactor.ts +668 -0
  14. package/src/algebra/polynom.ts +1247 -0
  15. package/src/analyze/index.ts +4 -0
  16. package/src/analyze/solution.ts +178 -0
  17. package/src/analyze/tableOfSigns.ts +30 -0
  18. package/src/coefficients/fraction.ts +718 -0
  19. package/src/coefficients/index.ts +4 -0
  20. package/src/coefficients/root.ts +346 -0
  21. package/src/geometry/TupleN.ts +128 -0
  22. package/src/geometry/circle.ts +456 -0
  23. package/src/geometry/geomMath.ts +71 -0
  24. package/src/geometry/index.ts +11 -0
  25. package/src/geometry/line.ts +653 -0
  26. package/src/geometry/line3.ts +211 -0
  27. package/src/geometry/plane3.ts +179 -0
  28. package/src/geometry/point.ts +104 -0
  29. package/src/geometry/sphere3.ts +214 -0
  30. package/src/geometry/triangle.ts +482 -0
  31. package/src/geometry/vector.ts +225 -0
  32. package/src/helpers.ts +35 -0
  33. package/src/index.ts +61 -0
  34. package/src/numeric.ts +196 -0
  35. package/src/pimath.interface.ts +162 -0
  36. package/src/randomization/algebra/rndEquation.ts +41 -0
  37. package/src/randomization/algebra/rndMonom.ts +39 -0
  38. package/src/randomization/algebra/rndPolynom.ts +100 -0
  39. package/src/randomization/coefficient/rndFraction.ts +38 -0
  40. package/src/randomization/geometry/rndCircle.ts +27 -0
  41. package/src/randomization/geometry/rndLine.ts +35 -0
  42. package/src/randomization/geometry/rndLine3.ts +27 -0
  43. package/src/randomization/geometry/rndVector.ts +63 -0
  44. package/src/randomization/random.ts +89 -0
  45. package/src/randomization/rndHelpers.ts +102 -0
  46. package/src/randomization/rndTypes.ts +67 -0
  47. package/types/algebra/equation.d.ts +18 -17
  48. package/types/algebra/equation.d.ts.map +1 -1
  49. package/types/algebra/equationSolver.d.ts +7 -3
  50. package/types/algebra/equationSolver.d.ts.map +1 -1
  51. package/types/algebra/factor.d.ts +1 -1
  52. package/types/algebra/factor.d.ts.map +1 -1
  53. package/types/algebra/linearSystem.d.ts +23 -6
  54. package/types/algebra/linearSystem.d.ts.map +1 -1
  55. package/types/algebra/logicalset.d.ts +1 -1
  56. package/types/algebra/logicalset.d.ts.map +1 -1
  57. package/types/algebra/monom.d.ts +1 -6
  58. package/types/algebra/monom.d.ts.map +1 -1
  59. package/types/algebra/operations.d.ts.map +1 -1
  60. package/types/algebra/polyFactor.d.ts +9 -4
  61. package/types/algebra/polyFactor.d.ts.map +1 -1
  62. package/types/algebra/polynom.d.ts +10 -7
  63. package/types/algebra/polynom.d.ts.map +1 -1
  64. package/types/analyze/index.d.ts +2 -0
  65. package/types/analyze/index.d.ts.map +1 -0
  66. package/types/analyze/solution.d.ts +27 -0
  67. package/types/analyze/solution.d.ts.map +1 -0
  68. package/types/analyze/tableOfSigns.d.ts +9 -0
  69. package/types/analyze/tableOfSigns.d.ts.map +1 -0
  70. package/types/coefficients/fraction.d.ts +14 -12
  71. package/types/coefficients/fraction.d.ts.map +1 -1
  72. package/types/coefficients/index.d.ts +1 -1
  73. package/types/coefficients/index.d.ts.map +1 -1
  74. package/types/coefficients/root.d.ts +41 -0
  75. package/types/coefficients/root.d.ts.map +1 -0
  76. package/types/geometry/TupleAbstract.d.ts +22 -0
  77. package/types/geometry/TupleAbstract.d.ts.map +1 -0
  78. package/types/geometry/TupleN.d.ts +24 -0
  79. package/types/geometry/TupleN.d.ts.map +1 -0
  80. package/types/geometry/circle.d.ts +26 -17
  81. package/types/geometry/circle.d.ts.map +1 -1
  82. package/types/geometry/geomMath.d.ts +2 -1
  83. package/types/geometry/geomMath.d.ts.map +1 -1
  84. package/types/geometry/index.d.ts.map +1 -1
  85. package/types/geometry/line.d.ts +21 -30
  86. package/types/geometry/line.d.ts.map +1 -1
  87. package/types/geometry/line3.d.ts +19 -19
  88. package/types/geometry/line3.d.ts.map +1 -1
  89. package/types/geometry/matrix.d.ts +11 -11
  90. package/types/geometry/plane3.d.ts +9 -9
  91. package/types/geometry/plane3.d.ts.map +1 -1
  92. package/types/geometry/point.d.ts +12 -7
  93. package/types/geometry/point.d.ts.map +1 -1
  94. package/types/geometry/triangle.d.ts +68 -23
  95. package/types/geometry/triangle.d.ts.map +1 -1
  96. package/types/geometry/vector.d.ts +24 -44
  97. package/types/geometry/vector.d.ts.map +1 -1
  98. package/types/helpers.d.ts +1 -0
  99. package/types/helpers.d.ts.map +1 -1
  100. package/types/index.d.ts +6 -4
  101. package/types/index.d.ts.map +1 -1
  102. package/types/numeric.d.ts +2 -0
  103. package/types/numeric.d.ts.map +1 -1
  104. package/types/pimath.interface.d.ts +38 -44
  105. package/types/pimath.interface.d.ts.map +1 -1
  106. package/types/randomization/algebra/rndPolynom.d.ts.map +1 -1
  107. package/types/randomization/coefficient/rndFraction.d.ts +1 -1
  108. package/types/randomization/coefficient/rndFraction.d.ts.map +1 -1
  109. package/types/randomization/geometry/rndLine.d.ts.map +1 -1
  110. package/types/randomization/random.d.ts +3 -2
  111. package/types/randomization/random.d.ts.map +1 -1
  112. package/types/randomization/rndTypes.d.ts +15 -10
  113. package/types/randomization/rndTypes.d.ts.map +1 -1
@@ -0,0 +1,439 @@
1
+ import type {IAlgebra, IEquation, InputValue, IPiMathObject, literalType} from "../pimath.interface"
2
+ import {Fraction} from "../coefficients"
3
+ import {Equation} from "./equation"
4
+ import {Monom} from "./monom"
5
+ import {Polynom} from "./polynom"
6
+ import {Numeric} from "../numeric"
7
+ import type {Solution} from "../analyze/solution"
8
+
9
+ export class LinearSystem implements IPiMathObject<LinearSystem>,
10
+ IEquation<LinearSystem>,
11
+ IAlgebra<LinearSystem> {
12
+
13
+ #equations: Equation[]
14
+ // Solve steps for TeX output.
15
+ #steps: string[] = []
16
+ // Determine the letters in the linear asSystem, usually ['x', 'y']
17
+ #variables: string[]
18
+
19
+ constructor(...values: (string | Equation)[]) {
20
+ this.#equations = []
21
+ this.#variables = []
22
+
23
+ if (values.length > 0) {
24
+ this.parse(...values)
25
+ }
26
+
27
+ return this
28
+ }
29
+
30
+ public parse = (...equations: (string | Equation)[]): this => {
31
+ // make the original equations
32
+ this.#equations = equations.map(value => new Equation(value))
33
+
34
+ // get the letters.
35
+ this.#findLetters()
36
+ return this
37
+ }
38
+
39
+ public clone = (): LinearSystem => {
40
+ return new LinearSystem()
41
+ .parse(...this.#equations.map(equ => equ.clone()))
42
+ }
43
+
44
+ public get tex(): string {
45
+ // Build the array of values.
46
+ // Reorder
47
+ // This clone the asSystem :!!!
48
+ //TODO: Avoid cloning this linear asSystem
49
+ const LS = this.clone().reorder()
50
+
51
+ return this.buildTex(LS.equations)
52
+ }
53
+
54
+ get display() {
55
+ // TODO : LinearSystem - display: implement the display of the linear asSystem
56
+ return this.tex + 'as display'
57
+ }
58
+
59
+ public static fromMatrix(
60
+ matrix: InputValue<Fraction>[][],
61
+ letters = 'xyz'): LinearSystem {
62
+ // Check that each row has the same number of columns
63
+ const cols = matrix[0].length
64
+ if (matrix.some(row => row.length !== cols)) {
65
+ throw new Error("All rows must have the same number of columns")
66
+ }
67
+
68
+ // Determine the default letters. The number of letters are cols-1
69
+ const vars = letters.split('')
70
+ .splice(0, cols - 1)
71
+
72
+ // Create a new LinearSystem
73
+ return new LinearSystem(
74
+ ...matrix.map(row => {
75
+ const P = new Polynom(vars.join(''), ...row)
76
+ return new Equation(P, 0)
77
+ })
78
+ )
79
+
80
+ }
81
+
82
+ public add(value: InputValue<LinearSystem | Equation | Polynom>, index?: number): this {
83
+ if (value instanceof LinearSystem) {
84
+ const length = value.equations.length
85
+ if (length !== this.#equations.length) {
86
+ throw new Error("The number of equations must be the same")
87
+ }
88
+
89
+ for (let i = 0; i < length; i++) {
90
+ this.#equations[i].add(value.equations[i])
91
+ }
92
+ } else {
93
+ if (index === undefined || index < 0 || index >= this.#equations.length) {
94
+ throw new Error("Index out of range")
95
+ }
96
+ const equ = new Equation(value)
97
+ this.#equations[index].add(equ)
98
+ }
99
+
100
+ return this
101
+ }
102
+
103
+ public buildTex = (equations: Equation[], operators?: (string[])[]): string => {
104
+ let equStr: string[]
105
+ let m: Monom
106
+ let letters: string[] = []
107
+ const equArray: string[] = []
108
+
109
+ // Get the letters from the linear asSystem
110
+ for (const equ of equations) {
111
+ letters = letters.concat(equ.letters())
112
+ }
113
+
114
+ letters = [...new Set(letters)]
115
+ letters.sort()
116
+
117
+ for (let i = 0; i < equations.length; i++) {
118
+ const equ = equations[i]
119
+
120
+ equStr = []
121
+ for (const L of letters) {
122
+ m = equ.left.monomByLetter(L)
123
+
124
+ if (equStr.length === 0) {
125
+ equStr.push(m.isZero() ? '' : m.tex)
126
+ } else {
127
+ equStr.push(m.isZero() ? '' : ((m.coefficient.sign() === 1) ? '+' : '') + m.tex)
128
+ }
129
+ }
130
+
131
+ // Add the equal sign
132
+ equStr.push('=')
133
+
134
+ // Add the right hand part of the equation (should be only a number, because it has been reordered)
135
+ equStr.push(equ.right.tex)
136
+
137
+ // Add the operations if existing
138
+ if (operators?.[i] !== undefined) {
139
+ // add extra space at the end of the equation
140
+ equStr[equStr.length - 1] = equStr[equStr.length - 1] + ' \\phantom{\\quad}'
141
+ for (const o of operators[i]) {
142
+ equStr.push(`\\ \\cdot\\ ${o.startsWith('-') ? "\\left(" + o + "\\right)" : o}`)
143
+ }
144
+ }
145
+
146
+ // Add to the list.
147
+ equArray.push(equStr.join('&'))
148
+ }
149
+
150
+ let operatorsColumns = 0
151
+ if (operators !== undefined && operators.length > 0) {
152
+ operatorsColumns = operators[0].length
153
+ }
154
+
155
+ return `\\left\\{\\begin{array}{${"r".repeat(letters.length)}cl ${"|l".repeat(operatorsColumns)}}${equArray.join('\\\\ ')}\\end{array}\\right.`
156
+ }
157
+
158
+ public degree(letter?: string): Fraction {
159
+ return Fraction.max(...this.#equations.map(equ => equ.degree(letter)))
160
+ }
161
+
162
+ // ------------------------------------------
163
+ public get equations(): Equation[] {
164
+ return this.#equations
165
+ }
166
+
167
+ public set equations(value) {
168
+ this.#equations = value
169
+
170
+ // update the variables.
171
+ this.#findLetters()
172
+ }
173
+
174
+ public evaluate(values: InputValue<Fraction> | literalType<number | Fraction>, asNumeric?: boolean): number | Fraction {
175
+ throw new Error("Method not implemented.")
176
+ }
177
+
178
+ public hasVariable(letter: string): boolean {
179
+ return this.#variables.includes(letter)
180
+ }
181
+
182
+ public isEqual(value: LinearSystem): boolean {
183
+ return this.equations.every((equ, index) => equ.isEqual(value.equations[index]))
184
+ }
185
+
186
+ public get isSolvable(): boolean {
187
+ const V = this.variables
188
+
189
+ // TODO: in some case, it is possible to resolve systems if there isn't the isSame number of vars and equations
190
+ if (V.length !== this.#equations.length) {
191
+ return false
192
+ }
193
+
194
+ //TODO: Must check if two equations isn't a linear combination of the others ?
195
+
196
+ return true
197
+ }
198
+
199
+ public get matrix(): [Fraction[][], Fraction[]] {
200
+ //TODO: use Matrix class
201
+ return this.#makeMatrix()
202
+ }
203
+
204
+ public mergeEquations(equation1: { id: number, factor: InputValue<Fraction> }, equation2: {
205
+ id: number,
206
+ factor: number
207
+ }): Equation {
208
+ // Set and clone the equations.
209
+ const eq1multiplied = this.equations[equation1.id].clone().multiply(equation1.factor)
210
+ const eq2multiplied = this.equations[equation2.id].clone().multiply(equation2.factor)
211
+
212
+ // Add both equations together.
213
+ return eq1multiplied.add(eq2multiplied)
214
+ }
215
+
216
+ public multiply(value: InputValue<Fraction> | InputValue<Fraction>[], index?: number): this {
217
+ // Multiply the asSystem by a number
218
+ // the value can be an array of numbers
219
+ // the value can be a number and the index of the equation to multiply
220
+ if (Array.isArray(value)) {
221
+ if (value.length !== this.#equations.length) {
222
+ throw new Error("The number of values must be the same as the number of equations")
223
+ }
224
+
225
+ for (let i = 0; i < value.length; i++) {
226
+ this.#equations[i].multiply(value[i])
227
+ }
228
+ return this
229
+ }
230
+
231
+ if (index === undefined || index < 0 || index >= this.#equations.length) {
232
+ throw new Error("Index out of range")
233
+ }
234
+
235
+ this.#equations[index].multiply(value)
236
+
237
+ return this
238
+ }
239
+
240
+ public reduce(): this {
241
+ // reduce all equations at once.
242
+ this.equations.forEach(equ=>equ.reduce())
243
+ return this
244
+ }
245
+
246
+ // ------------------------------------------
247
+ public reorder = (): this => {
248
+ for (const E of this.#equations) {
249
+ E.reorder()
250
+ }
251
+
252
+ return this
253
+ }
254
+
255
+ solve(): Solution[] {
256
+ // TODO : à retravailler, car ce n'est ni l'endroit, ni l'intérêt de l'avoir ici.
257
+ // 1. search in the equations if a variable has two same or opposite value = candidate for merging
258
+ // 2. if 1 is false, search for a variable that has coefficient one
259
+ // 3. if 2 is false, search for a variable that has a coefficient multiple of another.
260
+ // 4. if 3 is false, multiply both lines.
261
+ // => merge the equations and cycle.
262
+ const output: string[] = [this.tex]
263
+
264
+ const LS = this.clone()
265
+
266
+ while (LS.variables.length>1){
267
+ const letter = LS.variables[LS.variables.length-1]
268
+ const emptyLS = new LinearSystem()
269
+ const factors = LS.solve_compute_factors(letter).slice(0, LS.variables.length-1)
270
+ factors.forEach(factor=> {
271
+ emptyLS.equations.push(LS.mergeEquations(...factor))
272
+ })
273
+
274
+ LS.equations = emptyLS.equations
275
+
276
+ output.push(LS.tex)
277
+
278
+ // add the same but with a reduced value.
279
+ LS.reduce()
280
+ output.push(LS.tex)
281
+ }
282
+
283
+ return []
284
+ }
285
+
286
+ public solveMatrix = (): Fraction[] => {
287
+ const [matrix, vector] = this.matrix
288
+ // Solve the matrix
289
+
290
+ // Make the augmented matrix (matrix + vector)
291
+ const augmentedMatrix: Fraction[][] = matrix.map((row, index) => [...row, vector[index]])
292
+
293
+ // Reduce the matrix
294
+ for (let i = 0; i < matrix.length; i++) {
295
+ // Find the pivot (the first non-zero element in the row)
296
+ let pivot = augmentedMatrix[i][i].clone()
297
+ if (pivot.isZero()) {
298
+ // throw new Error('Divide by zero !')
299
+ // Search a line below that would add it.
300
+ const row_to_add = augmentedMatrix
301
+ .find((row, index) => {
302
+ return index > i && !row[i].isZero()
303
+ })
304
+
305
+ if (row_to_add) {
306
+ augmentedMatrix[i].forEach((value, index) => value.add(row_to_add[index]))
307
+ pivot = augmentedMatrix[i][i].clone()
308
+ } else {
309
+ throw new Error('Unsolvable...')
310
+ }
311
+
312
+
313
+ }
314
+
315
+ // Normalize the row: divide all elements by the pivot
316
+ // the pivot is now 1
317
+ augmentedMatrix[i] = augmentedMatrix[i].map(x => x.divide(pivot))
318
+
319
+ // reduce the other rows using the pivot.
320
+ for (let j = 0; j < matrix.length; j++) {
321
+ if (j === i) {
322
+ continue
323
+ }
324
+
325
+ const factor = augmentedMatrix[j][i].clone().opposite()
326
+ for (let k = 0; k < augmentedMatrix[j].length; k++) {
327
+ augmentedMatrix[j][k].add(augmentedMatrix[i][k].clone().multiply(factor))
328
+ }
329
+
330
+ // Check if the asSystem is undetermined (no solution or infinite solutions)
331
+ // the j line must not be all zeros
332
+ // the last element must be zero => the asSystem is undetermined
333
+ // the last element must not be zero => the asSystem is impossible
334
+ if (augmentedMatrix[j].slice(0, augmentedMatrix[j].length - 1).every(x => x.isZero())) {
335
+ if (augmentedMatrix[j][augmentedMatrix[j].length - 1].isZero()) {
336
+ return [new Fraction().infinite()]
337
+ } else {
338
+ return []
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ return augmentedMatrix.map(x => x[x.length - 1])
345
+ }
346
+
347
+ solve_compute_factors(letter: string):
348
+ [{ id: number, factor: number }, { id: number, factor: number }][] {
349
+ // when solving, every monoms with a variable is on the left !
350
+ // and every coefficient are relative numbers.
351
+ const result: [{ id: number, factor: number }, { id: number, factor: number }][] = []
352
+ const coefficients = this.equations.map(equ => equ.left.monomByLetter(letter).coefficient.value)
353
+
354
+ // search for a factor
355
+ coefficients.forEach((reference, index) => {
356
+ for (let i = index + 1; i < coefficients.length; i++) {
357
+ const lcm = Numeric.lcm(reference, coefficients[i])
358
+
359
+ const sign = reference < 0 ? -1 : 1
360
+ result.push([
361
+ {
362
+ id: index, factor: sign * lcm / reference
363
+ }, {
364
+ id: i, factor: -sign * lcm / coefficients[i]
365
+ }])
366
+ }
367
+ })
368
+
369
+ // Sort the value: prefer the smallest absolute values (1/-1, 2/-2, ...)
370
+ return result.sort((a, b) => {
371
+ return (Math.abs(a[0].factor) + Math.abs(a[1].factor)) - (Math.abs(b[0].factor) + Math.abs(b[1].factor))
372
+ })
373
+ }
374
+
375
+ public subtract(value: InputValue<LinearSystem | Equation | Polynom>, index?: number): this {
376
+ if (value instanceof LinearSystem) {
377
+ const length = value.equations.length
378
+ if (length !== this.#equations.length) {
379
+ throw new Error("The number of equations must be the same")
380
+ }
381
+
382
+ for (let i = 0; i < length; i++) {
383
+ this.#equations[i].subtract(value.equations[i])
384
+ }
385
+ } else {
386
+ if (index === undefined || index < 0 || index >= this.#equations.length) {
387
+ throw new Error("Index out of range")
388
+ }
389
+ const equ = new Equation(value)
390
+ this.#equations[index].subtract(equ)
391
+ }
392
+
393
+ return this
394
+ }
395
+
396
+ public get variables(): string[] {
397
+ return this.#variables
398
+ }
399
+
400
+ public set variables(value: string | string[]) {
401
+ const vars = (typeof value === "string") ? value.split('') : [...value]
402
+ vars.sort()
403
+ this.#variables = vars
404
+ }
405
+
406
+ #findLetters = (): this => {
407
+ this.#variables = this.#equations.reduce((acc: string[], equ) => {
408
+ return [...new Set([...acc, ...equ.variables])]
409
+ }, [])
410
+
411
+ this.#variables.sort()
412
+ return this
413
+ }
414
+
415
+ #makeMatrix = (): [Fraction[][], Fraction[]] => {
416
+ // Make the matrix
417
+ const matrix: Fraction[][] = []
418
+ const vector: Fraction[] = []
419
+
420
+ for (const E of this.#equations) {
421
+ const row: Fraction[] = []
422
+
423
+ const equ = E.clone().reorder()
424
+ for (const L of this.variables) {
425
+ const m = equ.left.monomByLetter(L)
426
+ row.push(m.coefficient)
427
+ }
428
+
429
+ // Add the "no letter part"
430
+ vector.push(equ.right.monoms[0].coefficient)
431
+
432
+ // Add to the matrix
433
+ matrix.push(row)
434
+ }
435
+
436
+ return [matrix, vector]
437
+ }
438
+
439
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Polynom module contains everything necessary to handle polynoms.
3
+ * @module Logicalset
4
+ */
5
+ import {ShutingYard, ShutingyardMode} from "piexpression"
6
+
7
+ /**
8
+ * Polynom class can handle polynoms, reorder, resolve, ...
9
+ */
10
+ export class LogicalSet {
11
+ #rpn: { token: string, tokenType: string }[]
12
+
13
+ /**
14
+ *
15
+ * @param {string} value (optional) Default polynom to parse on class creation
16
+ */
17
+ constructor(value?: string) {
18
+ this.#rpn = []
19
+
20
+ if (value !== undefined) {
21
+ this.parse(value)
22
+ }
23
+
24
+ return this
25
+ }
26
+
27
+ parse = (value: string): this => {
28
+ // Parse the updated value to the shutingyard algorithm
29
+ this.#rpn = new ShutingYard(ShutingyardMode.SET)
30
+ .parse(value)
31
+ .rpn
32
+
33
+ return this
34
+ }
35
+
36
+ get tex(): string {
37
+ const varStack: { token: string, tokenType: string }[] = []
38
+
39
+ for (const token of this.#rpn) {
40
+ if (token.tokenType === 'variable') {
41
+ varStack.push(token)
42
+ } else {
43
+ switch (token.token) {
44
+ case '&':
45
+ if (varStack.length >= 2) {
46
+ const second = varStack.pop(),
47
+ first = varStack.pop()
48
+
49
+ if (second && first) {
50
+ if (first.tokenType === 'mix') {
51
+ first.token = `( ${first.token} )`
52
+ }
53
+ if (second.tokenType === 'mix') {
54
+ second.token = `( ${second.token} )`
55
+ }
56
+ varStack.push({token: `${first.token} \\cap ${second.token}`, tokenType: 'mix'})
57
+ }
58
+ }
59
+ break
60
+ case '|':
61
+ if (varStack.length >= 2) {
62
+ const second = varStack.pop(),
63
+ first = varStack.pop()
64
+
65
+ if (second && first) {
66
+ if (first.tokenType === 'mix') {
67
+ first.token = `( ${first.token} )`
68
+ }
69
+ if (second.tokenType === 'mix') {
70
+ second.token = `( ${second.token} )`
71
+ }
72
+ varStack.push({token: `${first.token} \\cup ${second.token}`, tokenType: 'mix'})
73
+ }
74
+ }
75
+ break
76
+ case '-':
77
+ if (varStack.length >= 2) {
78
+ const second = varStack.pop(),
79
+ first = varStack.pop()
80
+
81
+ if (second && first) {
82
+ if (first.tokenType === 'mix') {
83
+ first.token = `( ${first.token} )`
84
+ }
85
+ if (second.tokenType === 'mix') {
86
+ second.token = `( ${second.token} )`
87
+ }
88
+ varStack.push({token: `${first.token} \\setminus ${second.token}`, tokenType: 'mix'})
89
+ }
90
+ }
91
+ break
92
+ case '!':
93
+ if (varStack.length >= 1) {
94
+ const first = varStack.pop()
95
+
96
+ if (first) {
97
+ varStack.push({token: `\\overline{ ${first.token} }`, tokenType: 'variable'})
98
+ }
99
+ }
100
+ break
101
+ }
102
+ }
103
+ }
104
+
105
+ return varStack[0].token
106
+ }
107
+
108
+ evaluate(values: Record<string, boolean>): boolean {
109
+ // Add missing key(s) and set them as false by default.
110
+ this.variables.forEach(key => {
111
+ if (!Object.hasOwn(values, key)) {
112
+ values[key] = false
113
+ }
114
+ })
115
+
116
+ const stack: boolean[] = []
117
+ for (const token of this.#rpn) {
118
+ if (token.tokenType === 'variable') {
119
+ stack.push(values[token.token])
120
+ } else if (token.tokenType === 'operation') {
121
+ if (token.token === '!') {
122
+ // need only one item from stack
123
+ if (stack.length >= 1) {
124
+ const a = stack.pop()
125
+ stack.push(!a)
126
+ } else {
127
+ return false
128
+ }
129
+ } else {
130
+ // All other operations needs two items from stack
131
+ const a = stack.pop()
132
+ const b = stack.pop()
133
+ if (a !== undefined && b !== undefined) {
134
+ switch (token.token) {
135
+ case "&":
136
+ stack.push(a && b)
137
+ break
138
+ case "|":
139
+ stack.push(a || b)
140
+ break
141
+ case "-":
142
+ return false
143
+ }
144
+
145
+ } else {
146
+ return false
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ return stack.length === 1 && stack[0]
153
+ }
154
+
155
+ get rpn(): { token: string, tokenType: string }[] {
156
+ return this.#rpn
157
+ }
158
+
159
+ get variables(): string[] {
160
+ return this.#rpn
161
+ .filter(value => value.tokenType === 'variable')
162
+ .map(value => value.token)
163
+ }
164
+
165
+ vennAB(): string[] {
166
+ return this.#evaluateAsVenn({
167
+ A: ['A', 'AB'],
168
+ B: ['B', 'AB']
169
+ },
170
+ ['A', 'B', 'AB', 'E']
171
+ )
172
+ }
173
+
174
+ vennABC(): string[] {
175
+ return this.#evaluateAsVenn({
176
+ A: ['A', 'AB', 'AC', 'ABC'],
177
+ B: ['B', 'AB', 'BC', 'ABC'],
178
+ C: ['C', 'AC', 'BC', 'ABC']
179
+ },
180
+ ['A', 'B', 'C', 'AB', 'AC', 'BC', 'ABC', 'E']
181
+ )
182
+ }
183
+
184
+ #evaluateAsVenn(tokenSets: Record<string, string[] | undefined>, reference?: string[]): string[] {
185
+ const varStack: (Set<string>)[] = []
186
+
187
+ let referenceSet: Set<string>
188
+ if (reference === undefined) {
189
+ referenceSet = new Set()
190
+ for (const key in tokenSets) {
191
+ referenceSet = new Set([
192
+ ...referenceSet,
193
+ ...(tokenSets[key] ?? [])
194
+ ])
195
+ }
196
+ } else {
197
+ referenceSet = new Set(reference)
198
+ }
199
+
200
+ for (const token of this.#rpn) {
201
+ if (token.tokenType === 'variable') {
202
+ // The variable has no token - assume it's empty.
203
+ if (tokenSets[token.token] === undefined) {
204
+ varStack.push(new Set())
205
+ } else {
206
+ varStack.push(new Set(tokenSets[token.token]))
207
+ }
208
+
209
+ } else {
210
+ switch (token.token) {
211
+ case '&':
212
+ if (varStack.length >= 2) {
213
+ const second = varStack.pop(),
214
+ first = varStack.pop()
215
+
216
+ if (first && second) {
217
+ varStack.push(new Set([...first].filter(x => second.has(x))))
218
+ }
219
+ }
220
+ break
221
+ case '|':
222
+ if (varStack.length >= 2) {
223
+ const second = varStack.pop(),
224
+ first = varStack.pop()
225
+ if (first && second) {
226
+ varStack.push(new Set([...first, ...second]))
227
+ }
228
+ }
229
+ break
230
+ case '-':
231
+ if (varStack.length >= 2) {
232
+ const second = varStack.pop(),
233
+ first = varStack.pop()
234
+
235
+ if (first && second) {
236
+ varStack.push(new Set([...first].filter(x => !second.has(x))))
237
+ }
238
+ }
239
+ break
240
+ case '!':
241
+ if (varStack.length >= 1) {
242
+ const first = varStack.pop()
243
+
244
+ if (first) {
245
+ varStack.push(new Set([...referenceSet].filter(x => !first.has(x))))
246
+ }
247
+ }
248
+ break
249
+ }
250
+ }
251
+ }
252
+
253
+ return [...varStack[0]].sort()
254
+ }
255
+ }