Reduce JavaScript array to matching elements with counts - javascript

Suppose I've got a nested array:
// 3rd element in sub-array indicates a number of repeats
var list = [["a","b",1],["a","d",1],["a","b",1],["c","d",1]];
Task is to remove identical sub-arrays and increase number in single unique sub-array, which would indicate the number of repeats, so that above example would transform into smth like:
[["a","b",2],["a","d",1],["c","d",1]]
What would be the most efficient way to achieve this?
Currently I'm trying smth like this:
var list = new Array();
// Sort by second element
list.sort(function(a,b) {
return a[1] > b[1];
});
function collateList(element,index,array){
// if 1st&2nd element of subarray equals to 1st&2nd element of next subarray
if (array[index[0]]==array[index[0]+1] && array[index[1]]==array[index[1]+1]){
// increase 3rd element of subarray by 1
array[index[2]] = array[index[2]+1];
// remove next element from an array
array.splice((index+1),1);
}
}
list.forEach(collateList);

Let us first define the function determining if two subarrays are to be combined, in this case that their first two values are the same:
function match(e1, e2) { return e1[0]===e2[0] && e1[1]===e2[1]; }
Now let us define a function which finds a matching element in an array, based on a matching function, and returns its index. This is the same as Array.prototype.findIndex, were it defined.
function find(a, v, fn) {
for (i in a) { if (fn(v, a[i])) {return i;} }
return -1;
}
Now we feed the input through a reduce to create an new array with counts updated and duplicates removed:
list.reduce( // Boil down array into a result
function(result, elt) { // by taking each element
var prev = find(result, elt, match); // and looking for it in result so far.
if (prev !== -1) { // If found
result[prev][2]++; // increment previous occurrence;
} else { // otherwise
result.push(elt); // include as is in the result.
}
return result; // Use this result for next iteration.
},
[] // Start off with an empty array.
)

Related

Don't understand how this code can detect the length

var data = [1, 2, 3, 5, 2, 1, 4];
// iterate over elements and filter
var res = data.filter(function (v) {
// get the count of the current element in array
// and filter based on the count
return data.filter(function (v1) {
// compare with current element
return v1 == v;
// check length
}).length == 1;
});
console.log(res);
I understand all the line in this code, but I don't understand how it can detect the length==1.
(My opinion) Because it loop though every single element in the array and return boolean value either true or false so how it can use to detect the length?
This code is to get the array element that only appear once in the array.
The return true is not to return the boolean, but when you return true from inside a filter, that item gets added to the resulting array of filter function.
The process here is called chaining of methods. On line 11, first, the filter is applied to the data array, then the .length method is applied to the result of the filter, which will be an array of equal length or less than the number of elements in the data array. Then finally only once the return is called, which will be a boolean used for the outer filter.
Another example:
function reverseString (inputString) {
return inputString.split("").reverse().join()
}
In the above example, the function returns after the split, then reverse on the split's result, then join on the reverse's result. The final resulting string is only returned.
Don't think about it as lines of code. Split each segment of code into its true meaning my padawan
data.filter(function (v1) {
// compare with current element
return v1 == v;
// check length
})
filters the data array to return an array of elements from data such that every element in it is equal to v
By checking if its length is 1, you want to return true if the the array has only 1 element.
So,
var res = data.filter(function (v) {
// return true if there is only 1 element in data which is equal to v
});
Which basically means, we are filtering data, and returning a new array where every element is such that it has only one occurrence in the array.
The function for filter returns a boolean, but filter itself returns an array.
So, for example:
array.filter(function(element) { return element >= 0; });
says that the function function(element) { return element >= 0; } will be applied to each element in array. Then, if the result of the function is true, that element will exist in the final, filtered version of array (which is what the filter function returns), or will not exist if the result is false.
In effect, all negative numbers from array will be filtered out because they return false.
What your code is saying is the following: For every element in data, filter data to keep only the elements equal to the current one. If this filtered list has only one element, then it can only be our current element. Therefor this element exists only once and should be kept in the final result, so we return true if .length == 1. In the end, once this is done for every element, filter is smart enough to convert those results of true and false into an array of the elements that produced true and leave out those that produced a false.
I will describe the filter method step by step in detail.
First, it takes a condition. then it returns an array with all values which passed the condition successfully
so think about this block of code as a condition
return data.filter(function (v1) {
// compare with current element
return v1 == v;
// check length
}).length == 1;
so let's take the first part of the condition
data.filter(function (v1) {
// compare with current element
return v1 == v;
// check length
})
and let's say we gonna start with the first element 1 , so how many times v1 === v ? the answer is two times so it will return this array [1, 1]
so forget the condition we wrote in the first lines and think about it like that [1,1].length === 1.
So [1,1].length === 1 will return false
The same will happen with number 2. so let's talk about the number 3. it will be [3].length === 1 which is true which will go to the upper level and that's why the res will be finally [3, 5, 4]

