5
\$\begingroup\$

I try to remove elements in an array if the number of contiguous element is greater than two.

Here are the tests:

test('delete pieces if number of piece contiguous greater than two', t => {
    t.deepEqual([null, null, null], removeIdenticalPieces([1, 1, 1]));
    t.deepEqual([null, null, null, null], removeIdenticalPieces([1, 1, 1, 1]));
    t.deepEqual([null, null, null, null, 4], removeIdenticalPieces([1, 1, 1, 1, 4]));
    t.deepEqual([4, null, null, null, null], removeIdenticalPieces([4, 1, 1, 1, 1]));
    t.deepEqual([1, 1, 4, 1, 1], removeIdenticalPieces([1, 1, 4, 1, 1]));
    t.deepEqual([1, 1], removeIdenticalPieces([1, 1]));
});

Here is a working function:

function removeIdenticalPieces(line) {
    const newLine = [];
    while (line.length !== 0) {
        const firstPiece = line.shift();
        let nbContiguousPieces = 0;
        for (let i = 0; i < line.length; i++) {
            let currentPiece = line[i];
            if (firstPiece !== currentPiece) {
                break;
            }
            nbContiguousPieces += 1
        }
        if (nbContiguousPieces >= 2) {
            newLine.push(null);
            for (let j = 0; j < nbContiguousPieces; j++) {
                line.shift();
                newLine.push(null)
            }
        } else {
            newLine.push(firstPiece);
            for (let k = 0; k < nbContiguousPieces; k++) {
                newLine.push(line.shift())
            }
        }
    }
    return newLine;
}

Does a more "functional" way exist to do the same?

Edit: thank you for your solutions.

Here the jsperf https://jsperf.com/removeidenticalpieces3.

I take the solution of @lilobase because it is faster and more readable.

\$\endgroup\$
4
\$\begingroup\$

Close to Damien's solution. If we hardcode the lookups, we can achieve a simpler version :

const lookAfter = (i, a) => (a[i] == a[i+1] == a[i+2]);
const lookBehind = (i, a) => (a[i] == a[i-1] == a[i-2]);
const lookAround = (i, a) => (a[i] == a[i-1] == a[i+1]);
const deleteContiguousItems = array => array.map((item, i) => (lookAfter(i, array) || lookAround(i, array) || lookBehind(i, array)) ? null : item);

And if you inline all the declarations you'll get the simplest expressions (not the most readable)

const lookAndReturn = (val, i, a) => (val == a[i+1] == a[i+2]) || (val == a[i-1] == a[i-2]) || (val == a[i-1] == a[i+1]) ? null : val;
const removeIdenticalPieces = array => array.map(lookAndReturn);
\$\endgroup\$
4
\$\begingroup\$

One neat thing about array.reduce is that the carry doesn't need to be your resulting value. You can actually hold a record of previous runs on it by carrying an object.

I would think something along the lines of:

line.reduce((carry, current, index) => {
  // If current is same a previous, increase repeats
  // If repeats === 3, splice the last 3  in result and replace with null,
  // Else, add current to the result
  // update previous and repeats a
  // return updated carry
}, {
  result: [],
  previous: null,
  repeats: 0
}).result;
\$\endgroup\$
3
\$\begingroup\$

My first instinct was to use map, and then deconstruct each operation in a separate function:

const isContiguous = ({ line, key1, key2, value}) => (
  line[key1] && line[key1] === value
  && line[key2] && line[key2] === value
)

const isTwiceContiguous = ({ line, key, value }) => (
  isContiguous({ line, key1: key - 2, key2: key - 1, value})
  || isContiguous({ line, key1: key - 1, key2: key + 1, value})
  || isContiguous({ line, key1: key + 1, key2: key + 2, value})
)

const removeIdenticalPieces = (line) => (
  line.map((value, key) => isTwiceContiguous({ line, key, value }) ? null : value)
)
\$\endgroup\$
2
\$\begingroup\$

Here's my take on this problem:

const last = (a) => a ? a[a.length - 1] : null
const init = (a) => a ? a.slice(0, -1) : null
const flatten = (a) => 
  a.reduce((m, v) => m.concat(Array.isArray(v) ? flatten(v) : v), [])

const splitReducer = (m, v) => 
  last(last(m)) === v 
    ? init(m).concat([last(m).concat(v)])
    : m.concat([[v]])

const splitWhenValueDiffer = (list) => 
  list.reduce(splitReducer, [])

const removeIdenticalPieces = (list) =>
  flatten(splitWhenValueDiffer(list).map(a => a.length > 2 ? a.map(v => null) : a))

console.log(removeIdenticalPieces([1, 1, 1])) // [null, null, null]
console.log(removeIdenticalPieces([1, 1, 1, 1])) // [null, null, null, null]
console.log(removeIdenticalPieces([1, 1, 1, 1, 4])) // [null, null, null, null, 4]
console.log(removeIdenticalPieces([4, 1, 1, 1, 1])) // [4, null, null, null, null]
console.log(removeIdenticalPieces([1, 1, 4, 1, 1])) // [1, 1, 4, 1, 1]
console.log(removeIdenticalPieces([1, 1])) // [1, 1]

The first three functions are just your classical functional utilities.

Then the splitWhenValueDiffer and its splitReducer takes an array and will split it whenever a value is different than the previous one. For example, given [1,1,2,2,3] it returns [[1,1],[2,2],[3]].

Finally the removeIdenticalPieces function splits the passed-in list using splitWhenValueDiffer then map the array and replaces every array of a size greater than 2 with an array of same length containing null and flatten everything.

As you can notice, every function is pure, has no local variables and never mutate anything.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.