-1

I have two arrays:
The arrays with a length of two ([0, 0] or [1, 2] arrays) represent co-ordinates in a format of [x, y]:

let coArr = [
  [[0, 0], [1, 1], [2, 2], "Mode1"],
  [[0, 0], [0, 1], [0, 2], "Mode2"]
  // and six more...
], strArr = [
  "Mode1(0, 0)",
  "Mode2(1, 2)",
  "Mode1(0, 2)" 
  // ...
  // You get the idea
  // ...
];

How can I create a function that returns true when the strArr contains the three strings, which correspond with the arrays in one of the arrays in coArr, only if the Mode is the same? Can anybody give me some code with explanation on how I can achieve this?

The question is hard to explain but maybe an example will help:

I want to return true if... let's say:
coArr contains [[0, 0], [0, 1], [0, 2], "Mode2"]
And strArr contains the 3 strings:

"Mode2(0, 0)",
"Mode2(0, 1)",
"Mode2(0, 2)"

Also, is there a way where you don't need to provide the co-ordinates as arguements?

Angshu31
  • 1,172
  • 1
  • 8
  • 19
  • 1
    Your name is Learn for Fun but I don't evidence of you trying to learn from this question :) What have you tried? What are you looking for? An algorithm, or set of steps, in readable language on how to approach this? An all-out working code solution? – Ashley Feb 11 '20 at 21:00
  • @Ashley 1st of all) I would if have something to learn from right now. 2nd) I have absolutely no idea of how to approach this without `Array.includes()` method which never works for me. 3rd) I'd like a piece of code with explanation please. – Angshu31 Feb 11 '20 at 21:25

1 Answers1

1

So, I want to start by saying you definitely have lots to learn from right now. It's always a good idea to make some attempts, figure out your approach, and then explain it with any bits of code you can provide, whether it works or not. Your question here isn't the clearest either so I am going to provide my example on how I have interpreted it. I'm also going to explain in a somewhat top-down approach, so this is a bit of a read.

Using the input and description you've given, let's work on a function that takes a mode name and returns true if strArr contains all coordinates that that mode has in coArr. To clarify terms, let's say that if this function returns true we will say that the given mode is covered by strArr. Hopefully you are with me so far.

const isModeCovered = modeName => {
    let mode = coArr.find(x => x.name == modeName);
    let modeCoordinates = strArr.filter(x => x.name == modeName);
    return mode.isCoveredBy(modeCoordinates);
};

function isModeCovered(modeName) {
    let mode = coArr.find(function(x) {
        return x.name == modeName;
    });
    let modeCoordinates = strArr.filter(function(x) {
        return x.name == modeName;
    });
    return mode.isCoveredBy(modeCoordinates);
}

Here we have a function that I've written to do just that. As you can see, I've written this function twice: once using Arrow functions and once using "normal" functions. I am not going to go in-depth about what an Arrow function is in Javascript and you will see the rest of this example using them. If you need to familiarize yourself with them now is a great time to do so, but you might also be able to understand them just from comparing the two functions above. Anyway, let's move on and look at what this function is actually is doing.

let mode = coArr.find(x => x.name == modeName);

Here we're using the find function to find the first item in coArr that has a name equal to the given mode name. As you can see in the link, find searches each item of the Array it is performed on and returns the first item that makes the given callback return true. The callback we have given it will not work. That is because it is assuming each item of coArr is an object that has a name. The next line, where we are filtering strArr, will also not work for a similar reason.

Why did I do this?

I did this because the data, in the form that it has been given is - to be blunt - a headache. You can absolutely solve this problem without modifying the data but there are many reasons why I prefer to that I will explain later. So, we are going to assume that we have taken the data given and parsed it into more usable forms:

const modes = coArr.map(x => new Mode(x));
const allModeCoordinates = strArr.map(x => new ModeCoordinate(x));

const isModeCovered = modeName => {
    let mode = modes.find(x => x.name == modeName);
    let modeCoordinates = allModeCoordinates.filter(x => x.name == modeName);
    return mode.isCoveredBy(modeCoordinates);
};

Alright, here is some slightly modified code. We see that it is mapping coArr to a new Array of an object called Mode and mapping strArr to a new Array of an object called ModeCoordinate. We can worry about how the constructor for those objects are handling that later, but for clarity's sake here is a brief idea of what those objects look like (in pseudo-JS, this won't compile):

class Coordinate {
    int x,
    int y
}

class Mode {
    string name,
    Coordinate[] coordinates // an Array of Coordinates
}

class ModeCoordinate {
    string name,
    Coordinate coordinate
}

If our data is in Arrays of Modes and ModeCoordinates we can easily search by their name and find their associating Coordinate(s). Once we can easily do that most of the work is done for us and we just have to determine the details of the logic behind the last line of the function:

return mode.isCoveredBy(modeCoordinates);

Let's add that function, isCoveredBy to the Mode class:

class Mode {
    string name,
    Coordinate[] coordinates