create custom function to remove all occurrence of element in an array

I have created below function to delete a custom element from an array:
Array.prototype.removeElement=function(x){
var index = this.indexOf(x);
if (index !== -1) {
this.splice(index, 1);
}
};
It works fine with below array:
var data = [1,2,3,4];
data.removeElement(2); //returns [1,3,4]
But when I have more than one item from a certain element it removes only first occurrence.
var data = [1,2,3,4,2];
data.removeElement(2);
// returns [1,3,4,2] while I expect to get [1,3,4]
I know I can do this by using loops, But I am curious to know if there is any cleaner code?
Using JS .filter() array method can be handy. Try this code,
// Create a function in array prototype as
Array.prototype.removeElement = function(x){
return this.filter((elem)=>elem!==x);
}
This should work a charm, but I don't think. There is any other way to do this other than looping.
2 solutions: one returns a new array and the other does it in-place
Solution 1: returns a new array
You can leverage the built-in filter method
function removeAllOccurences (array, element) {
return array.filter((ele) => ele !== element);
}
console.log(removeAllOccurences([1,2,3,4,3],3)); // [1,2,4]
Solution 2: in-place using recursion
function removeAllOccurences (array, element) {
if (!array.includes(element)) {
return array;
} else {
let index = array.indexOf(element);
array.splice(index, 1);
return removeAllOccurences(array, element);
}
}
console.log(removeAllOccurences([1,2,3,4,3],3)); // [1,2,4]
Try a while loop to continue using the splice method until that element is no longer present.
Array.prototype.removeElement=function(x){
var index = this.indexOf(x);
if (index !== -1) {
while (this.includes(x)) {
index = this.indexOf(x);
this.splice(index, 1);
}
}
}
The while loop uses the array.includes method to determine whether the array still contains that element's value, and if it does, it updates the index to the next element x, after which it will then splice the element like your code did. The while loop breaks when array.includesis false in turn, removing all the elements equal to x from the array.

Flatten nested arrays using recursion (and without using loops)

My logic for the problem, using the below as the input.
var input = [['A','B'],1,2,3,['C','D']]
Check first element to see if is an Array or not using Array.isArray(input)
If first element is array, call function, first element ['A,'B'] as argument.
The first element of the nested array is 'A' which is not an array, so push this element into a result array, and shift this element out. Repeat the function call.
When trying to flatten nested arrays using recursion, my input variable to the function keeps getting reassigned, preventing me from calling the function again using the original array. How do I prevent the original input variable from getting reassigned?
I understand this is not the complete solution, however I am stuck at when I shift the first element out of the nested array.
I've gone through step by step with this function, but there must be something I'm missing, another set of eyes would help greatly.
I've also been using my chrome developer tool, set breakpoints to monitor the function step by step.
//Defining original input variable
var input = [['A','B'],1,2,3,['C','D']]
function flat(array){
var result = []
var firstElement = array[0]
//CHECK IF FIRST ELEMENT IS ARRAY OR NOT
if(Array.isArray(firstElement)){
return flat(firstElement)
}
//IF ELEMENT NOT ARRAY, PUSH ELEMENT TO RESULT
else{result.push(firstElement)
array.shift() //removing child element
return (flat(array)) //call function on same array
}
if(array.length===0){return result}
}
First iteration:
firstElement = ['A','B'], Array.isArray(firstElement) would be true, hence call flat(firstElement)
Second Iteration:
firstElement = 'A', Array.isArray(firstElement) is false, so we
1. jump down to push this element into result
2. remove 'A' by using array.shift()
3. Call flat(array), where array is now ['B']
Third Iteration:
firstElement = 'B', Array.isArray(firstElement) is false
1. jump down to push this element into result, result is now only ['B'] since I've reset the result when I recalled the function.
2. remove 'B' by using array.shift(), array is now empty, ->[ ]
3. How can I step out, and use flat() on the original input array?
Your code doesn't consider the following elements if the first element is an array. The solution below uses array.concat(...) to combine both the result of the recursion (going down the tree), but also to combine the results of processing the rest of the list (in the same level). Visualizing the problem as a tree, often helps with recursions IMO:
[] 1 2 3 []
| |
A [] C D
|
B C
So perhaps it is more clear here, that we must both concat the result of the recursion and the result of taking a "step" to the right (recursion again) which would otherwise be a loop iterating the array.
var input = [['A',['B', 'C']],1,2,3,['C','D']]
function flat(array) {
var result = []
if (array.length == 0) return result;
if (Array.isArray(array[0])) {
result = result.concat(flat(array[0])); // Step down
} else {
result.push(array[0]);
}
result = result.concat(flat(array.slice(1))) // Step right
return result;
}
console.log(flat(input));
// ["A", "B", "C", 1, 2, 3, "C", "D"]
This is somewhat analogous to a version with loops:
function flat(array) {
var result = []
for (var i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
result = result.concat(flat(array[i]));
} else {
result.push(array[i]);
}
}
return result;
}
EDIT: For debugging purposes, you can track the depth to help get an overview of what happens where:
var input = [['A',['B', 'C']],1,2,3,['C','D']]
function flat(array, depth) {
var result = []
if (array.length == 0) return result;
if (Array.isArray(array[0])) {
result = result.concat(flat(array[0], depth + 1));
} else {
result.push(array[0]);
}
var res1 = flat(array.slice(1), depth);
console.log("Depth: " + depth + " | Concatenating: [" + result + "] with: [" + res1 + "]");
result = result.concat(res1)
return result;
}
console.log(flat(input, 0));
If you want to avoid loops, and I'm considering concating/spreading arrays as loops, you need to pass the result array to your function.
const input = [['A', 'B'], 1, 2, 3, ['C', 'D']]
// Start the function with an empty result array.
function flat(array, result = []) {
if (!array.length)
return result
// Extract first element.
const first = array.shift()
// Call the function with the array element and result array.
if (Array.isArray(first))
flat(first, result)
// Or add the non array element.
else
result.push(first)
// Call the function with the rest of the array and result array.
flat(array, result)
return result
}
console.log(flat(input))
Here is my answer if you are using JavaScript
You can use the below one line code to flatten n level nested Array
let flattendArray = input.flat(Infinity);
Or use this approach using reduce and concat
function flatDeep(arr, d = 1) {
return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), [])
: arr.slice();
};
Refer this link

