Remove duplicates within the output of a reduce function - javascript

I am outputting the matched value of projects when the name of my logo matches the items object within projects with the help of a reduce function. However, whenever I click on multiple logos that both match project.items I am rendering duplicates.
Here is my code:
logos.reduce((acc, logo) => {
if (logo.active) {
Object.values(projects).forEach((proj) => {
if (Object.values(proj.items).includes(logo.name)) {
console.log(acc)
acc.push((<Project title={proj.title} routeName={proj.routeName} items={proj.items} description={proj.description}/>));
}
});
}
return acc
}, [])
My first idea was to create another array, run a for loop and iterate through the values like: filteredValues[i].props.title and push the contents of that loop to an array. I ran run a reduce on that array like this but I was not able to eliminate the duplicate:
const filteredArr = arr.reduce((acc, current) => {
const x = acc.find(item => item.title === current.title);
if (!x) {
return acc.concat([current]);
} else {
return acc;
}
}, []);
Anyway, here's the output of acc which I am using to render my Project component

May be below code is what you need.
const filteredArr = this.getUnique(arr, 'title');
getUnique(arr, comp) {
const unique = arr.map(e => e[comp]).map((e, i, final) => final.indexOf(e) === i && i).filter((e) => arr[e]).map(e => arr[e]);
return unique;
}
Steps involve is to:
Store the comparison values in array "arr.map(e => e[comp])"
Store the indexes of the unique objects ".....).map((e, i, final) => final.indexOf(e) === i && i)"
Eliminate the false indexes & return unique objects ".....).filter((e) => arr[e]).map(e => arr[e])"

You can write your original loop like this
logos
.reduce((acc, logo) => {
if (logo.active) {
Object.values(projects).forEach((proj) => {
if (
Object.values(proj.items).includes(logo.name) &&
!acc.find((item) => item.value === logo.name)
) {
console.log(acc);
acc.push({
value: logo.name,
component: (
<Project
title={proj.title}
routeName={proj.routeName}
items={proj.items}
description={proj.description}
/>
),
});
}
});
}
return acc;
}, [])
.map((values) => values.component);

You can use the Map object to filter out duplicates.
let arrFiltered = [];
// form map of unique arr items
const arrMap = new Map();
arr.forEach(item => arrMap.set(item.title, item));
// form array of all items in map
arrMap.forEach(item => arrFiltered.push(item));

I had to run a reduce function outside of the original forEach loop and check those values with a some function.
logos.reduce((acc, logo) => {
if (logo.active) {
Object.values(projects).forEach((proj) => {
if (Object.values(proj.items).includes(logo.name)) {
console.log(acc)
acc.push((<Project title={proj.title} routeName={proj.routeName} items={proj.items} description={proj.description}/>));
}
});
acc = acc.reduce(function (p, c) {
if (!p.some(function (el) { return el.props.title === c.props.title; })) p.push(c);
return p;
}, []);
}
return acc
}, [])

Related

Why first element of array is empty?

