Ex:
const arr = [{
group: 1,
question: {
templateId: 100
}
}, {
group: 2,
question: {
templateId: 200
}
}, {
group: 1,
question: {
templateId: 100
}
}, {
group: 1,
question: {
templateId: 300
}
}];
Expected Result: const result = groupBy(arr, 'group', 'question.templateId');
const result = [
[{
group: 1,
question: {
templateId: 100
}
}, {
group: 1,
question: {
templateId: 100
}
}],
[{
group: 1,
question: {
templateId: 300
}
}],
[{
group: 2,
question: {
templateId: 200
}
}]
];
So far: I am able to group the result by a single property using Array.prototype.reduce().
function groupBy(arr, key) {
return [...arr.reduce((accumulator, currentValue) => {
const propVal = currentValue[key],
group = accumulator.get(propVal) || [];
group.push(currentValue);
return accumulator.set(propVal, group);
}, new Map()).values()];
}
const arr = [{
group: 1,
question: {
templateId: 100
}
}, {
group: 2,
question: {
templateId: 200
}
}, {
group: 1,
question: {
templateId: 100
}
}, {
group: 1,
question: {
templateId: 300
}
}];
const result = groupBy(arr, 'group');
console.log(result);
I would recommend to pass a callback function instead of a property name, this allows you to do the two-level-access easily:
function groupBy(arr, key) {
return Array.from(arr.reduce((accumulator, currentValue) => {
const propVal = key(currentValue),
// ^^^^ ^
group = accumulator.get(propVal) || [];
group.push(currentValue);
return accumulator.set(propVal, group);
}, new Map()).values());
}
Now you can do groupBy(arr, o => o.group) and groupBy(arr, o => o.question.templateId).
All you need to do for getting to your expected result is group by the first property and then group each result by the second property:
function concatMap(arr, fn) {
return [].concat(...arr.map(fn));
}
const result = concatMap(groupBy(arr, o => o.group), res =>
groupBy(res, o => o.question.templateId)
);
#Bergi's answer is really practical but I'll show you how building a multi-value "key" can be possible using JavaScript primitives – don't take this to mean Bergi's answer is bad in anyway; in fact, it's actually a lot better because of it's practicality. If anything, this answer exists to show you how much work is saved by using an approach like his.
I'm going to go over the code bit-by-bit and then I'll have a complete runnable demo at the end.
compound data equality
Comparing compound data in JavaScript is a little tricky, so we're gonna need to figure out a way around this first:
console.log([1,2] === [1,2]) // false
I want to cover a solution for the multi-value key because our entire answer will be based upon it - here I'm calling it a CollationKey. Our key holds some value and defines its own equality function which is used for comparing keys
const CollationKey = eq => x => ({
x,
eq: ({x: y}) => eq(x, y)
})
const myKey = CollationKey (([x1, x2], [y1, y2]) =>
x1 === y1 && x2 === y2)
const k1 = myKey([1, 2])
const k2 = myKey([1, 2])
console.log(k1.eq(k2)) // true
console.log(k2.eq(k1)) // true
const k3 = myKey([3, 4])
console.log(k1.eq(k3)) // false
wishful thinking
Now that we have a way to compare compound data, I want to make a custom reducing function that uses our multi-value key to group values. I'll call this function collateBy
// key = some function that makes our key
// reducer = some function that does our reducing
// xs = some input array
const collateBy = key => reducer => xs => {
// ...?
}
// our custom key;
// equality comparison of `group` and `question.templateId` properties
const myKey = CollationKey ((x, y) =>
x.group === y.group
&& x.question.templateId === y.question.templateId)
const result =
collateBy (myKey) // multi-value key
((group=[], x) => [...group, x]) // reducing function: (accumulator, elem)
(arr) // input array
So now that we know how we want collateBy to work, let's implement it
const collateBy = key => reducer => xs => {
return xs.reduce((acc, x) => {
const k = key(x)
return acc.set(k, reducer(acc.get(k), x))
}, Collation())
}
Collation data container
Ok, so we were being a little optimistic there too using Collation() as the starting value for the xs.reduce call. What should Collation be?
What we know:
someCollation.set accepts a CollationKey and some value, and returns a new Collation
someCollation.get accepts a CollationKey and returns some value
Well let's get to work!
const Collation = (pairs=[]) => ({
has (key) {
return pairs.some(([k, v]) => key.eq(k))
},
get (key) {
return (([k, v]=[]) => v)(
pairs.find(([k, v]) => k.eq(key))
)
},
set (key, value) {
return this.has(key)
? Collation(pairs.map(([k, v]) => k.eq(key) ? [key, value] : [k, v]))
: Collation([...pairs, [key, value]])
},
})
finishing up
So far our collateBy function returns a Collation data container which is internally implemented with an array of [key, value] pairs, but what we really want back (according to your question) is just an array of values
Let's modify collateBy in the slightest way that extracts the values – changes in bold
const collateBy = key => reducer => xs => {
return xs.reduce((acc, x) => {
let k = key(x)
return acc.set(k, reducer(acc.get(k), x))
}, Collation()).values()
}
So now we will add the values method to our Collation container
values () {
return pairs.map(([k, v]) => v)
}
runnable demo
That's everything, so let's see it all work now – I used JSON.stringify in the output so that the deeply nested objects would display all content
// data containers
const CollationKey = eq => x => ({
x,
eq: ({x: y}) => eq(x, y)
})
const Collation = (pairs=[]) => ({
has (key) {
return pairs.some(([k, v]) => key.eq(k))
},
get (key) {
return (([k, v]=[]) => v)(
pairs.find(([k, v]) => k.eq(key))
)
},
set (key, value) {
return this.has(key)
? Collation(pairs.map(([k, v]) => k.eq(key) ? [key, value] : [k, v]))
: Collation([...pairs, [key, value]])
},
values () {
return pairs.map(([k, v]) => v)
}
})
// collateBy
const collateBy = key => reducer => xs => {
return xs.reduce((acc, x) => {
const k = key(x)
return acc.set(k, reducer(acc.get(k), x))
}, Collation()).values()
}
// custom key used for your specific collation
const myKey =
CollationKey ((x, y) =>
x.group === y.group
&& x.question.templateId === y.question.templateId)
// your data
const arr = [ { group: 1, question: { templateId: 100 } }, { group: 2, question: { templateId: 200 } }, { group: 1, question: { templateId: 100 } }, { group: 1, question: { templateId: 300 } } ]
// your answer
const result =
collateBy (myKey) ((group=[], x) => [...group, x]) (arr)
console.log(result)
// [
// [
// {group:1,question:{templateId:100}},
// {group:1,question:{templateId:100}}
// ],
// [
// {group:2,question:{templateId:200}}
// ],
// [
// {group:1,question:{templateId:300}}
// ]
// ]
summary
We made a custom collation function which uses a multi-value key for grouping our collated values. This was done using nothing but JavaScript primitives and higher-order functions. We now have a way to iterate thru a data set and collate it in an arbitrary way using keys of any complexity.
If you have any questions about this, I'm happy to answer them ^_^
#Bergi's answer is great if you can hard-code the inputs.
If you want to use string inputs instead, you can use the sort() method, and walk the objects as needed.
This solution will handle any number of arguments:
function groupBy(arr) {
var arg = arguments;
return arr.sort((a, b) => {
var i, key, aval, bval;
for(i = 1 ; i < arguments.length ; i++) {
key = arguments[i].split('.');
aval = a[key[0]];
bval = b[key[0]];
key.shift();
while(key.length) { //walk the objects
aval = aval[key[0]];
bval = bval[key[0]];
key.shift();
};
if (aval < bval) return -1;
else if(aval > bval) return 1;
}
return 0;
});
}
const arr = [{
group: 1,
question: {
templateId: 100
}
}, {
group: 2,
question: {
templateId: 200
}
}, {
group: 1,
question: {
templateId: 100
}
}, {
group: 1,
question: {
templateId: 300
}
}];
const result = groupBy(arr, 'group', 'question.templateId');
console.log(result);
Related
I have an Array of objects and one object
const filterArray = [{bestTimeToVisit: 'Before 10am'}, {bestDayToVisit: Monday}]
This values are setting in a reducer and the payload will be like
{bestTimeToVisit: 'After 10am'}
or
{bestDayToVisit: Tuesday}.
So what I need is when I get a payload {bestTimeToVisit: 'After 10am'} and if bestTimeToVisit not in filterList array, then add this value to the filterList array.
And if bestTimeToVisit already in the array with different value, then replace the value of that object with same key
if(filterArray.hasOwnProperty("bestTimeToVisit")) {
filterArray["bestTimeToVisit"] = payload["bestTimeToVisit"];
} else {
filterArray.push({"bestTimeToVisit": payload["bestTimeToVisit"]});
}
I convert the object array into a regular object and then back into an object array. makes things less complicated. I'm making the assumption each object coming back only has one key/value and that order doesnt matter.
const objectArraytoObject = (arr) =>
arr.reduce((acc, item) => {
const key = [Object.keys(item)[0]];
return { ...acc, [key]: item[key] };
}, {});
const newValues = [{ someKey: 'something' }, { bestDayToVisit: 'Tuesday' }];
const filterArray = [
{ bestTimeToVisit: 'Before 10am' },
{ bestDayToVisit: 'Monday' },
];
const newValuesObj = objectArraytoObject(newValues);
const filterObj = objectArraytoObject(filterArray);
const combined = { ...filterObj, ...newValuesObj };
const combinedToArray = Object.keys(combined).map((key) => ({
[key]: combined[key],
}));
console.log(combinedToArray);
Need to iterate over the array and find objects that satisfy for modification or addition if none are found.
function checkReduced(filterrray,valueToCheck="After 10am"){
let isNotFound =true;
for(let timeItem of filterrray) {
if(timeItem.bestTimeToVisit && timeItem.bestTimeToVisit !== valueToCheck) {
timeItem.bestTimeToVisit=valueToCheck;
isNotFound=false;
break;
}
}
if(isNotFound){filterrray.push({bestTimeToVisit:valueToCheck})}
}
const filterArray = [{bestDayToVisit: "Monday"}];
checkReduced(filterArray,"After 9am");//calling the function
const updateOrAdd = (arr, newItem) => {
// get the new item key
const newItemKey = Object.keys(newItem)[0];
// get the object have the same key
const find = arr.find(item => Object.keys(item).includes(newItemKey));
if(find) { // the find object is a reference type
find[newItemKey] = newItem[newItemKey]; // update the value
} else {
arr.push(newItem); // push new item if there is no object have the same key
}
return arr;
}
// tests
updateOrAdd([{ a: 1 }], { b: 2 }) // => [{ a: 1 }, { b: 2 }]
updateOrAdd([{ a: 1 }], { a: 2 }) // => [{ a: 2 }]
This question already has an answer here:
How to merge each object within arrays by index?
(1 answer)
Closed 3 months ago.
I am filtering an array for every value the is the same as the key provided. Im certain there is a one shot reduce method someone better than me can condense this down to, but alas filter map filter map.
So I submit to an array an object that says [{k:v}, {k2:otherv}] and find all the elements that are not that and then return those object keys.
The code below returns:
[
{k: v1},
{k: v2},
{k: v3}
]
[
{k2: v4},
{k2: v5},
{k2: v6}
]
]
And obviously to map over it correctly id like it to look like
[{k:v1, k2:v4}, {k:v2,k2:v5}, {k:v3, k2:v6}]
I've tried several examples from:
How can I merge two object arrays by index in JavaScript?
and
Combine same-index objects of two arrays
but short of writing every object key possible into each of these, none of what I've tried works.
const blogkeys = cont
.filter((k) => k.type === "blogs")
.map(({ key, content }) => {
if (key.includes(".")) {
let objkey = key.substr(key.indexOf(".") + 1, key.length);
let obj = { [objkey]: content };
let arrName = key.substr(0, key.indexOf("."));
let pushedObj = { [arrName]: [{ ...obj }] };
return pushedObj;
} else {
let obj = { [key]: content };
return obj;
}
});
this creates the keys we are looking for in the parent array
const everyOtherBlog = blogkeys.map((blogkey) => {
const returned = blogs
.filter(
(f) =>
!JSON.stringify(f).includes(
JSON.stringify(blogkey).replace("{", "").replace("}", "")
)
)
.map(({ _doc }) => {
let obj = {};
Object.keys(_doc)
.filter((f) => f === Object.keys(blogkey)[0])
.map((a) => {
obj = Object.assign(obj, { [a]: _doc[a] });
return obj;
});
return obj[0];
});
return returned;
});
This returns the data set you see.
Here is what blogkeys looks like :
[0] [
[0] { title: ' stuff' },
[0] {
[0] p1: ' stuff '
[0] }
[0] ]
which is made from
{
[0] _id: '606a4049d4812928986afc10',
[0] contentId: '60443ced4e233336f8306b5b',
[0] type: 'blogs',
[0] key: 'title',
[0] content: 'stuff'
[0] },
and a blog looks something like
{
title: '',
p1:''
}
Everyone here provided alot of cool stuff that ended up not helping me because of how i was feeding the data in, when i fixed that i realized i didnt need any fancy zips just good old object.fromEntries. Ill leave this up though cause some of these are very interesting.
Any help would be great
two arrays
You can use map to implement zip and then map again to perform your tranform. This solution works for only two input arrays -
const zip = (a, b) =>
a.map((x, i) => [x, b[i]])
const foo =
[{a:1},{a:2},{a:3}]
const bar =
[{b:4},{b:5},{b:6}]
const result =
zip(foo, bar).map(o => Object.assign({}, ...o))
console.log(JSON.stringify(result))
[{"a":1,"b":4},{"a":2,"b":5},{"a":3,"b":6}]
many arrays, any size
Above, you will run into strange output if a or b is longer than the other. I think a better approach is to use generators though. It works for any number of input arrays of any size -
const iter = t =>
t?.[Symbol.iterator]()
function* zip (...its)
{ let r, g = its.map(iter)
while (true)
{ r = g.map(it => it.next())
if (r.some(v => v.done)) return
yield r.map(v => v.value)
}
}
const foo =
[{a:1},{a:2},{a:3}]
const bar =
[{b:4},{b:5},{b:6}]
const qux =
[{c:7},{c:8}]
const result =
Array.from(zip(foo, bar, qux), o => Object.assign({}, ...o))
console.log(JSON.stringify(result))
This does the zipping and transformation in a single pass, without the need map afterward -
[{"a":1,"b":4,"c":7},{"a":2,"b":5,"c":8}]
without generators
If you don't like generators but still want the flexibility offered by the solution above, we can write a simple zip2 -
const zip2 = ([a, ...nexta], [b, ...nextb]) =>
a == null || b == null
? [] // empty
: [ [a, b], ...zip2(nexta, nextb) ] // recur
And then the variadiac zip which accepts any amount of arrays of any size -
const zip = (t, ...more) =>
more.length
? zip2(t, zip(...more)).map(([a, b]) => [a, ...b]) // flatten
: t.map(a => [a]) // singleton
Now we can zip any amount of arrays -
const foo =
[{a:1},{a:2},{a:3}]
const bar =
[{b:4},{b:5},{b:6}]
const qux =
[{c:7},{c:8}]
const result =
zip(foo, bar, qux).map(o => Object.assign({}, ...o))
console.log(JSON.stringify(result))
Expand the snippet below to verify the result in your own browser -
const zip2 = ([a, ...nexta], [b, ...nextb]) =>
a == null || b == null
? []
: [ [a, b], ...zip2(nexta, nextb) ]
const zip = (t, ...more) =>
more.length
? Array.from(zip2(t, zip(...more)), ([a, b]) => [a, ...b])
: t.map(a => [a])
const foo =
[{a:1},{a:2},{a:3}]
const bar =
[{b:4},{b:5},{b:6}]
const qux =
[{c:7},{c:8}]
const result =
zip(foo, bar, qux).map(o => Object.assign({}, ...o))
console.log(JSON.stringify(result))
[{"a":1,"b":4,"c":7},{"a":2,"b":5,"c":8}]
You can try this too with map and reduce, this is just another alternative
function merge(...args) {
// finding highest length Array to not skip missing elements from other arrays
// for skipping missing elements use "acc.length < ele.length"
const maxArray = args.reduce((acc, ele) => acc.length > ele.length ? acc : ele);
//Iterating over highest length array
return maxArray.map((ele, index) =>
//merging all the instances in arrays with same index
args.reduce((acc, group) => Object.assign(acc, group[index]), {})
);
}
merge([ {k: 'v1'}, {k: 'v2'}, {k: 'v3'} ], [ {k2: 'v4'}, {k2: 'v5'}, {k2: 'v6'} ]);
// [{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k":"v3","k2":"v6"}]
merge([ {k: 'v1'}, {k: 'v2'}], [ {k2: 'v4'}, {k2: 'v5'}, {k2: 'v6'} ])
// [{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k2":"v6"}]
merge([ {k: 'v1'}, {k: 'v2'}, {k: 'v3'} ], [ {k2: 'v4'}, {k2: 'v5'}])
//[{"k":"v1","k2":"v4"},{"k":"v2","k2":"v5"},{"k":"v3"}]
Here's a fairly straightforward solution using .reduce() that will accept any number of arrays of various lengths.
const
foo = [{ a: 1 }, { a: 2 }, { a: 3 }],
bar = [{ b: 4 }, { b: 5 }, { b: 6 }],
qux = [{ c: 7 }, { c: 8 }],
zip = (...arrs) =>
arrs.reduce((a, arr) => {
arr.forEach((x, i) => Object.assign((a[i] = a[i] || {}), x));
// or using logical nullish assignment
// arr.forEach((x, i) => Object.assign((a[i] ??= {}), x));
return a;
}, []);
result = zip(foo, bar, qux);
console.log(JSON.stringify(result))
// [{ a: 1, b: 4, c: 7 }, { a: 2, b: 5, c: 8 }, { a: 3, b: 6 }]
I wanted to share what I ended up doing cause it worked well with both nested arrays and simple object arrays and is formatted for getting info straight from an await from mongo db, sadly its just a filter map tho.
blog obj is
{
title:"stuff",
p1:"stuff"
}
and the return is the zipped array.
const everyOtherBlog = Object.values(blogObj).map((val) => {
const b = blogs
.filter((f) => !JSON.stringify(f).includes(val))
.map(({ _doc }) => {
const keys = Object.keys(_doc).filter((k) =>
Object.keys(blogObj).includes(k)
);
const entryObj = Object.fromEntries(keys.map((k) => [k, _doc[k]]));
return entryObj;
});
return b[0];
});
This question already has answers here:
How to get a key in a JavaScript object by its value?
(31 answers)
Closed 2 years ago.
i have an object
const alpha = {
a: 'apple'
b: 'bubble'
}
the goal is to get the property name by using only the value, for example, I want to use the string apple to get the proberty name a in a form of a string.
The following simple implementation will log the keys for apple in the console.
const alpha = {
a: 'apple',
b: 'bubble'
}
Object.entries(alpha).map(
([key, value]) => {
if (value === 'apple'){
console.log(key);
}
}
)
I think you want to do something like this...
const alpha = {
a: 'apple',
b: 'bubble'
};
const selectKeys = (object, selector) => {
let results = [];
if (object != null) {
results = Object.entries(object)
.filter((e) => selector(e[1], e[0], e))
.map((e) => e[0]);
}
return results;
};
const keys = selectKeys(alpha, (value) => value === 'apple');
console.log(keys);
This will just select all keys where the selector expression returns true. For your case thats just the name. Notice that this returns an array, because multiple keys can be returned. To get the first key simply use keys[0] or whatever.
You could get fancier and add higher order functions to make your selectors easier to read as well.
const byValue = (value) => (v) => v === value;
const a = selectKeys(object, byValue('apple'));
const a = selectKeys(object, byValue('bubble'));
You would return a list of keys who's value matches the provided value. If you wanted the first match, just shift the value off the beginning of the result.
const alpha = {
a: 'apple',
b: 'bubble'
};
const keysForValue = (obj, value) =>
Object.entries(obj)
.filter(([, val]) => val === value)
.map(([key]) => key);
console.log('Key:', keysForValue(alpha, 'bubble').shift()); // Key: b
.as-console-wrapper { top: 0; max-height: 100% !important; }
Try the below approach,
const alpha = {
a: 'apple',
b: 'bubble',
c: 'bubble',
d: 'orange',
e: 'bubble'
}
const findKeyByValue = (obj, value) => {
const arr = [];
for (const prop in obj) {
if(obj[prop] === value){
arr.push(prop);
}
}
return arr;
};
findKeyByValue(alpha, 'bubble'); //[ 'b', 'c', 'e' ]
I am creating an object from an existing array of arrays, but when running the arrays through Object.fromEntries, Object.assign and ...Array.from I get a repetition of the last element from the array of arrays but the other element in the array iterates out as intended.
The code will show this more clearly than I can explain. I am finding it quite difficult to explain as this is quite near territory for me.
The odd thing about this problem is I have done this before, in the same file and it works. When copying the code to replicate it on another array it is not behaving as the previous rendition of the same function. Console.logging out the data at each point it is manipulated shows the data is fine, right up until the result of the ...Array.from
UPDATE: added full code to help with replication of the issue
Here is the full function/app
import React, { Component } from "react";
import data from "./data/convertedData/POSTCODES FOR DISTRICT_AVRG HOUSE PRICE - Average price.json";
class JSONCreator extends Component {
// makeJson = () => {
// data.map(i => )
// }
// {
// "year":,
// "location":,
// "price":,
// "lat-lon":,
// }
render() {
const newData = data.map(i => {
const j = i.year;
const values = Object.entries(j);
Object.fromEntries = arr =>
Object.assign(
{},
...Array.from(arr, ([a, b]) => ({
year: a,
locationAndPrice: b
}))
);
const obj = Object.fromEntries(values);
console.log('obj1', obj)
return obj;
});
const newNewData = newData.map(i => {
console.log('i.locationAndPrice', i.locationAndPrice)
const j = i.locationAndPrice;
const values = Object.entries(j);
console.log('values', values)
Object.fromEntries = arr =>
Object.assign(
{},
...Array.from(arr, ([a, b]) => ({
location: a,
price: b
}))
);
const obj = Object.fromEntries(values);
console.log('obj', obj);
return obj;
});
// const newArrayHere = newData.map(i => {
// const j = i.locationAndPrice;
// const values = Object.entries(j);
// console.log('values', {...[values]})
// // Object.fromEntries = arr =>
// // Object.assign(
// // {},
// // ...Array.from(arr, (p, l) => ({ p, l }))
// // );
// // const obj = Object.fromEntries(values)
// // return obj;
// });
// console.log(newArrayHere);
return <div>hi</div>;
}
}
export default JSONCreator;
All that i am not showing is it is done with CRA (create-react-app) and this is a component. It is not being used in a larger scale app and is being built for the purpose of data conversion/practice.
Here is a sample of the data:
{
"year": {
"Jan-95": {
"City of London": "91449",
"Barking & Dagenham": "50460",
"Barnet": "93285",
"Bexley": "64958",
"Brent": "71307",
"Bromley": "81671",
"Camden": "120933",
"Croydon": "69158",
"Ealing": "79886",
"Enfield": "72515",
"Greenwich": "62300",
"Hackney": "61297",
"Hammersmith & Fulham": "124903",
"Haringey": "76288",
"Harrow": "84770",
"Havering": "68000",
"Hillingdon": "73835",
"Hounslow": "72232",
"Islington": "92516",
"Kensington & Chelsea": "182695",
"Kingston upon Thames": "80876",
"Lambeth": "67771",
"Lewisham": "60491",
"Merton": "82071",
"Newham": "53539",
"Redbridge": "72190",
"Richmond upon Thames": "109326",
"Southwark": "67885",
"Sutton": "71537",
"Tower Hamlets": "59865",
"Waltham Forest": "61319",
"Wandsworth": "88559",
"Westminster": "133025,"
}
}
},
Here is the functions that I am trying to run.
Below is what I run to convert the above JSON to the next JSON object below
const newData = data.map(i => {
const j = i.year;
const values = Object.entries(j);
Object.fromEntries = arr =>
Object.assign(
{},
...Array.from(arr, ([a, b]) => ({
year: a,
locationAndPrice: b
}))
);
const obj = Object.fromEntries(values);
return obj;
});
This is he console.log returned from the above function, as expected
{
locationAndPrice:
{
Barking & Dagenham: "50460"
Barnet: "93285"
Bexley: "64958"
Brent: "71307"
Bromley: "81671"
Camden: "120933"
City of London: "91449"
Croydon: "69158"
Ealing: "79886"
Enfield: "72515"
Greenwich: "62300"
Hackney: "61297"
Hammersmith & Fulham: "124903"
Haringey: "76288"
Harrow: "84770"
Havering: "68000"
Hillingdon: "73835"
Hounslow: "72232"
Islington: "92516"
Kensington & Chelsea: "182695"
Kingston upon Thames: "80876"
Lambeth: "67771"
Lewisham: "60491"
Merton: "82071"
Newham: "53539"
Redbridge: "72190"
Richmond upon Thames: "109326"
Southwark: "67885"
Sutton: "71537"
Tower Hamlets: "59865"
Waltham Forest: "61319"
Wandsworth: "88559"
Westminster: "133025,"}
year:{ "Jan-95"}
}
}
const newNewData = newData.map(i => {
const j = i.locationAndPrice;
const values = Object.entries(j);
console.log('values', values)
Object.fromEntries = arr =>
Object.assign(
{},
...Array.from(arr, ([a, b]) => ({
location: a,
price: b
}))
);
const obj = Object.fromEntries(values);
return obj;
});
I want to convert the above JSON data to this form
{
"location": "City of London",
"price": "93285",
},
What I am getting currently is
{
"location": "Westminster",
"price": "406413",
},
Now the issue here is, "Westminster" is getting repeated as the location for iterations of the data. The price is iterating and uniquely.
Please ask questions so I can refine this question and get to a result.
UPDATE:
I ended up figuring it out on my own. Same data as mentioned in the upper most part of this question.
import React, { Component } from "react";
import data from "./data/convertedData/POSTCODES FOR DISTRICT_AVRG HOUSE PRICE - Average price.json";
class JSONCreator extends Component {
componentDidMount() {
this.doSomething();
}
doSomething = () => {
const newData = data.map(i => {
const j = i.year;
const doStuff = Object.entries(j).map(([key, value]) => [key, value]);
const entries = doStuff.map(a => ({
year: a[0],
locationAndPrice: a[1]
}));
return entries;
});
const newState = newData.map(i => {
const j = i[0].year;
return Object.entries(i[0].locationAndPrice).map(a => ({
year: j,
location: a[0],
price: a[1]
}));
});
console.log("newData", newState);
this.setState({
newState: newState
})
};
render() {
console.log("this.state", this.state);
return <div>{JSON.stringify(this.state)}</div>;
}
}
export default JSONCreator;
I have a Javascript array that I would like to split into two based on whether a function called on each element returns true or false. Essentially, this is an array.filter, but I'd like to also have on hand the elements that were filtered out.
Currently, my plan is to use array.forEach and call the predicate function on each element. Depending on whether this is true or false, I will push the current element onto one of the two new arrays. Is there a more elegant or otherwise better way to do this? An array.filter where the will push the element onto another array before it returns false, for instance?
With ES6 you can make use of the spread syntax with reduce:
function partition(array, isValid) {
return array.reduce(([pass, fail], elem) => {
return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
}, [[], []]);
}
const [pass, fail] = partition(myArray, (e) => e > 5);
Or on a single line:
const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);
You can use lodash.partition
var users = [
{ 'user': 'barney', 'age': 36, 'active': false },
{ 'user': 'fred', 'age': 40, 'active': true },
{ 'user': 'pebbles', 'age': 1, 'active': false }
];
_.partition(users, function(o) { return o.active; });
// → objects for [['fred'], ['barney', 'pebbles']]
// The `_.matches` iteratee shorthand.
_.partition(users, { 'age': 1, 'active': false });
// → objects for [['pebbles'], ['barney', 'fred']]
// The `_.matchesProperty` iteratee shorthand.
_.partition(users, ['active', false]);
// → objects for [['barney', 'pebbles'], ['fred']]
// The `_.property` iteratee shorthand.
_.partition(users, 'active');
// → objects for [['fred'], ['barney', 'pebbles']]
or ramda.partition
R.partition(R.contains('s'), ['sss', 'ttt', 'foo', 'bars']);
// => [ [ 'sss', 'bars' ], [ 'ttt', 'foo' ] ]
R.partition(R.contains('s'), { a: 'sss', b: 'ttt', foo: 'bars' });
// => [ { a: 'sss', foo: 'bars' }, { b: 'ttt' } ]
I came up with this little guy. It uses for each and all that like you described, but it looks clean and succinct in my opinion.
//Partition function
function partition(array, filter) {
let pass = [], fail = [];
array.forEach((e, idx, arr) => (filter(e, idx, arr) ? pass : fail).push(e));
return [pass, fail];
}
//Run it with some dummy data and filter
const [lessThan5, greaterThanEqual5] = partition([0,1,4,3,5,7,9,2,4,6,8,9,0,1,2,4,6], e => e < 5);
//Output
console.log(lessThan5);
console.log(greaterThanEqual5);
You can use reduce for it:
function partition(array, callback){
return array.reduce(function(result, element, i) {
callback(element, i, array)
? result[0].push(element)
: result[1].push(element);
return result;
}, [[],[]]
);
};
Or if using Typescript:
const partition = <T,>(
array: T[],
callback: (element: T, index: number, array: T[]) => boolean
) => {
return array.reduce(function(result, element, i) {
callback(element, i, array)
? result[0].push(element)
: result[1].push(element);
return result;
}, [[],[]]);
};
Example:
const groceries = [
{ type: "apple" },
{ type: "pear" },
{ type: "banana" }
]
const [apples, others] = partition(
groceries,
(item) => item.type === "apple",
);
// => apples: [{ type: "apple" }]
// => others: [{ type: "pear" }, { type: "banana" }]
Using ES6 syntax you also can do that using recursion (updated to avoid creating new arrays on every iteration):
function partition([current, ...tail], f, left = [], right = []) {
if(current === undefined) {
return [left, right];
}
if(f(current)) {
left.push(current);
return partition(tail, f, left, right);
}
right.push(current);
return partition(tail, f, left, right);
}
This sounds very similar to Ruby's Enumerable#partition method.
If the function can't have side-effects (i.e., it can't alter the original array), then there's no more efficient way to partition the array than iterating over each element and pushing the element to one of your two arrays.
That being said, it's arguably more "elegant" to create a method on Array to perform this function. In this example, the filter function is executed in the context of the original array (i.e., this will be the original array), and it receives the element and the index of the element as arguments (similar to jQuery's each method):
Array.prototype.partition = function (f){
var matched = [],
unmatched = [],
i = 0,
j = this.length;
for (; i < j; i++){
(f.call(this, this[i], i) ? matched : unmatched).push(this[i]);
}
return [matched, unmatched];
};
console.log([1, 2, 3, 4, 5].partition(function (n, i){
return n % 2 == 0;
}));
//=> [ [ 2, 4 ], [ 1, 3, 5 ] ]
In filter function you can push your false items into another variable outside function:
var bad = [], good = [1,2,3,4,5];
good = good.filter(function (value) { if (value === false) { bad.push(value) } else { return true});
Of course value === false need to be real comparasion ;)
But it do almost that same operation like forEach. I think you should use forEach for better code readability.
A lot of answers here use Array.prototype.reduce to build a mutable accumulator, and rightfully point out that for large arrays, this is more efficient than, say, using a spread operator to copy a new array each iteration. The downside is that it's not as pretty as a "pure" expression using the short lambda syntax.
But a way around that is to use the comma operator. In C-like languages, comma is an operator that always returns the right hand operand. You can use this to create an expression that calls a void function and returns a value.
function partition(array, predicate) {
return array.reduce((acc, item) => predicate(item)
? (acc[0].push(item), acc)
: (acc[1].push(item), acc), [[], []]);
}
If you take advantage of the fact that a boolean expression implicitly casts to a number as 0 and 1, and you can make it even more concise, although I don't think it's as readable:
function partition(array, predicate) {
return array.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);
}
Usage:
const [trues, falses] = partition(['aardvark', 'cat', 'apple'], i => i.startsWith('a'));
console.log(trues); // ['aardvark', 'apple']
console.log(falses); // ['cat']
What about this?
[1,4,3,5,3,2].reduce( (s, x) => { s[ x > 3 ].push(x); return s;} , {true: [], false:[]} )
Probably this is more efficient than the spread operator
Or a bit shorter, but uglier
[1,4,3,5,3,2].reduce( (s, x) => s[ x > 3 ].push(x)?s:s , {true: [], false:[]} )
Try this:
function filter(a, fun) {
var ret = { good: [], bad: [] };
for (var i = 0; i < a.length; i++)
if (fun(a[i])
ret.good.push(a[i]);
else
ret.bad.push(a[i]);
return ret;
}
DEMO
Easy to read one.
const partition = (arr, condition) => {
const trues = arr.filter(el => condition(el));
const falses = arr.filter(el => !condition(el));
return [trues, falses];
};
// sample usage
const nums = [1,2,3,4,5,6,7]
const [evens, odds] = partition(nums, (el) => el%2 == 0)
I ended up doing this because it's easy to understand:
const partition = (array, isValid) => {
const pass = []
const fail = []
array.forEach(element => {
if (isValid(element)) {
pass.push(element)
} else {
fail.push(element)
}
})
return [pass, fail]
}
// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element) => element > 3)
And the same method including types for typescript:
const partition = <T>(array: T[], isValid: (element: T) => boolean): [T[], T[]] => {
const pass: T[] = []
const fail: T[] = []
array.forEach(element => {
if (isValid(element)) {
pass.push(element)
} else {
fail.push(element)
}
})
return [pass, fail]
}
// usage
const [pass, fail] = partition([1, 2, 3, 4, 5], (element: number) => element > 3)
ONE-LINER Partition
const partitionBy = (arr, predicate) =>
arr.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);
DEMO
// to make it consistent to filter pass index and array as arguments
const partitionBy = (arr, predicate) =>
arr.reduce(
(acc, item, index, array) => (
acc[+!predicate(item, index, array)].push(item), acc
),
[[], []]
);
console.log(partitionBy([1, 2, 3, 4, 5], x => x % 2 === 0));
console.log(partitionBy([..."ABCD"], (x, i) => i % 2 === 0));
For Typescript (v4.5)
const partitionBy = <T>(
arr: T[],
predicate: (v: T, i: number, ar: T[]) => boolean
) =>
arr.reduce(
(acc, item, index, array) => {
acc[+!predicate(item, index, array)].push(item);
return acc;
},
[[], []] as [T[], T[]]
);
Lodash partition alternative, same as the first solution of #Yaremenko Andrii but shorter syntax
function partition(arr, callback) {
return arr.reduce(
(acc, val, i, arr) => {
acc[callback(val, i, arr) ? 0 : 1].push(val)
return acc
},
[[], []]
)
}
I know there are multiple solutions already but I took the liberty of putting together the best bits of the answers above and used extension methods on Typescript. Copy and paste and it just works:
declare global {
interface Array<T> {
partition(this: T[], predicate: (e: T) => boolean): T[][];
}
}
if(!Array.prototype.partition){
Array.prototype.partition = function<T>(this: T[], predicate: (e: T) => boolean): T[][] {
return this.reduce<T[][]>(([pass, fail], elem) => {
(predicate(elem) ? pass : fail).push(elem);
return [pass, fail];
}, [[], []]);
}
}
Usage:
const numbers = [1, 2, 3, 4, 5, 6];
const [even, odd] = numbers.partition(n => n % 2 === 0);