-1

Given an array of strings, I'd like to check for opposite directions and cancel them out (remove them from the array) and return a new array.

For example:

let arr = ["N", "E", "W", "S", "W", "S", "N", "W"]

should reduce to:

newArr = ["W", "W"]

Here is what I have come up with so far. I am getting undefined and I am not sure why.

let arr = ["N", "E", "W", "S", "W", "S", "N", "W"]

function checkForOpposites(arr) {
  let nOpp = ["N", "S"]
  let sOpp = ["S", "N"]
  let eOpp = ["E", "W"]
  let wOpp = ["W", "E"]
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] + arr[i + 1] === nOpp) 
    if (arr[i] + arr[i + 1] === sOpp)
    if (arr[i] + arr[i + 1] === eOpp)
    if (arr[i] + arr[i + 1] === wOpp) {
      return true
    }
  }
}

function newDirections(arr) {
  arr.filter(checkForOpposites)
}
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • Did you check what e.g. `arr[i] + arr[i + 1]` is? For example, try `"N" + "S" === ["N", "S"]`. – jonrsharpe Dec 14 '20 at 21:23
  • `arr.filter` does not pass the array into `checkForOpposites()`, but instead it passes each element. I think you can do without the filter. – Han Xiong Dec 14 '20 at 21:24
  • newDirections does not return anything hence why you get undefined.... – epascarello Dec 14 '20 at 21:25
  • `if() if() if() {}` is not valid, it should be `if (conditionA || conditionB || ...) {}` **Edit:** well, as said below, it's valid, but equivalent to `if (conditionA && conditionB && ...) {}`, which is not what you want – blex Dec 14 '20 at 21:25
  • 1
    @blex it is valid.... it is just wrong – epascarello Dec 14 '20 at 21:27
  • @jonrsharpe you mean something like this? `if (arr[i] + arr[i + 1] === nOpp.toString())`? I did to checking to see what `arr[i] + arr[i + 1]` is. It is `Nundefined` etc. I do not understand why. – TheFreeThinkingRyan Dec 14 '20 at 21:35
  • I mean literally just trying out the comparisons in your code, seeing if they give the responses you expect; this is basic debugging. And if you're seeing undefined think about what i + 1 might be. – jonrsharpe Dec 14 '20 at 21:37
  • On a side note, your question's title specifically says _"adjecent strings"_ (and your code assumes that), but the expected output you present has actually every opposites removed (not just adjacent ones) – blex Dec 14 '20 at 21:39
  • @blex Ah, that makes sense. @Han Xiong you are correct. Here are some changes: `function newDirections(arr) { arr.filter(word => checkForOpposites(arr)) }` and my conditionals are: `if ( arr[i] + arr[i + 1] === nOpp.toString() || arr[i] + arr[i + 1] === sOpp.toString() || arr[i] + arr[i + 1] === eOpp.toString() || arr[i] + arr[i + 1] === wOpp.toString() ) { return true }` but now I am still getting undefined for some reason. – TheFreeThinkingRyan Dec 14 '20 at 21:47

5 Answers5

1

You could filter the array until the length is not changing.

let
    array = ["N", "E", "W", "S", "W", "S", "N", "W"],
    temp;

do {
    let index = -1;
    temp = array;
    array = temp.filter((v, i, { [i + 1]: next }) => {
        if (i === index) return false;
        if (
            v === 'N' && next === 'S' || v === 'S' && next === 'N' ||
            v === 'E' && next === 'W' || v === 'W' && next === 'E'
        ) {
            index = i + 1;
            return false;
        }
        return true;
    });
} while (temp.length !== array.length)

console.log(array);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

function cancelDirections(array) {
    var ncnt = 0, ecnt = 0, scnt = 0, wcnt = 0;
    var output = "";

    for (var n of array) {
        switch (n) {
            case "N":
                ncnt++;
                break;
            case "E":
                ecnt++;
                break;
            case "S":
                scnt++;
                break;
            case "W":
                wcnt++;
                break;
        }
    }

    if (ecnt > wcnt) { ecnt -= wcnt; wcnt = 0;}
    else { wcnt -= ecnt; ecnt = 0}

    if (ncnt > scnt) { ncnt -= scnt; scnt = 0; }
    else { scnt -= ncnt; ncnt = 0; }

    return output + "N".repeat(ncnt) + "S".repeat(scnt) + "E".repeat(ecnt) + "W".repeat(wcnt);
}

let arr = ["N", "E", "W", "S", "W", "S", "N", "W"]
console.log(cancelDirections(arr))

Explanation: I keep track of the count of each element, and then just cancel them out. This approach is O(n); I would be interested if there was a faster way.

Han Xiong
  • 171
  • 1
  • 6
0

First issue is you get undefined because you are not returning anything

function newDirections(arr) { arr.filter(checkForOpposites) } // <-- no return so undefined

Second issue is your logic is wrong and your code is basically saying everything would have to be equal which is not possible. Your code looks like this:

