TypeScript Algorithm for Expressions

I have a list of logical expressions,

const expressions = [
    'A&(B|C)',
    'A|(B&C)',
    'A|(B&(C|D))',
    'A|(B&C&D)',
];

From these expressions, I am aiming to obtain the following output,

[[A, B], [A, C]]
[[A], [B, C]]
[A, [B, C], [B, D]]
[A, [B, C, D]] 

I attempted to achieve this using the code below, however, I am receiving empty arrays instead of the correct result.

class ParsedExpression {
    operator: string | null;
    variables: string[];
    subExpressions: ParsedExpression[];

    constructor(operator: string | null, variables: string[], subExpressions: ParsedExpression[]) {
        this.operator = operator;
        this.variables = variables;
        this.subExpressions = subExpressions;
    }
}

function parseExpression(expression: string): ParsedExpression {
    const stack: ParsedExpression[] = [];
    let currentExpr: ParsedExpression | null = null;
    let currentVariable = '';

    for (const char of expression) {
        if (char === '(') {
            if (currentExpr !== null) {
                stack.push(currentExpr);
            }
            currentExpr = null;
        } else if (char === ')') {
            if (stack.length > 0) {
                const prevExpr = stack.pop();
                if (prevExpr !== null && currentExpr !== null) {
                    prevExpr.subExpressions.push(currentExpr);
                    currentExpr = prevExpr;
                }
            }
        } else if (char === '&' || char === '|') {
            if (currentExpr === null) {
                currentExpr = new ParsedExpression(char, [], []);
            } else {
                currentExpr.operator = char;
            }
        } else {
            if (currentExpr === null) {
                currentExpr = new ParsedExpression(null, [], []);
            }
            currentExpr.variables.push(char);
        }
    }

    return currentExpr as ParsedExpression;
}

function generateCombinations(parsedExpr: ParsedExpression): string[][] {
    if (!parsedExpr.operator) {
        return [parsedExpr.variables];
    }

    const leftCombinations = parsedExpr.subExpressions.length > 0 ? generateCombinations(parsedExpr.subExpressions[0]) : [[]];
    const rightCombinations = parsedExpr.subExpressions.length > 1 ? generateCombinations(parsedExpr.subExpressions[1]) : [[]];

    const combinations: string[][] = [];
    for (const left of leftCombinations) {
        for (const right of rightCombinations) {
            combinations.push(left.concat(right));
        }
    }

    return combinations;
}

const expressions = [
    'A&(B|C)',
    'A|(B&C)',
    'A|(B&(C|D))',
    'A|(B&C&D)',
];

for (const expr of expressions) {
    const parsedExpression = parseExpression(expr);
    const combinations = generateCombinations(parsedExpression);
    console.log(`Expression: ${expr}`);
    console.log(`Combinations: ${JSON.stringify(combinations)}`);
    console.log('-------------------');
}

The current output shows empty combinations for each expression. Is there any way to fix this and get the desired combinations?

Answer №1

The function called generateCombinations is not aligned with the task at hand. It should be structured to flatten the tree into two levels, where the top level represents a disjunction and the second level consists of conjunctions.

This can be achieved through recursive methods, working to flatten the structure as we exit recursion.

I opted for an implementation where the parser allows optional parentheses. In this setup, the & operator takes precedence over |, and variable names can contain more than one character. The parser constructs a binary tree structure. Instead of storing variables in a separate array, each child can either be a variable (string) or another node.

In a subsequent phase, the tree can be converted into an array of arrays from the bottom-up perspective. This nested array will then represent either a CNF (conjunction normal form) or DNF (disjunction normal form). To allow merging of children nodes, a conversion between the two forms needs to take place, which essentially involves a Cartesian product operation.

To simplify things further, bottom-level arrays are refined to contain only unique values (i.e., changing A&A&B to A&B). While additional simplification rules could be implemented (as evidenced by an extra test case), I have left this aspect unexplored.

Below you'll find a JavaScript implementation that can easily incorporate typing:

class BinaryNode {
    constructor(value, left, right) {
        this.value = value;
        this.left = left;
        this.right = right;
    }
}

function parseToBinaryTree(expression) {
    const precedence = { "&": 2, "|": 1, "(": -1, ")": 0, "": 0 };
    const operators = [];
    const operands = [];
    for (const token of expression.match(/[^&|()\s]+|\S?/gs)) {
        if (token in precedence) {
            if (token !== "(") {
                while (precedence[operators.at(-1)] >= precedence[token]) {
                    if (operands.length < 2) throw new Error("Parser: Syntax error: operands length < 2"); 
                    operands.push(new BinaryNode(operators.pop(), ...operands.splice(-2)));
                }
            }
            if (token && token !== ")") operators.push(token);
            else operators.pop(); // "("
        } else {
            operands.push(token);
        }
    }
    if (operands.length != 1) throw new Error("Parser: Syntax error: left over");
    return operands.pop();
}

const unique = values => [...new Set(values)];
const uniqueEach = arrays => arrays.map(unique);
const unwrapSingle = values => values.length === 1 ? values[0] : values;
const unwrapSingleEach = arrays => arrays.map(unwrapSingle);

function product(arrays) {
    if (arrays.length === 0) {
        return [[]];
    }
    const result = [];
    for (const x of arrays[0]) {
        for (const rest of product(arrays.slice(1))) {
            result.push([x, ...rest]);
        }
    }
    return result;
}

function normalize(root, operator="|") {
    if (typeof root === "string") { // Leaf node
        return [[root]];
    }
    const terms = uniqueEach([
        ...normalize(root.left, root.value),
        ...normalize(root.right, root.value)
    ]);
    if (root.value !== operator) {
        return uniqueEach(product(terms));
    }
    return terms;
}

const expressions = [
    'A&(B|C)',
    'A|(B&C)',
    'A|(B&(C|D))',
    'A|(B&C&D)',
    'A&(B|C)&(C|D)',
];