I have a function which returns an Observable and once its fires I'm looping the subscribed array
this.productService.getProductBySeo(proudctName).pipe(
catchError(e => of(null)),
this.unsubsribeOnDestroy
).subscribe(res => {
if (res) {
this.categoriesService.currentCategory.subscribe(menus => {
const activeElem = res;
const allItems = menus;
allItems.map(item => {
console.log(item)
console.log(item.id) // can see id of each elem
console.log(item.children) // first element of item.children is empty
....
UPD 1
I have changed to switchMap as it was advised in comments , anyway the arrays is empty
this.productService.getProductBySeo(proudctName).pipe(
catchError(e => of(null)),
switchMap(res => {
this.categoriesService.currentCategory.subscribe(menus => {
if (menus) {
const activeElem = res;
const allItems = menus;
allItems.map(item => {
console.log(item)
// console.log(item.id) // can see id ,
console.log(item.children) // still empty :-(
item.children.map(item => {
// console.log(item);
return item
})
const fountObj = item.children.find(el => {
// console.log('element ', el)
return el === activeElem;
});
if (fountObj) {
this.totalStyle = item.seo_url; // to identify total style of the component
}
})
}
// console.log(menus)
})
return of(res)
}),
this.unsubsribeOnDestroy
).subscribe(res => {
if (res) {
this.product = res;
Regarding what do I expect to see is when subscription fires in this.categoriesService.currentCategory.subscribe, I want to see item.children in console. As I see only empty array when log item.children , but when I console just item, I can see that item.children has 2 items in own Array
Not a great solution, But I suspect that it might have been changed by reference on somewhere else. Try deep cloning and looping.
i.e.
const allItems = JSON.parse(JSON.stringify(menus));
allItems.forEach(item => {
console.log(item)
console.log(item.children)
Also, forEach looks much better than a map in such circumstances.

Invert key value in js object

I can't figure out how I can change :
{"first":["de"], "second":["ab","de"], "third":["de"]}
to:
{"de":["first", "second", "third"], "ab":["second"]}
I want to associate unique values with list of containing keys. What I tried(but I think I'm far from it):
const data = {
"first":["de"],
"second":["ab","de"],
"third":["de"]
}
console.log(
Object
.keys(data).reduce(function(obj, key) {
obj[data[key]] = key;
return obj;
}, {})
)
Thanks for your help!
Object.entries to get it into an array, reduce to build the new object, and forEach to loop over the array
const o = {"first":["de"], "second":["ab","de"], "third":["de"]}
const result = Object.entries(o).reduce((obj, [key, arr])=>{
arr.forEach(lng => {
obj[lng] = obj[lng] || [];
obj[lng].push(key);
})
return obj
}, {});
console.log(result);
You have to loop the array and for each item in the array check if an array for that value exists in the accumulator or not before adding it:
let result = Object.entries(data).reduce((acc, [key, arr]) => { // for each key-array of the original object
arr.forEach(value => { // for each value in the array
acc[value] = acc[value] || []; // create an array in the output object if it doesn't already exist
acc[value].push(key); // push the key to it
});
return acc;
}, {});
I also used Object.entries with each entry desctuctured as [key, arr] so I don't have to use the extra [key] to get the array while using Object.keys.
Demo:
let data = {"first":["de"], "second":["ab","de"], "third":["de"]};
let result = Object.entries(data).reduce((acc, [key, arr]) => {
arr.forEach(value => {
acc[value] = acc[value] || [];
acc[value].push(key);
});
return acc;
}, {});
console.log(result);
On reduce callback, data[key] is an array of string values. So it is needed to loop that data[key] array values and assign value for each array item.
const data = {
"first":["de"],
"second":["ab","de"],
"third":["de"]
}
console.log(
Object.keys(data).reduce(function(obj, key) {
data[key].forEach((val) => {
obj[val] ? obj[val].push(key) : obj[val] = [ key ];
});
return obj;
}, {})
)
Try this (naive solution), if this works for you
const data = { first: ["de"], second: ["ab", "de"], third: ["de"] };
let dataMap = new Map();
Object.keys(data).forEach((key) => {
data[key].forEach((val) => {
if (dataMap.has(val)) {
dataMap.set(val, [...dataMap.get(val), key]);
} else {
dataMap.set(val, [key]);
}
});
});
let nData = [];
dataMap.forEach((value, key) => {
nData.push({
[key]: value
});
});
console.log(nData);
You could take a double reduce with the entries.
const
data = { first: ["de"], second: ["ab", "de"], third: ["de"] },
result = Object
.entries(data)
.reduce((o, [value, keys]) => keys.reduce((q, key) => {
(q[key] ??= []).push(value);
return q;
}, o), {});
console.log(result);
I'm not using reduce but here's a "bruteforce" for your problem which works:
res = {};
Object.keys(data).forEach(key => {
data[key].forEach(el => {
if (! res[el])
res[el] = [];
if (! res[el].includes(key))
res[el].push(key);
})
});

reduce inside function returns undefined (?)

in this sample using reduce on array of objects works fine but when i insert it all into a function it starts to return undefined... it probably has to do with me not understanding how returning stuff from functions works
const egArrOfObj = [
{
name:'tomato',
},
{
name:'potato',
},
];
console.log(
egArrOfObj
.reduce((acc, curr) => {
return [...acc, curr.name]
} ,[] )
);
const namesFunction = (arrOfObj) => {
arrOfObj
.reduce((acc, curr) => {
return [...acc, curr.name]
} ,[] )
};
const names = namesFunction(egArrOfObj);
console.log(names)
If you add the return statement into your function then it will work as well.
Like the following:
const egArrOfObj = [{
name:'tomato',
},
{
name:'potato',
}];
const namesFunction = (arrOfObj) => {
return arrOfObj.reduce((acc, curr) => {
return [...acc, curr.name]
}, [])
};
console.log(namesFunction(egArrOfObj));
I hope that helps!

How to use array push with filter and map method in JavaScript

I have a filter method that filters an item from an array using an if condition. Using the filterArray I then use map method.
I would like to add a second condition and push a new array called OLD_ITEMS to the ITEMS array. How would I go about doing this?
import { OLD_ITEMS, ITEMS } from './constants';
let filterArray = ITEMS.filter(item => {
if (item.id === 'three') {
return false;
}
return true;
});
// TODO: add second condition here to push `OLD_TIMES` to `ITEMS`
const options = Object.assign(
...filterArray.map(({ id, name }) => ({ [id]: name }))
);
You need to be a little more clear about what you mean by "push" OLD_ITEMS to Items. Do you want to concatenate/push ALL of the OLD_ITEMS to the end of if a single condition is met, or do you want to push subset of OLD_ITEMS that meet a certain condition?
I believe that this is what you're looking for, but it's hard to know exactly:
import { OLD_ITEMS, ITEMS } from './constants';
let filterArray = ITEMS.filter(item => {
if (item.id === 'three') {
return false;
}
return true;
});
// TODO: add second condition here to push `OLD_TIMES` to `ITEMS`
const validOldItems = OLD_ITEMS.filter(item => {
if (item === 'some_condition') {
return false;
}
return true;
}
filterArray.push(validOldItems);
const options = Object.assign(
...filterArray.map(({ id, name }) => ({ [id]: name }))
);
Also, I highly recommend making your code more concise by returning the value of the conditional check, instead of an if/then
let filterArray = ITEMS.filter(item => {
return (item.id === 'three');
});
Or even more concise
let filterArray = ITEMS.filter(item => (item.id === 'three'));
Grand finale of conciseness:
const filterArray = ITEMS.filter(item => (item.id === 'three'))
.concat(OLD_ITEMS.filter(item => (item.id === 'some_condition'))
.map(({ id, name }) => ({ [id]: name }))
const options = Object.assign(...filterArray);

Map array items through several functions

Is there a more elegant way then this to execute several functions in succession for each item in the array:
type Transform<T> = (o: T) => T;
type Item = { /* properties */ };
transform(input, transformers: Transform<Item>[]) {
const items: Item[] = getItems(input);
return items.map(item => {
let transformed = item;
tramsformers.forEach(t => transformed = t(transformed));
return transformed;
})
}
This is a great use case for reduce:
transform(input, transformers: Transform<Item>[]) {
const items: Item[] = getItems(input);
return items.map(item => transformers.reduce((val, transformer) => transformer(val), item));
}
Or perhaps more readably:
transform(input, transformers: Transform<Item>[]) {
const items: Item[] = getItems(input);
return items.map(
item => transformers.reduce(
(val, transformer) => transformer(val),
item
)
);
}
Live Example:
function getItems(input) {
return [
"abcdefg",
"1234567"
];
}
function transform(input, transformers) {
const items = getItems(input);
return items.map(item => transformers.reduce((val, transformer) => transformer(val), item));
}
const result = transform("x", [
v => v.toUpperCase(),
v => v.substring(1, v.length - 1)
]);
console.log(result);
As Nitzan Tomer points out, we could do away with the items constant:
transform(input, transformers: Transform<Item>[]) {
return getItems(input).map(
item => transformers.reduce(
(val, transformer) => transformer(val),
item
)
);
}
I frequently keep those sorts of things for debugging, but some good debuggers now make it easy to see the return value of functions before they return (Chrome's does), so if you removed it, you could step into getItems to see the items before the map.
Here's a bit more reusable version, based on #T.J.Crowder's answer:
export type Transform<T> = (o: T) => T;
export function pipe<T>(sequence: Transform<T>[] = []) {
return (item: T) => sequence.reduce((value, next) => next(value), item);
}
transform(input, transformers?) {
return getItems(input).map( pipe(transformers) );
}
Note that type is inferred from getItems(input) and return type is transform(): Item[].

Categories