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.
4 Answers 4
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);
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;
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)
)
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.