    isCoveredBy(modeCoordinates) {
        for (const coordinate of this.coordinates) {
            if (!!!modeCoordinates.find(c => c.coordinate.equals(coordinate)))
                return false;   
        }
        return true;
    }
}

It's a short method, but let's quickly go through it. We use a for of to loop through every Coordinate in the Mode's coordinates. Then, we use an if to test if we can find that Coordinate in the Array of given Coordinates (which, if you rememeber, are taken from strArr who have the same mode name as the Mode we are in). If we can't find one we immediately return false because is order for the mode to be "covered" we stated that every single coordinate needs to also exist in strArr. If it hasn't returned false at any point, we return true at the end of the loop because that means they were all found.

There's a chance that this line, the if condition, may be confusing:

!!!modeCoordinates.find(c => c.coordinate.equals(coordinate))

We can briefly break that down. modeCoordinates.find(c => c.coordinate.equals(coordinate)) is taking modeCoordinates which, if you recall, is a ModeCoordinate Array that we have mapped all our strArr items into and filtered it by the mode name of the Mode we are in, and trying to find one that has a Coordinate equal to the coordinate of our current loop iteration. It returns back the item, if found, or falsy if not found. The !!! in front of it is a combination of a regular !, or not, operator and a !! operator, which forces the returned value into a boolean.

Alright, so now we know we have to parse the given data into our nicer object types to make our find and filters easy breezy and we've created the function that can test to see if a given mode name is covered. All that is left is to do the actual parsing, which we already mentioned can be done in the constructors of our classes.

class Coordinate {
    constructor(x, y) {
        this.x = parseInt(x);
        this.y = parseInt(y);
    }

    equals(coordinate) {
        return coordinate.x == this.x && coordinate.y == this.y;
    }
}

Our Coordinate class, used only internally by our other classes, is nice and simple. Its constructor accepts an x and a y and parses them into integers. I added an equals function so that we can easily compare if two Coordinates are the same.

class Mode {
    constructor(arr) {
        this.name = arr.pop();
        this.coordinates = arr.map(coordinate =>
            new Coordinate(coordinate[0], coordinate[1])
        );
    }

    isCoveredBy(modeCoordinates) {
        for (const coordinate of this.coordinates) {
            if (!!!modeCoordinates.find(c => c.coordinate.equals(coordinate)))
                return false;   
        }
        return true;
    }
}

Our Mode class's contructor takes an Array (since coArr is an Array of Arrays) and pops the last member into its name. The remaining Array is all coordinates so they are mapped into Coordinate objects.

class ModeCoordinate {
    constructor(input) {
        let inputArr = input.replace(/\s/g, "").split(/[^A-Za-z0-9]/);
        this.name = inputArr[0];
        this.coordinate = new Coordinate(inputArr[1], inputArr[2]);
    }
}

Our ModeCoordinate class expects a string and uses regex to remove whitespace (using replace) and then split the string and leave only alphanumeric characters. So for example, "Mode2(1, 2)" becomes ["Mode2", "1", "2"]. This names it easy to store the first item as the name and create a Coordinate from the last two.

Now we can put it all together. I've added some test data so you can run it:

class Coordinate {
    constructor(x, y) {
        this.x = parseInt(x);
        this.y = parseInt(y);
    }

    equals(coordinate) {
        return coordinate.x == this.x && coordinate.y == this.y;
    }
}

class Mode {
    constructor(arr) {
        this.name = arr.pop();
        this.coordinates = arr.map(coordinate =>
            new Coordinate(coordinate[0], coordinate[1])
        );
    }

    isCoveredBy(modeCoordinates) {
        for (const coordinate of this.coordinates) {
            if (!!!modeCoordinates.find(c => c.coordinate.equals(coordinate)))
                return false;   
        }
        return true;
    }
}

class ModeCoordinate {
    constructor(input) {
        let inputArr = input.replace(/\s/g, "").split(/[^A-Za-z0-9]/);
        this.name = inputArr[0];
        this.coordinate = new Coordinate(inputArr[1], inputArr[2]);
    }
}

let coArr = [
    [[0, 0], [1, 1], [2, 2], "Mode1"],
    [[0, 0], [0, 1], [0, 2], "Mode2"],
    [[0, 0], [0, 1], [0, 2], "Mode3"],
    [[0, 0], [0, 1], [0, 2], "Mode4"],
    [[0, 0], [0, 1], [0, 2], "Mode5"],
    [[0, 0], [0, 1], [0, 2], "Mode6"]
  ], strArr = [
    "Mode1(0, 0)",
    "Mode1(1, 1)",
    "Mode1(2, 2)",
    "Mode2(0, 0)",
    "Mode2(1, 2)",
    "Mode2(0, 2)"
  ];

const modes = coArr.map(x => new Mode(x));
const allModeCoordinates = strArr.map(x => new ModeCoordinate(x));

const isModeCovered = modeName => {
    let mode = modes.find(x => x.name == modeName);
    let modeCoordinates = allModeCoordinates.filter(x => x.name == modeName);
    return mode.isCoveredBy(modeCoordinates);
};