for (const expr of expressions) {
    const root = parseToBinaryTree(expr);
    const disj = unwrapSingleEach(normalize(root));
    console.log(`Expression: ${expr}`);
    console.log(JSON.stringify(disj));
    console.log('-------------------');
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

typescript makeStyles() functions from material-ui library

I've been struggling to find the correct type without relying on any. I have a working code that styles the component as expected: import { makeStyles } from '@material-ui/core/styles' const useStyles = makeStyles((theme) => ({ mainC ...

What could be the reason for the tsc command not displaying compilation errors when compiling a particular file?

My file, titled app.ts, contains the following code snippet: interface Foo { bar:String; } const fn = (foo? :Foo) => foo.bar; When I run tsc from the root folder with strict:true in my tsconfig.json file, I receive an error message like this ...

What steps should be taken to resolve the error message "This Expression is not constructable"?

I'm trying to import a JavaScript class into TypeScript, but I keep getting the error message This expression is not constructable.. The TypeScript compiler also indicates that A does not have a constructor signature. Can anyone help me figure out how ...

Trying to add a single value to a specific index in a JavaScript array, but it is mistakenly assigning multiple values at once

Currently tackling a matrix algorithm with an early roadblock. The array at hand is: [ [ 0, 0, 0 ], [ 0, 0, 0 ], [ 0, 0, 0 ] ] The goal is to convert it into this: [ [ 0, 0, 0 ], [ 0, 9, 0 ], [ 0, 0, 0 ] ] My plan was to alter the middle value like so ...

Angular 2 iframe balked at being shown

Whenever I enter a URL into a text input and press enter, my objective is to have the URL open in an iframe. However, some websites produce an error when trying to do so. Is there a method to successfully display a web page in an iframe? Error message: Th ...

Tips for effectively hydrating Redux state in Next.js upon page refresh?

I'm experiencing an issue with hydrating user data from local storage upon app reload or page refresh. My project utilizes NextJS for the frontend, and for managing state across the application I rely on redux-toolkit and next-redux-wrapper. Upon us ...

Utilizing ES6 Promises in Mongoose with Typescript to Create a Seamless Chain

When attempting to chain ES6 promises with Mongoose 4.5.4, I encountered an error in Typescript. public static signup(req: express.Request, res: express.Response) { UserModel.findOne({ email: req.body.email }).exec() .then(existingUser => { ...

Attach an event listener to a particular textarea element

Currently, I am developing a project in Next.js13 and my focus is on creating a custom textarea component. The goal is to have this component add an event listener to itself for auto-adjusting its height as the user types. Below is the relevant section of ...

The issue with downloading all files in Firefox persists when attempting to download multiple files simultaneously due to an anchor click

I am currently facing an issue with downloading multiple files using TypeScript in Angular 6. I am receiving an array of blobs from a web API service. Here is the service method used to get multiple blobs for downloading: private downloadTest(): void { ...

What is the best way to prevent ngFor from repeating the input values being typed?

How can I prevent the same word from being repeated in all the inputs when using ngFor to create multiple inputs? https://i.sstatic.net/kqh5X.png Here is my code snippet: <div class="" *ngFor="let publication of publications"&g ...

How can one retrieve the "code" attribute from a FirebaseError object in AngularFire using TypeScript?

When using FirebaseError with a "code" property, how can you access it within the catch method of a promise? The code snippet below results in a TypeScript error: Property 'code' does not exist on type 'Error'. this.af.database .object ...

Improving the App() function in React and Typescipt

When working on my React/Typescript app, I encountered a challenge with the length of the code in one of the sections. Despite watching tutorials and searching for solutions, I couldn't find a clear way to refactor it. The specific issue is with refa ...

Steps for wrapping a class with a higher order component

Is it feasible to encapsulate a class component within a higher order component (HOC) that is also a class? import React, { Component } from "react"; import { View } from "react-native"; import { Toast } from "react-native-easy-toast"; const withToast = ...

Guide on creating a style instance in a component class using Material-UI and Typescript

After transitioning my function component to a class component, I encountered an error with makeStyle() from Material-UI as it violates the Rule of Hooks for React. The documentation for Material-UI seems to focus mainly on examples and information related ...

Discovering specific values for an ID using API calls in Angular (Implementing CRUD Operations in Angular with API Integration)

My current project involves CRUD operations in Angular utilizing the API created in Laravel. I have successfully added and fetched values, but encountered an issue when attempting to update values using their respective IDs. This snippet is from my app.co ...

How to Implement Animations with Angular 2 Directives

Incorporating a special Directive onto elements to monitor their current scroll position seems like a handy feature. Here's an example: @Directive({ selector: '[my-scroll-animation]' }) My goal is to make the element appear on screen ...

Definition file in TypeScript for an npm package provided by an external source - constructor

In my Node project, I am utilizing ES6 and Typescript. Despite this, there is a commonjs library that I need to incorporate. To address this, I have created my own .d.ts declaration file for the library: module "@alpacahq/alpaca-trade-api" { e ...

Expanding the global object in ES6 modules to include TypeScript support for extensions like `Autodesk.Viewing.Extension`

I created a custom forge extension and now I am looking to incorporate typescript support as outlined in this blog post. However, I am facing an issue where typescript cannot locate the objects like Autodesk.Viewing.Extension and Autodesk.Viewing.ToolInter ...

Focusing in on a particular category of items based on a specific characteristic

I can't seem to understand why typescript has an issue with the code below. The errors from the compiler are detailed in the comments within the code. const FOO = Symbol(); function bar<T>(param: T) { if (param !== null && typeof para ...

Ionic - What is the correct way to import ViewController? - Uncaught (in promise): Error: ViewController provider not found

I have a Popover in my app and I want it to behave differently based on the selected item. I followed the instructions in the Ionic documentation to achieve this. Error: Uncaught (in promise): Error: No provider for ViewController! When I tried adding ...