I have an object that has the key ID and some status key. I wanna count how many times the same status repeats itself.
My object will look like this
const items = { id: 2, status_a: 1, status_b: 1, status_c: 3 };
This is my code
curr.userBuyer is a number.
curr.status is a string, for each equal status, i want to add 1 to it
const userBuyers: any[] = buyers.reduce((acc: any[], curr) => {
if (acc.filter((item) => item.id == curr.userBuyer).length == 0) {
//If the there's no item in acc, then it gets pushed into it
acc.push({ id: curr.userBuyer, [curr.status]: 1 });
return acc;
} else {
//If acc already have that item
acc = acc.map((retail) => {
//Check if that key already exists
const isKey = retail[curr.status] ? true : false;
//If the key exists, then it will add 1 to the existing value, else it will set it as 1
return { ...retail, [curr.status]: isKey ? retail[curr.status]++ : 1 };
});
return acc;
}
}, []);
This is what is returning
[
{
id: 713,
delivered: 1,
sold: 1,
in_delivery: 1,
},
{
id: 833,
delivered: 1,
sold: 1,
in_delivery: 1,
},
];
It's not adding 1 to the status that already exists.
What am i doing wrong?
You have two errors:
You are using the postfix operator to increment count retail[curr.status]++, instead, use the prefix operator ++retail[curr.status] or simply (retail[curr.status] + 1).
You are updating all the items, in an account, only the matching item should be updated.
Try this:
const userBuyers: any[] = buyers.reduce((acc: any[], curr) => {
if (acc.filter((item) => item.id == curr.userBuyer).length == 0) {
acc.push({ id: curr.userBuyer, [curr.status]: 1 });
return acc;
} else {
acc = acc.map((retail) => {
if (retail.id === curr.userBuyer) {
const isKey = retail[curr.status] ? true : false;
return { ...retail, [curr.status]: isKey ? retail[curr.status] + 1 : 1 };
}
return retail;
});
return acc;
}
}, []);
Related
I have an array of objects, objects that contain an order: number; property.
I want for each object in that array that has the order property higher than a specific value to have it decreased it by one.
Any simple way to achieve this?
myArray.forEach(x => x.order >= someValue)...
You can map the result, updating the value when >= someValue
const someValue = 3;
const result = [
{ order: 1 },
{ order: 2 },
{ order: 3 },
{ order: 4 }
].map(x => x.order >= someValue ? { ...x, order: x.order - 1 } : x);
console.log(result);
With array.reduce
const value = 2;
const testArray = [{ order: 1 }, { order: 1 }, { order: 3 }];
const result = testArray.reduce((acc, curr) => {
if (curr.order > value) {
return [...acc, { ...curr, order: curr.order - 1 }];
}
return [...acc, curr];
}, []);
console.log(result);
Several options to reach this:
Adding functionality to Array.prototype.
if(!Array.prototype.decreaseOrderIfBiggerThan) {
Array.prototype.decreaseOrderIfBiggerThan = function(value) {
if(!value || typeof value != 'number') return this;
return this.map((el) => ({
...el,
order: el?.order > value ? --el.order : el.order
}))
}
}
Simply mapping:
myArray = myArray.map((el) => ({
...el,
order: el?.order > value ? --el.order : el.order
}))
Mutating original array: it's basically #2 method but without assigning new array to original one and with forEach.
Hopes this is what you needed.
I am trying to write a find function to find items from matched items from a potentially nested array (without having to flat the array first) and I am trying to write in a FP way.
Here is my attempt:
const nestedArray = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
function findTarget(arr, predicate) {
const helper = ([x, ...xs]) =>
x === undefined
? null
: predicate(x)
? x
: Array.isArray(x)
? helper(x) ?? helper(xs)
: helper(xs)
return helper(arr)
}
findTarget(nestedArray, (item) => item.id === 5)
I think it works but it is not super readable and I am sure there are better ways to write such a function.
Here's how I would implement this using recursion:
function findTarget(value, predicate) {
const isArray = Array.isArray(value);
// Base case: if value is not array and predicate matches, we found a match
if (!isArray) {
if (predicate(value)) return value;
return null;
}
// value must be an array, so run recursion and see if value exists
for (const item of value) {
const foundItem = findTarget(item, predicate);
if (foundItem !== null) {
return foundItem;
}
}
// nothing found
return null;
}
does the same thing that your code does and imo looks cleaner.
Since your example is calling predicate(x) in the first place, it will return a false positive when matching an array with an id: 5 property, so the Array.isArray(x) should go first to avoid this:
const nestedArray = [
Object.assign([{ id: 1 }], { id: 5 }),
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }], null, [[{ id: 5 }]]],
{ id: 6 },
]
function findTargetLoop (arr, match) {
if (!Array.isArray(arr))
return arr && match(arr) ? arr : null;
let item, i = 0;
while (!(item = findTargetLoop(arr[i++], match)) && i < arr.length);
return item ?? null;
}
const findTargetFunc = (arr, match, next) =>
(next = ([item, ...rest]) =>
Array.isArray(item) ? next(item) ?? next(rest)
: item && match(item) ? item
: rest.length ? next(rest) : null)(arr);
const match = item => item.id === 5;
console.log('with iterations', findTargetLoop(nestedArray, match));
console.log('pure functional', findTargetFunc(nestedArray, match));
Here's one approach I can think of. It uses the init function as a sentinel value to distinguish whether the element being searched for has already been found. Before returning, it invokes the accumulated value which is either () => undefined, or () => curr capturing the first element that matches the predicate.
const flatFind = (array, predicate) => {
const init = () => undefined
const reducer = (prev, curr) => (
prev === init
? Array.isArray(curr)
? curr.reduce(reducer, init)
: predicate(curr)
? () => curr
: init
: prev
)
return array.reduce(reducer, init)()
}
const nestedArray = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
console.log(flatFind(nestedArray, item => item.id === 5))
Your form is a good start but the helper is unnecessary and the order of conditions should be changed. Here we use inductive reasoning -
If x is undefined, there is nothing left to match. Return no match.
(inductive) x is defined. If x is an array, find within x or find within xs
(inductive) x is defined and x is a non-array. If x matches predicate f, return the match
(inductive) x is defined and x is a non-array that does not match the predicate f. Find within the sub-problem, xs.
const find = ([x, ...xs], f) =>
x === undefined // 1
? null
: Array.isArray(x) // 2
? find(x, f) ?? find(xs, f)
: f(x) // 3
? x
: find(xs, f) // 4
const t = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
console.log(find(t, (item) => item?.id === 5)) // { id: 5 }
console.log(find(t, (item) => item?.id === 9)) // null
Note, the behavior of your findTarget checks child arrays against the predicate and allows for return of arrays that match the predicate. This is inconsistent behaviour as it's not possible to match the outermost array and the predicate checks for .id properties on arrays. The find solution above avoids this by changing the order of conditions 2 and 3. If you want the ability to return array matches with your function, you can change the order to 1,3,2,4.
I have an array of objects and I want to add an element to specific index when a certain attribute changes compared to the previous one.
We have:
const arr = [
{ num: 1 },
{ num: 1 },
{ num: 1 },
{ num: 3 },
{ num: 3 },
{ num: 4 },
{ num: 5 },
];
I want it to become
const arr = [
{ separator:true }
{ num: 1 },
{ num: 1 },
{ num: 1 },
{ separator:true }
{ num: 3 },
{ num: 3 },
{ separator:true }
{ num: 4 },
{ separator:true }
{ num: 5 },
];
I did this:
const getIndexes = (myArr) => {
let indexes = [];
let previousValue = null;
myArr.forEach((el, idx) => {
if (el.num !== previousValue) {
indexes.push(idx);
previousValue = el.num;
}
});
return indexes;
};
const insertSeparator = (arr) => {
let result = arr;
getIndexes(arr).forEach((position) => result.splice(position, 0, { separator: true }));
return result
};
and it returns:
[
{ separator: true },
{ num: 1 },
{ num: 1 },
{ separator: true },
{ num: 1 },
{ separator: true },
{ separator: true },
{ num: 3 },
{ num: 3 },
{ num: 4 },
{ num: 5 }
]
Maybe because of the "new" size of the array, because it is getting bigger and changes its dimension.
What do you think is the best way to solve this?
Run it through .flatMap()
const result = arr.flatMap((obj, idx, arr) => {...
.flatMap() is .map() and .flat() combined, so it transforms the contents of a copy of the given array and removes the brackets []. Next, we return the first object with a separator:
if (idx == 0) {
// returns are wrapped in brackets because they'll be removed before being returned
return [{separator: true}, obj];
}
The next step is to compare the current value with the previous value:
obj.num == arr[idx - 1].num ? // current value vs previous value
[arr[idx - 1]] : // if they are the same value return previous value
[{separator: true}, obj]; /* if they are not the same then return that separator
and current */
const arr = [
{ num: 1 },
{ num: 1 },
{ num: 1 },
{ num: 3 },
{ num: 3 },
{ num: 4 },
{ num: 5 },
];
const result = arr.flatMap((obj, idx, arr) => {
if (idx == 0) {
return [{
separator: true
}, obj];
}
return obj.num == arr[idx - 1].num ? [arr[idx - 1]] : [{
separator: true
}, obj];
});
console.log(JSON.stringify(result, null, 2));
I propose this solution which would consume only one iteration with a reduce :
const arr = [{
num: 1
},
{
num: 1
},
{
num: 1
},
{
num: 3
},
{
num: 3
},
{
num: 4
},
{
num: 5
},
];
let prev_value = arr[0];
const result = arr.reduce((acc, val) => {
const insert = (val.num !== prev_value.num) ? [{
separator: true
}, val] : [val];
prev_value = val;
return acc.concat(insert)
}, [{
separator: true
}, ])
console.log(result)
There must be other ways to do it too. But with a simple modification to your code it can be done. You just need to keep track of the offset with a new variable, incrementing it in the loop:
const arr = [
{ num: 1 },
{ num: 1 },
{ num: 1 },
{ num: 3 },
{ num: 3 },
{ num: 4 },
{ num: 5 },
];
const getIndexes = (myArr) => {
let indexes = [];
let previousValue = null;
myArr.forEach((el, idx) => {
if (el.num !== previousValue) {
indexes.push(idx);
previousValue = el.num;
}
});
return indexes;
};
const insertSeparator = (arr) => {
let result = [...arr];
let offset = -1;
getIndexes(arr).forEach((position) => {
offset++;
return result.splice(position+offset, 0, { separator: true });
});
return result
};
console.log(insertSeparator(arr));
Note: If you want to start with 0 you can do the increment in the .splice() itself : result.splice(position+(offset++),
const positions = [];
//arr.sort((a, b) => a.num - b.num); You can uncomment this line to ensure that the array will always sorted based on num property
arr.forEach((item, index) => {
if (index < arr.length - 1 && item.num != arr[index + 1].num) {
positions.push(index + 1);
}
});
let counter = 0;
positions.forEach((pos) => {
arr.splice(pos + counter++, 0, { separator: true });
});
console.log(arr);
You want to:
Do something which each item in a list
Want to return something other than a list of the same size.
Then I would suggest the good all-round Array.prototype.reduce() function.
const separator = {separator: true};
arr.reduce((result, item) => {
if (result.at(-1)?.num === item.num) {
return [...result, separator, item];
}
return [...result, item]
}, [])
This is (according to me) easier, cleaner and safer since it doesn't mutate variables.
Note
Array.prototype.at() is at the time of writing a new function. If you are using an ancient browser that doesn't support it you can use arr[arr.length -1] to get the last item instead.
I have the following sample arr:
const fetchedArr = [
{ id: "3cc74658-a984-4227-98b0-8c28daf7d3d4", type: a },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: a },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: b }
]
i need the following output :
const arr = [
{ id: "3cc74658-a984-4227-98b0-8c28daf7d3d4", type: a, checked: true },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: a, checked: true, hasPair: true }
]
I have the following snippet which works
const newLegendItems = fetchedArr
.reduce((acc, curr, idx, arr) => {
const singleComponentLines = arr.filter((g) => g.id === curr.id);
const exists = !!acc.find((x) => x.id === curr.id);
if (!exists) {
if (singleComponentLines.length === 2 && singleComponentLines.includes(curr)) {
acc[idx] = {...curr, hasPair: true};
} else {
acc[idx] = curr;
}
}
return acc;
}, [])
.map((l) => ({ ...l, checked: true }));
, but i was thinking if there's simpler way to achieve this?
I should clarify that in the fetchedArr, the type does not matter, and that there won't be more than two same Id's, hence my idea for singleComponentLines.length === 2.
Like this?
const fetchedArr = [
{ id: "3cc74658-a984-4227-98b0-8c28daf7d3d4", type: "a" },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: "a" },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: "b" }
];
let result = fetchedArr.reduce((acc,v) => {
//first i need to check if i already have an element with the same ID in my accumulator. i either get -1 for not found or the index where the element is.
let i = acc.findIndex(el => el.id === v.id);
if(i !== -1) {
//if there is an element then access the element in the array with a[i] and add a new property to the object with ["hasPair"] and set it to true
acc[i]["hasPair"] = true;
return acc;
}
//in case i = -1 what means not found
return [...acc, {...v, checked: true}];
},[])
console.log(result);
I don't fully understand your question but it should help:
const result = [{
id: "3cc74658-a984-4227-98b0-8c28daf7d3d4",
type: 'a'
},
{
id: "9b96e055-dc2a-418c-9f96-ef449e34db60",
type: 'a'
},
{
id: "9b96e055-dc2a-418c-9f96-ef449e34db60",
type: 'b'
}
].reduce((acc, el) => {
const idx = acc.findIndex(it => it.id === el.id);
if (idx > -1) {
acc[idx] = { ...acc[idx],
hasPair: true
}
} else {
acc.push({ ...el,
checked: true
});
}
return acc;
}, []);
console.log(result)
I rather use a Map for this kind of things since it brings more readability IMO.
Start by checking if we already have it
Update our component and add it to the Map
The only "tricky" thing is that we need to iterate over .values() to grab our updated components, but thanks to spread operator it's quite easy.
const components = [
{ id: "3cc74658-a984-4227-98b0-8c28daf7d3d4", type: 'a' },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: 'a' },
{ id: "9b96e055-dc2a-418c-9f96-ef449e34db60", type: 'b' },
];
const newLegendItems = components
.reduce((acc, component) => {
if (acc.has(component.id)) {
acc.get(component.id)['hasPair'] = true;
} else {
acc.set(component.id, { ...component, checked: true });
}
return acc;
}, new Map());
console.log([...newLegendItems.values()]);
I have read a few questions and answers on it already. It looks like my recursive function has got enough "return" statements, so... I do not know why it returns undefined... I have added extra log statement to show that the function itself finds the element, but does not return it...
let animals = [
{
name: "dogs",
id: 1,
children: [
{
name: "lessie",
id: 2
},
{
name: "bark-a-lot",
id: 3
}
]
},
{
name: "cats",
id: 4,
children: [
{
name: "meows-a-lot",
id: 5,
children: [
{
name: "meows-a-lot-in-the-morning",
id: 6
}
]
},
{
name: "whisk-ass",
id: 7
}
]
}
];
function recurseFind(node, id) {
if (Array.isArray(node)) {
return node.forEach(el => {
return recurseFind(el, id);
});
} else {
if (node.id === id) {
console.log("node matched", node.id, id, node);
return node;
} else if (node.children) {
return node.children.forEach(child => {
return recurseFind(child, id);
});
} else {
return "not found";
}
}
}
const found = recurseFind(animals, 6);
console.log("found", found, "wtf");
forEach returns undefined, so
return node.forEach(el => {
return recurseFind(el, id);
});
will always return undefined, no matter what the recursive calls find.
I'd use a for loop instead, and if a match is found, return it:
let animals = [
{
name: "dogs",
id: 1,
children: [
{
name: "lessie",
id: 2
},
{
name: "bark-a-lot",
id: 3
}
]
},
{
name: "cats",
id: 4,
children: [
{
name: "meows-a-lot",
id: 5,
children: [
{
name: "meows-a-lot-in-the-morning",
id: 6
}
]
},
{
name: "whisk-ass",
id: 7
}
]
}
];
function recurseFind(node, id) {
if (Array.isArray(node)) {
for (const el of node) {
const result = recurseFind(el, id);
if (result) return result;
}
} else {
if (node.id === id) {
return node;
} else if (node.children) {
for (const child of node.children) {
const result = recurseFind(child, id);
if (result) return result;
}
}
}
}
const found = recurseFind(animals, 6) || 'not found';
console.log("found", found);
VLAZ and CertainPerformance already pointed out why your function wasn't working.
Here's an alternative technique that seems a little simpler to me:
const recursiveFind = (pred) => (xs) => xs .reduce (
(r, x) => r != null ? r : pred (x) ? x : recursiveFind (pred) (x.children || []) || null,
null
)
const findById = (id) => recursiveFind(x => x.id == id)
const animals = [{name: "dogs", id: 1, children: [{name: "lessie", id: 2}, {name: "bark-a-lot", id: 3}]}, {name: "cats", id: 4, children: [{name: "meows-a-lot", id: 5, children: [{ name: "meows-a-lot-in-the-morning", id: 6}]}, {name: "whisk-ass", id: 7}]}];
console .log (findById (3) (animals))
console .log (findById (4) (animals))
We start with a generic function that searches for objects nested this way by whether they match the supplied predicate function. Then we pass it the predicate x => x.id == id to create a function that takes an id and then a list of values and finds the first value with matching ids in the list, or null if none are found.
If you have absolutely no use for this recursiveFind function, you can inline it into findById like this:
const findById = (id, xs) => xs .reduce (
(r, x) => r != null ? r : x.id == id ? x : findById (id, x.children || []) || null,
null
)
findById (3, animals)
But I actually would prefer to go in the other direction, and make it still more generic, using something like this:
const recursiveFind = (pred, descend) => (xs) => xs .reduce (
(r, x) => r != null ? r : pred (x) ? x : recursiveFind (pred, descend) (descend (x) || []) || null,
null
)
const findById = (id) => recursiveFind (x => x.id == id, x => x.children)
findById (3) (animals)
This version also parameterizes how we descend into the children of a node. In this case, we simply use x => x.children, but it's easy to imagine using other properties or a more complex method.
In all of these, do note that the function processes all nodes of your nested array structure, even when we've already found a match. If we have, the first check (r != null) skips ahead quickly, but if performance is critical, you might prefer a solution with explicit short-circuiting loops such as the one from CertainPerformance.