if (arr[i] + arr[i + 1] === nOpp) {
  if (arr[i] + arr[i + 1] === sOpp) {
    if (arr[i] + arr[i + 1] === eOpp) {
      if (arr[i] + arr[i + 1] === wOpp) {
        return true;
      }
    }
  }
}

Next you are using filter which loops multiple times and then you are looping inside of it. Filter is not the way to go. You want to do something with a loop.

var opposites = {
  N: 'S',
  S: 'N',
  E: 'W',
  W: 'E',
};

function newDirections(orgArr) {
  // in case you want original cloning array
  var arr = orgArr.slice();
  // loop from start to 2nd to last element in array
  var i = 0;
  while (i < arr.length - 1) {
    // grab current and next direction
    var dir = arr[i];
    var nextDir = arr[i + 1];
    // check to see if they are opposite
    if (opposites[dir] === nextDir) {
      // remove them
      arr.splice(i, 2); // index and 2 items
      // if we are not at the start move back one position so we can check the new direction
      if (i > 0) {
        i--;
      }
    } else {
      // if not opposite move forward
      i++;
    }
  }
  return arr;
}

const arr = ["N", "E", "W", "S", "W", "S", "N", "W"];
console.log(newDirections(arr));

console.log(newDirections(["N", "S", "E", "W", "W", "S"]));
console.log(newDirections(["E", "N", "S", "E", "W", "W", "S"]));
console.log(newDirections(["E", "E", "W", "N", "S", "E", "W", "W"]));
epascarello
  • 204,599
  • 20
  • 195
  • 236
0

You can map the directions to vectors, sum the vectors and reduce the resulting vector to a list of directions.

var arr = ["N", "E", "W", "S", "W", "S", "N", "W"]

var vecMap = {
    N: [1,0],
    E: [0,1],
    S: [-1,0],
    W: [0,-1]
};

var revMap = [
    ['N','S'],
    ['E','W']
];

const dir2Vec = dir => vecMap[dir];
const vec2Dir = (acc, cur, i) => {
    //get the direction based on the reverse map. 
    const dir = revMap[i][+(cur < 0)];
    //array with n items where n is the length of the vector
    const dup = Array.from({length: Math.abs(cur)}).fill(dir);
    return [...acc, ...dup]
}
const sumVec = (a,b) => [a[0]+b[0], a[1]+b[1]];

const res = arr
.map(dir2Vec)
.reduce(sumVec)
.reduce(vec2Dir, [])

console.log(res)
Moritz Roessler
  • 8,542
  • 26
  • 51
0

First of all, you cannot compare arrays like this if (arr[i] + arr[i + 1] === nOpp). In this case, === will compare if both sides of the comparison are Array type objects, and if they are the same object, not if their content is the same, for that you should use other ways.

Open node.js CLI and paste this code

const nOpp = ["N", "S"];
console.log(nOpp === ["N", "S"]);

and you'll see false printed in the console.

For a more detailed explanation, look here: How to compare arrays in JavaScript?

Second, nesting if like this is equivalent to successive ANDs

    if (arr[i] + arr[i + 1] === nOpp) 
    if (arr[i] + arr[i + 1] === sOpp)
    if (arr[i] + arr[i + 1] === eOpp)
    if (arr[i] + arr[i + 1] === wOpp) {
     ...
    }

This means that only if the first if evaluates to true the second one will be evaluated if this evaluates to true the third will be evaluated, and so on.

Now, that all being said, here is my version. I just took into account the first paragraph of your question, and the desired result, then came to this:

let arr = ["N", "E", "W", "S", "W", "S", "N", "W"]
const opposites = [
  ["N", "S"],
  ["W", "E"],
];

const checkForOpposites = data => {
  const result = [];
  data.forEach((v0, i, a) => {
    let oppositeFound = false;
    opposites.forEach(o => {
      if (v0 != a[i+1] && o.includes(v0) && o.includes(a[i+1])) {
        oppositeFound = true
        result.push(i, i+1);
      }
    });
//     console.log(`${i} => ${v0} | ${i+1} => ${a[i+1]} | ${oppositeFound}`);
  });
  return result;
}

const cleanOpposites = data => {
  let result = data;
  let n = [];
  do {
    n = checkForOpposites(result);
//     console.log(n);
    if (n.length > 0) {
      result = result.reduce((accum, curr, i) => {
        if (!n.includes(i)) {
          accum.push(curr);
        }
        return accum;
      }, []);
    }
//     console.log(result);
  } while (n.length > 0);
  
  return result;
}

const r0 = cleanOpposites(arr);
console.log(arr);
console.log(r0);

The cleaning process should be recursive due to this: In the first iteraction we get this result:

indexes of items deleted: [1, 2, 5, 6]
resulting array: ["N", "S", "W", "W"]

But there remains ["N", "S",... that shouldn't be part of the final result, so to achieve your requirement, I think we must iterate through the checkForOpposites function till it returns no matches.

Certainly, the code can be shortened, but this would give you an idea of how to do it.