Reduce an array to groups - javascript

I have this array:
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
I would like to reduce it to this:
['blueblue', 'green', 'blue', 'yellowyellow', 'green']
Where it concats the values if they are the same. Once the value changes it starts again.
Trying to use reduce but not sure how to make it work as the acc needs to be a string and an array depending on if the value is the same or not!
let lastType = vals[0]
const groups = vals.reduce((acc, value) => {
if (lastType === value) {
acc += value // string
}
lastType = value
return acc.push(value)
}, [])

The final result is an array, so that is what acc should be.
Instead of appending value to acc, append it to the last element of the array:
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
let lastType = null;
const groups = vals.reduce((acc, value) => {
if (lastType === value) {
acc[acc.length - 1] += value;
} else {
lastType = value
acc.push(value)
}
return acc;
}, [])
console.log(groups);
The use of lastType in a closure is a bit of a code smell, as is the use of mutability in the reducer.
A preferable, though slightly more verbose approach:
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
const { groups } = vals.reduce(({ lastType, groups }, value) => {
if (lastType === value) {
return {
lastType,
groups: [
...groups.slice(0, groups.length - 2),
groups[groups.length - 1] + value
],
};
}
return {
lastType: value,
groups: [...groups, value],
};
}, { groups: [], lastType: null })
console.log(groups);

You need to check the element at the index in front of the actual index and add a new string to the accumulator.
const
values = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green'],
result = values.reduce((accumulator, value, index, array) => {
if (value === array[index - 1]) accumulator[accumulator.length - 1] += value;
else accumulator.push(value);
return accumulator;
}, []);
console.log(result);

Push an empty string to the accumulator if the type is new. Remember that .push returns the new length of the array, so don't return it at the bottom of the function - instead, return the whole accumulator:
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
let lastType;
const groups = vals.reduce((acc, value) => {
if (lastType !== value) {
acc.push('');
lastType = value;
}
acc[acc.length - 1] += value;
return acc;
}, [])
console.log(groups);

You need to keep track of a bit more. Basic way of doing it is using an bject to hold the state.
const vals = ['blue', 'blue', 'green', 'blue', 'yellow', 'yellow', 'green']
const groups = vals.reduce((acc, value, index, array) => {
// is it the same, duplicate it
if (acc.lastType === value) {
acc.current += value;
} else {
// did we have a previous value? Add it to the array
if (acc.lastType) {
acc.result.push(acc.current);
}
// set the current type
acc.current = value;
acc.lastType = value;
}
// if we are at the end, add what we have to the array
if (index+1===array.length) {
acc.result.push(acc.current);
}
return acc;
}, { result: [], current: '', lastType: null }).result;
console.log(groups);

Related

remove specific element from all arrays inside object

I want to remove all 'main-1-3' elements inside arrays:
const a = {
white: ['main-1-1'],
red: ['main-1-3'],
orange: [],
green: [],
blue: [],
}
const colors = ['white', 'red', 'orange', 'green', 'blue'];
for(let i = 0; i < colors.length; i++) {
a[colors[i]].splice(a[colors[i]].indexOf('main-1-3'), 1);
}
console.log(a)
But as you see if there is no such element other elements are removed too! I want to only remove the 'main-1-3' in all of the arrays?
How can I fix this?
You should first check that the item really is in the array. indexOf an item that isn't in the array will return -1, which will mess up the splice call:
const a = {
white: ['main-1-1'],
red: ['main-1-3'],
orange: [],
green: [],
blue: [],
}
const colors = ['white', 'red', 'orange', 'green', 'blue'];
for(let i = 0; i < colors.length; i++) {
const index = a[colors[i]].indexOf('main-1-3');
if (index >= 0) {
a[colors[i]].splice(index, 1);
}
}
console.log(a)
You could just loop over a's Object.entries and filter out all the main-1-3 elements.
const a = {
white: ['main-1-1'],
red: ['main-1-3', 'main-1-1'],
orange: [],
green: [],
blue: ['main-1-2'],
}
for (let [key, arr] of Object.entries(a)) {
a[key] = arr.filter(el => el !== 'main-1-3');
}
console.log(a);
iterate through each array and filter out the item main-1-3:
const a = {
white: ['main-1-1'],
red: ['main-1-3'],
orange: [],
green: [],
blue: [],
}
const colors = ['white', 'red', 'orange', 'green', 'blue'];
colors.forEach(e => a[e] = a[e].filter(n => n !== 'main-1-3'))
console.log(a)
Just check whether string 'main-1-3' exist in arrays in a.
Because if the string main-1-3 doesnot exist in the arrays in object a, the index will return -1.
Array.splice(-1, 1) will delete the last node in the array, this is what happening in your case.
Array.splice(-1, 1) Example
const myArray = [1, 2, 3, 4];
myArray.splice(-1, 1);
console.log(myArray); // Last element removed
As you see in the above example, Array.splice(-1, 1) deletes the last node. So you have to ensure that your index a[colors[i]].indexOf('main-1-3') is not -1 or else the last nodes in your array will be deleted.
Working Fiddle
const a = {
white: ['main-1-1'],
red: ['main-1-3'],
orange: [],
green: [],
blue: [],
}
const colors = ['white', 'red', 'orange', 'green', 'blue'];
for (let i = 0; i < colors.length; i++) {
const index = a[colors[i]].indexOf('main-1-3');
if (index > -1) {
a[colors[i]].splice(index, 1);
}
}
console.log(a)