Check if array1 is contained in array2 with elements in exactly the same order?

Call this array 1:
["one","two","three","four","five"]
I have a second array, array 2, which is a sub-set of array 1. It might look like this:
["one","two"]
I now want to check whether the elements of array 2 are in the same order as the elements in array 1. It doesn't matter how many elements there are in array 2, however they need to be in the same order.
So for example:
["one","two"] ==> true
["two","three"] ==> true
["one","three"] ==> false
["two","one"] ==> false
I believe I have solved this, but I am wondering whether there isn't a better solution or way to approach it, and of course I'm wondering whether there are maybe hidden assumptions I shouldn't have made or errors I have committed. My solution looks like this:
let arr = ["s1","s2","s3","s4","s5","s6"];
let subArray = ["s4","s5"];
let counter = 0;
for(let i=0;i<=arr.length;i++) {
if(arr[i] === subArray[counter]) {
counter++;
} else if(counter>0) {
counter=0;
}
if(counter === subArray.length) {
console.log('consecutive');
return true;
}
}
if(counter !== subArray.length) {
console.log('not consecutive i guess');
return false;
}
Use Array.findIndex() to find the index (startIndex) in the containing array of the 1st element in the sub array. Then iterate the the sub array with Array.every(), and check that starting from the startIndex, the sub array's items have an equivalent in the containing array:
const isContained = (arr1, arr2) => {
const startIndex = arr1.findIndex((item) => item === arr2[0]);
if(startIndex === -1) return false;
return arr2.every((item, i) => item === arr1[i + startIndex]);
}
console.log(isContained(["s1","s2","s3","s4","s5","s6"], ["s4","s5"])); // true
console.log(isContained(["s1","s2","s3","s4","s5","s6"], ["s6","s5"])); // false
You could do something sneaky like joining both of the array elements and then seeing if the test array's elements joined is a substring of the original array's elements joined.
Ex:
function containsElementsInSameOrder(arr, elements) {
return arr.join(' ').indexOf(elements.join(' ')) > -1;
}

Remove Array by Value - Real Time Data

I have a Real-Time web app, receiving push events from Node JS, adding to and removing from an array in Javascript. I have found after doing the typical splice to remove the item by the matching value's index, the size of the array decreases as expected, but sometimes the element isn't getting removed.
My theory is the real time data is adding to and removing from the array in parallel for different places in the array, and sometimes the index position becomes out of date at the moment in time it's about to do the splice.
How can I remove an item from an array by value in real time if the index is always changing?
for(var i=0; i<data.geometries.length; i++) {
if(data.geometries[i].id == item.id) { //Found Item in Array
//do some stuff
data.geometries.splice(i,1); //Remove Geometry
break;
}
}
You could use .filter method:
var result = data.geometries.filter(function (e) {
return e.id !== item.id;
});
What you need is a sparse array, where you can have an array with gaps or holes. Using splice to modify the array will affect any indexes that come after the removed element. So, you could use a regular array as a sparse array, but you would delete items differently. For example:
// array to act as sparse array
var set = [1,2,3,4];
// remove an item by value
var pos = set.indexOf(3);
if (pos !== -1) {
// don't 'splice', just set to undefined
set[pos] = undefined;
}
// add an item
if (set.indexOf(5) == -1) {
set.push(5);
}
// to get the values from sparse array
var values = set.filter(function(v) { return typeof v !== 'undefined' });
console.log(values);
// => [1, 2, 4, 5]
This way, your indexes stay the same across parallel processes and threads.

Categories