I have 3 different test cases, each of which is passed to the function getEarliest below:
{}
{ '2022-04-29': 1 }
{ '2022-04-29': 3, '2022-04-30': 2, '2022-04-28': 3, '2022-05-02': 2 }
From each test case, I want to return the key:value with the smallest date and the biggest value. My problem is at the third test case, I'm getting '2022-04-29': 3 instead of '2022-04-28': 3
getEarliest = dates => {
return Object.keys(dates).reduce((prev, curr) => {
if (dates[curr] > prev.date) {
return {
val: dates[curr],
date: curr
};
} else {
return prev;
}
}, {
val: 0,
date: null
}); }
expected result test case 1 : { val: 0, date: null }
expected result test case 2 : { val: 1, date: '2022-04-29' }
expected result test case 3 : { val: 3, date: '2022-04-28' }
Looks like you need just sort object by two parametrs:
const test1 = {};
const test2 = { '2022-04-29': 1 };
const test3 = { '2022-04-29': 3, '2022-04-30': 2, '2022-04-28': 3, '2022-05-02': 2 };
const test4 = { '2022-04-29': 4, '2022-04-28': 4, '2022-04-30': 4, '2022-05-02': 2 };
const getEarliest = (dates) => {
const [date, val] = Object.entries(dates)
.sort(([k1, v1], [k2, v2]) => v2 - v1 || Date.parse(k1) - Date.parse(k2) )
.at(0) ?? [null, 0];
return { val, date };
};
console.log(getEarliest(test1));
console.log(getEarliest(test2));
console.log(getEarliest(test3));
console.log(getEarliest(test4));
.as-console-wrapper {max-height: 100% !important; top: 0}
And the same result with reduce:
const test1 = {};
const test2 = { '2022-04-29': 1 };
const test3 = { '2022-04-29': 3, '2022-04-30': 2, '2022-04-28': 3, '2022-05-02': 2 };
const test4 = { '2022-04-29': 4, '2022-04-28': 4, '2022-04-30': 4, '2022-05-02': 2 };
const getEarliest = (dates) => {
const [date, val] = Object.entries(dates)
.reduce((prev, curr) => (curr[1] - prev[1] || Date.parse(prev[0]) - Date.parse(curr[0])) > 0
? curr
: prev
, [null, 0]);
return { val, date };
};
console.log(getEarliest(test1));
console.log(getEarliest(test2));
console.log(getEarliest(test3));
console.log(getEarliest(test4));
.as-console-wrapper {max-height: 100%!important;top:0 }
In your code prev.date value is initially null. so reduce() will always return the initial object.
Try this code it's help you
getEarliest = (dates) => {
if (Object.keys(dates).length > 0) {
let date = Object.keys(dates).reduce((prev, curr) => {
if (prev > curr) {
return curr;
} else {
return prev;
}
});
return {
val: dates[date],
date: date
}
} else {
return {
val: 0, date: null
}
}
}
Because you have 2 sort conditions so they have to be put in a certain order. From your examples I assume that order is: "biggest value" -> "smallest date". Then your if will become like below:
getEarliest = (dates) => {
return Object.keys(dates).reduce(
(prev, curr) => {
if (dates[curr] > prev.val || (dates[curr] === prev.val && curr < prev.date)) {
return {
val: dates[curr],
date: curr,
}
} else {
return prev
}
},
{
val: 0,
date: null,
}
)
}
In the first statement the .date property was never defined so it should be undefined. dates is an array not an object so dates[curr] looks like dates['2022-04-28'] which isn't helpful. In notation form ex. array[5] an interger goes in the bracket as the index which is the 3rd parameter of .reduce()
if (dates[curr] > prev.date) {...
The reason you get "2022-04-29" is because the array is never changed, you need to acrually sort the dates with .sort() a simplier method or .reduce() too complex for a novice.
Details are commented in the example below
let A = {};
let B = {
'2022-04-29': 1
};
let C = {
'2022-04-29': 3,
'2022-04-30': 2,
'2022-04-28': 3,
'2022-05-02': 2
};
// Optional utility function
const log = data => console.log(JSON.stringify(data));
/*
Can convert from timestamp to YYYY-MM-DD and vice versa
*/
const dX = (number, string) => {
let D;
if (number) {
let date = new Date(number);
D = ` ${date.getFullYear()}-${("0" + (date.getMonth() + 1)).slice(-2)}-${("0" + date.getDate()).slice(-2)}`;
} else if (string) {
D = Date.parse(string);
} else {
return false;
}
return D;
};
// Pass tS = {...}
const getEarliest = tS => {
// if tS is empty...
if (Object.keys(tS).length === 0) { // Return premade object
return {
val: 0,
date: null
};
}
/*
Object.entries(tS) converts {k: v, k: v,...} into [[k,v], [k,v]...]
*/
/*
.map(([k, v]) exposes key/value
pairs by destructuring [k, v]
*/
/*
.sort(a, b) is referencing each
date key and converting it into
a number (ms since epoch)
*/
let table = Object.entries(tS).map(([k, v]) => [k, v]).sort((a, b) => dX(null, a[0]) - dX(null, b[0]));
let obj = {};
obj.val = table[0][1];
obj.date = table[0][0];
return obj;
};
log(getEarliest(A));
log(getEarliest(B));
log(getEarliest(C));
I have a string like this "(ReadAccountAccess || ReadContractAccess) && CreateAccountAccess"
And I have this object:
{
PermissionID: 1,
AccountID: 1,
ReadAccountAccess: true,
ReadContractAccess: true,
CreateAccountAccess: true,
}
How can I check this condition?
It is NOT recommended, but you could use with statement and evaluate the expression with that object as the scope.
function check(obj, condition) {
with(obj) {
return eval(condition)
}
}
const input={PermissionID:1,AccountID:1,ReadAccountAccess:true,ReadContractAccess:true,CreateAccountAccess:true};
console.log(
check(input, "(ReadAccountAccess || ReadContractAccess) && CreateAccountAccess"),
check(input, "ReadAccountAccess === false"),
check(input, "AccountID === 1")
)
Instead of saving the condition in a string, you could use a function which takes an object as an input and returns the condition expression. This is a much better of deferring the check.
const input={PermissionID:1,AccountID:1,ReadAccountAccess:true,ReadContractAccess:true,CreateAccountAccess:true};
const c1 = o => (o.ReadAccountAccess || o.ReadContractAccess) && o.CreateAccountAccess,
c2 = o => o.ReadAccountAccess === false,
c3 = o => o.AccountID === 1
console.log(
c1(input),
c2(input),
c3(input)
)
You can use eval() javascript function.
const obj = {
PermissionID: 1,
AccountID: 1,
ReadAccountAccess: true,
ReadContractAccess: true,
CreateAccountAccess: true,
};
const { PermissionID, AccountID, ReadAccountAccess, ReadContractAccess, CreateAccountAccess } = obj;
const opr = "(ReadAccountAccess || ReadContractAccess) && CreateAccountAccess";
console.log(eval(opr));
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);
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);