Filter only unique values from an array of object javascript

I have an array of objects i want to filter only the unique style and is not repeated .
const arrayOfObj = [ {name:'a' , style:'p'} , {name:'b' , style:'q'} , {name:'c' , style:'q'}]
result expected : [ {name:'a' , style:'p'}]
Here is a solution in O(n) time complexity. You can iterate all entries to track how often an entry occurs. And then use the filter() function to filter the ones that occur only once.
const arrayOfObj = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" },
]
const styleCount = {}
arrayOfObj.forEach((obj) => {
styleCount[obj.style] = (styleCount[obj.style] || 0) + 1
})
const res = arrayOfObj.filter((obj) => styleCount[obj.style] === 1)
console.log(res)
On of the possible solutions depending on your performance / readability needs can be:
arrayOfObj.filter(a => arrayOfObj.filter(obj => obj.style === a.style).length === 1)
Use splice when you find the existing item and remove it
const arrayOfObj = [{
name: 'a',
style: 'p'
}, {
name: 'b',
style: 'q'
}, {
name: 'c',
style: 'q'
}]
const result = arrayOfObj.reduce((acc, x) => {
const index = acc.findIndex(y => y.style === x.style);
if (index >= 0) {
acc.splice(index, 1);
} else {
acc.push(x);
}
return acc;
}, [])
console.log(result)
Here is a solution in O(n) time complexity. You can iterate all entries to track how often an entry occurs. And then use the filter() function to filter the ones that occur only once.
const arrayOfObj = [ {name:'a' , style:'p'} , {name:'b' , style:'q'} , {name:'c' , style:'q'}];
let count = {};
arrayOfObj.forEach(({style}) => {
count[style] = (count[style] || 0) + 1;
});
let result = arrayOfObj.filter(({style}) => count[style] === 1);
console.log(result);
You reduce it. Check if in the array already an element with the same style exists and remove it from the accumulator otherwise push it to the accumulator
const arr = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" }
];
let result = arr.reduce((a,v) => {
let i = a.findIndex(el => el.style === v.style);
if(i !== -1) {
a.splice(i,1);
return a;
}
a.push(v)
return a;
},[])
console.log(result);
There is a one liner answer too if you are using lodash library
(uniqBy(array, iteratee))
const arr = [
{ name: "a", style: "p" },
{ name: "b", style: "q" },
{ name: "c", style: "q" }
];
let result = _.uniqBy(arrayOfObj,'style')
console.log(result)

JavaScript - array, object - How can I streamline this code

How can I streamline this code ?
I think it might have better way to refactor .
const aa = ['red', 'yellow', 'blue']
const bb = { first: 0, second: 1, third: 2 }
const cc = { ...bb }
cc.first = aa[cc.first]
cc.second = aa[cc.second]
cc.third = aa[cc.third]
You can use .reduce() and Object.entries() methods:
const aa = ['red', 'yellow', 'blue']
const bb = { first: 0, second: 1, third: 2 }
const cc = Object.entries(bb)
.reduce((r, [k, i]) => (r[k] = aa[i], r), {});
console.log(cc);
This is a solution that still works after adding or removing an item from aa or bb, without need to change your code.
const aa = ['red', 'yellow', 'blue']
const bb = { first: 0, second: 1, third: 2 }
const cc = {};
for(var key in bb)
cc[key] = aa[bb[key]];