console.log(isModeCovered("Mode1")); // returns true
console.log(isModeCovered("Mode2")); // returns false

If you want to see if all modes are "covered" you can run the function we created in a loop over all modes in modes. Obviously there isn't any error handling here - we're really expecting the data to be in the format you've shown. Hopefully this answers your question.

Edit with alternative solution as requested in comments:

class Coordinate {
    constructor(x, y) {
        this.x = parseInt(x);
        this.y = parseInt(y);
    }

    equals(coordinate) {
        return coordinate.x == this.x && coordinate.y == this.y;
    }
}

class Mode {
    constructor(name, coordinates) {
        this.name = name;
        this.coordinates = coordinates.map(coordinate =>
            new Coordinate(coordinate[0], coordinate[1])
        );
    }

    isCoveredBy(modeCoordinates) {
        for (const coordinate of this.coordinates) {
            if (!!!modeCoordinates.find(c => c.coordinate.equals(coordinate)))
                return false;   
        }
        return true;
    }

    addCoordinates(coordinates) {
        let newCoordinates = coordinates.map(coordinate =>
            new Coordinate(coordinate[0], coordinate[1])
        );
        this.coordinates.concat(newCoordinates);
    }
}

class ModeCoordinate {
    constructor(input) {
        let inputArr = input.replace(/\s/g, "").split(/[^A-Za-z0-9]/);
        this.name = inputArr[0];
        this.coordinate = new Coordinate(inputArr[1], inputArr[2]);
    }
}

let coArr = [
    [[0, 0], [1, 1], [2, 2], "Mode1"],
    [[0, 0], [2, 2], "Mode3"],
    [[0, 0], [0, 1], [0, 2], "Mode2"],
    [[1, 1], "Mode3"],
    [[0, 0], [0, 1], [0, 2], "Mode4"],
    [[0, 0], [0, 1], [0, 2], "Mode5"],
    [[0, 0], [0, 1], [0, 2], "Mode6"]
  ], strArr = [
    "Mode1(0, 0)",
    "Mode1(1, 1)",
    "Mode1(2, 2)",
    "Mode2(0, 0)",
    "Mode2(1, 2)",
    "Mode2(0, 2)",
    "Mode3(0, 0)",
    "Mode3(1, 1)",
    "Mode3(2, 2)"
  ];

const extractModes = () => {
    let modes = [];
    for (let item of coArr) {
        let name = item.pop();
        let mode = modes.find(x => x.name == name);
        if (!!mode)
            mode.addCoordinates(item);
        else 
            modes.push(new Mode(name, item));      
    }

    return modes;
};

const modes = extractModes();
const allModeCoordinates = strArr.map(x => new ModeCoordinate(x));

const isModeCovered = modeName => {
    let mode = modes.find(x => x.name == modeName);
    let modeCoordinates = allModeCoordinates.filter(x => x.name == modeName);
    return mode.isCoveredBy(modeCoordinates);
};

console.log(isModeCovered("Mode1")); // returns true
console.log(isModeCovered("Mode2")); // returns false
console.log(isModeCovered("Mode3")); // returns true
Ashley
  • 897
  • 1
  • 5
  • 17
  • You typed `"Mode1"` into both. If you change the second to `"Mode2"` it will return false. – Ashley Feb 12 '20 at 20:00
  • I really want to say that this solved my problem [but...](https://codepen.io/Ongshu777/pen/KKpdBYW?editors=0010). I have tried using your solution, but I encountered a problem. As you would be able to see, I have a mode 2 array in `coArrs`: `[[[0, 0], [1, 0], [2, 0]], "Mode2"]` and **only one** `Mode 2` string in `strArr`. Could you please fix this? Thanks a lot. – Angshu31 Feb 12 '20 at 20:04
  • Sorry for bothering you again but just one more Question. Is there a way to only have two modes? – Angshu31 Feb 12 '20 at 20:26
  • I'm not sure what you mean - this can accommodate any number of modes, you just have to add them into the data. Could you explain? – Ashley Feb 12 '20 at 20:29
  • I mean, is there a way where there's multiple arrays with the same mode in `coArr` and still make it work. What you did is make a lot of different modes for every group of coordinates. Is there a way to have a lot of groups of coordinates with the same mode, but still make it work? – Angshu31 Feb 12 '20 at 20:32
  • Updated my response with a new example. You can check out what I did and play around with the code as needed, but I won't have time to update any further. Good luck! – Ashley Feb 12 '20 at 20:51
  • So, if any of the group of coordinates in the given Mode would return `true` then return `true`. – Angshu31 Feb 12 '20 at 20:52
  • Oh, yes you can do that with the original example just by changing `isModeCovered` - you would just change the line `let mode = modes.find(x => x.name == modeName);` to `let modes = modes.filter(x => x.name == modeName);` and check at least one in the `Array` returns `true` with `.isCoveredBy(modeCoordinates)`. – Ashley Feb 12 '20 at 20:56
  • That almost worked: `Uncaught TypeError: coordinates.map is not a function`. (In class Mode) – Angshu31 Feb 12 '20 at 21:20