Javascript - Index Each Element in an Array/List

> var lst = ['red', 'blue', 'yellow'];
> someFunc(lst);
[('red', 0), ('blue', 1), ('yellow', 2)]
Is there any way to do this in Javascript/JQuery? I know that I can simply just make a for loop and convert each of the original list's element to what I want, but I was wondering if there was a built in way of doing this.
Thanks!
You could use Array#map and return for each item an array with the value and the index.
var lst = ['red', 'blue', 'yellow'],
array = lst.map(function (a, i) { return [a, i]; });
console.log(array);
You can leverage the map() function that is providing you both the value and and index of each item in the array:
lst.map(function(v, i) { return { value: v, index: i }; } );
See MDN
With map method in ES6 :
var lst = ['red', 'blue', 'yellow'];
var array = lst.map((item, index) => [item, index])
console.log(array); // [ [ 'red', 0 ], [ 'blue', 1 ], [ 'yellow', 2 ] ]

How to turn the following recursive function into a pure function?

The following function appends an object into a nested array (by searching for it recursively):
function appendDeep (arr, obj, newObj) {
if (arr.indexOf(obj) !== -1) {
arr.splice(arr.indexOf(obj) + 1, 0, newObj)
} else {
arr.map(item => {
if (item.children) spliceDeep(item.children, obj)
})
}
}
Example:
const colors = {
children: [
{
name: 'white',
},
{
name: 'yellow',
children: [
{
name: 'black'
}
]
}
]
}
const color = {
name: 'black'
}
const newColor = {
name: 'brown'
}
appendDeep(colors.children, color, newColor)
Result:
children: [
[
{
name: 'white',
},
{
name: 'yellow',
children: [
{
name: 'black'
},
{
name: 'brown'
}
]
}
]
]
As you can see appendDeep returns a side-effect; it modifies arr. So I decided to return the array instead (so the function would become pure):
function findDeep (arr, obj) {
if (arr.indexOf(obj) !== -1) {
console.log(arr)
return arr
} else {
arr.map(item => {
if (item.children) findDeep(item.children, obj)
})
}
}
And use the new function like this:
const newArr = findDeep(colors.children, color)
newArr.splice(newArr.indexOf(color) + 1, 0, newColor)
But I get this error:
bundle.js:19893 Uncaught TypeError: Cannot read property 'splice' of undefined
What I'm a doing wrong?
(Note: Here's the CodePen.)
(Note 2: console.log(arr) does return the nested children. But for some reason they become undefined outside of the function.)
You are not returning you recursive findDeep method within the map. Return that for the recursion to work because your conditional branch is not returning anything from within map. As a result you are getting the result as undefined. JSBin
First, a find method that will return the array in which the requested item is located (as a direct child).
function findDeep(arr, obj) {
return arr.map((item) => {
if (item.name === obj.name) {
return arr;
} else if (item.children) {
return findDeep(item.children, obj);
} else {
return undefined;
}
}).reduce((prev, cur) => {
return prev ? prev : cur;
});
}
You could use that to append items to the list, but that will still modify the original array:
function appendDeep(arr, color, newColor) {
let found = findDeep(arr, color);
if (found) {
found.splice(found.indexOf(color) + 1, 0, newColor);
}
return arr;
}
If you don't want to modify the original array, things get more complex. That's because the standard array functions such as push and splice will modify the original array. There's no quick fix, at least not that I know of, because preferably you would not want to clone any more items than you really have to.
You don't need to clone black, but you do need to clone the array that contains it (It can simply reuse the existing object for black.) That means the object for yellow also needs to be cloned (to use the cloned array) and the array in which yellow is located needs to be cloned. But white, which is in the same array, is not modified and does not need to be cloned. I've not figured out how to do that properly.
This is a proposal which uses thisArgs of Array#some.
function appendDeep(object, search, insert) {
function iter(a) {
if (a.name === search.name) {
this.children.push(insert);
return true;
}
return Array.isArray(a.children) && a.children.some(iter, a);
}
object.children.some(iter, object);
}
var colors = { children: [{ name: 'white', }, { name: 'yellow', children: [{ name: 'black' }] }] },
color = { name: 'black' },
newColor = { name: 'brown' };
appendDeep(colors, color, newColor);
document.write('<pre>' + JSON.stringify(colors, 0, 4) + '</pre>');

Categories