"Count where" in a collection - lodash

Using lodash, what would be a good way to count the number of objects in a collection conditionally? Say I wanted to count the number of objects where
a < 4
in the following collection
[{a : 1}, {a : 2}, {a : 3}, {a : 4}, {a : 5}, {a : 6}]

Solution
You can use countBy:
const total = _.countBy(
array,
({ a }) => a < 4 ? 'lessThanFour' : 'greaterThanFour'
).lessThanFour
Alternative
Using sumBy:
const total = _.sumBy(
array,
({ a }) => Number(a < 4)
);
And here's the same but with lodash/fp:
const count = _.sumBy(_.flow(_.get('a'), _.lt(4), Number), objects);

Below you can find an easy way to achieve that using the filter method:
var b = _.filter(a, function(o) { if (o.a < 4) return o }).length;

You can use _.countBy:
const count = _.countBy(arr, o => o.a < 4).true

Related

Reducing to list of object from object key and value

Given a previous Ramda groupBy to
{
'2018-Q4': 2,
'2019-Q1': 5
}
How can I map this to
[
{'quarter': '2018-Q4', 'value': 2},
{'quarter': '2019-Q1', 'value': 5},
]
Convert to pairs and then zipObj with the field names:
const { pipe, toPairs, map, zipObj } = R
const fn = pipe(
toPairs,
map(zipObj(['quarter', 'value']))
)
const data = {
'2018-Q4': 2,
'2019-Q1': 5
}
const result = fn(data)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>

custom sum elements by key using lodash

I do have two objects containing keys like
var a = {bar:[1,2], foo:[7,9]}
var b = {bar:[2,2], foo:[3,1]}
I want to get the fallowing results:
var c = {bar:[3,4], foo:[10,10]}
I already have a for logic like:
for (let key in b) {
if (a[key]) {
a[key][0] += b[key][0];
a[key][1] += b[key][1];
}
else a[key] = b[key];
}
But I would like to make this logic in a lodash way. How can I Do it?
You can use create a function that takes n objects, and collects them to an array using rest parameters. Now you can spread the array into _.mergeWith() to combine the objects, and in the customizer function sum the items in the arrays using Array.map() or lodash's _.map() and _.add():
const { mergeWith, isArray, map, add } = _
const fn = (...rest) => _.mergeWith({}, ...rest, (o = [], s) =>
map(s, (n, i) => add(n, o[i]))
)
const a = {bar:[1,2], foo:[7,9]}
const b = {bar:[2,2], foo:[3,1]}
const c = {bar:[3,2], foo:[5,6]}
const d = {bar:[4,2], foo:[5,4]}
const result = fn(a, b, c, d)
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
You can also use lodash/fp to create a function that merges all values to a multidimensional array with _.mergeAllWith(), then transpose the arrays using _.zipAll(), and sums each array:
const { rest, flow, mergeAllWith, isArray, head, mapValues, zipAll, map, sum } = _
const fn = rest(flow(
mergeAllWith((o, s) => [...isArray(head(o)) ? o : [o], s]), // combine to a multidimensional array
mapValues(flow(
zipAll,
map(sum)
)),
))
const a = {bar:[1,2], foo:[7,9]}
const b = {bar:[2,2], foo:[3,1]}
const c = {bar:[3,2], foo:[5,6]}
const d = {bar:[4,2], foo:[5,4]}
const result = fn(a, b, c, d)
console.log(result)
<script src='https://cdn.jsdelivr.net/g/lodash#4(lodash.min.js+lodash.fp.min.js)'></script>
You can accomplish this using plain JavaScript with Object.entries, concat and reduce:
const a = { bar: [1,2], foo: [7,9] };
const b = { bar: [2,2], foo: [3,1] };
const entries = Object.entries(a).concat(Object.entries(b));
const result = entries.reduce((accum, [key, val]) => {
accum[key] = accum[key] ? accum[key].map((x, i) => x + val[i]) : val;
return accum;
}, { });
console.log(result);

how to iterate through two arrays simultaneously with lodash

I have
A = [1,2,3,4,5,6,7]
B = [3,5,9]
I want to get an array C containing the last elements < than elements in B, like
C = [2,4,7], by using Lodash
I tried
C=_.map(A, a = (v) -> v == _.findLast(A, b = (v) -> v < _.forEach(B, c = (v) -> v==v)))
which does not work. Above I used coffeescript instead of javascript, but please reply with either, I want a solution using lodash, without explicit looping through B elements, thank you.
The forEach is redundant, and just returns the original collection:
const A = [1,2,3,4,5,6,7]
const B = [3,5,9]
// C = [2,4,7], by using Lodash
const result = _.map(B, n => _.findLast(A, m => m < n));
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
I know you said you want a lodash soultion, but just in case you were curious here is a way to get your desired array using vanilla ES6:
const A = [1, 2, 3, 4, 5, 6, 7];
const B = [3, 5, 9];
const C = findEls(A, B);
function findEls(A, B) {
const copy = [...A].reverse(); //Copy since reverse is an in-place operation
return B.map(b => {
return copy.find(a => a < b);
});
}
console.log(C);

How group and count elements by lodash

has data
items = {
0: {id:1,name:'foo'},
1: {id:2,name:'bar'},
2: {id:1,name:'foo'}
};
I wont get counted elements like this
result = {
0: {id:1,name:'foo', count:2},
1: {id:2,name:'bar', count:1}
};
lodash has function _.countBy(items, 'name') it's got {'foo': 2, 'bar':1}, i need id too.
If pure JS approach is acceptable, you can try something like this:
Logiic:
Loop over array and copy the object and add a property count and set it to 0.
Now on every iteration update this count variable.
Using above 2 steps, create a hashMap.
Now loop over hashMap again and convert it back to array.
var items = [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 1,
name: 'foo'
}
];
var temp = items.reduce(function(p,c){
var defaultValue = {
name: c.name,
id: c.id,
count: 0
};
p[c.name] = p[c.name] || defaultValue
p[c.name].count++;
return p;
}, {});
var result = [];
for( var k in temp ){
result.push(temp[k]);
}
console.log(result)

Using Linq to group, order and concatenate strings

Suppose I have the following data
Key ID Data
A 1 Hello
A 2 World
B 2 Bar
B 1 Foo
I am looking to produce the result
A HelloWorld
B FooBar
I am struggling to get the syntax quite right - I was trying to use Aggregate, but I wasn't sure if I could (or should) use SelectMany
I'd be grateful of any help.
Dim data = result.Rows.
GroupBy(Function(r) r.Key).
Select(Function(g) g.OrderBy(Function(s) s.ID)).
Aggregate(New StringBuilder, Function(cur, nxt)
cur.Append(nxt.First.Data))
Thanks
Simon
I think this (C#) should work:
var data = from r in result.Rows
group r by r.Item("Key").ToString() into g
select new {
g.Key,
Joined = string.Join("", g.OrderBy(s => s.Item("ID"))
.Select(s => s.Item("Data")))
};
Dim result = result.Rows.GroupBy(Function(r) r.Key).Select(Function(g) New With { _
g.Key, _
String.Join("", g.OrderBy(Function(r) r.ID)) _
})
Here's an alternative implementation:
var source = new Item[]
{
new Item { Key = "A", ID = 1, Data = "Hello" },
new Item { Key = "A", ID = 2, Data = "World" },
new Item { Key = "B", ID = 2, Data = "Bar" },
new Item { Key = "B", ID = 1, Data = "Foo" }
};
var results = source
.GroupBy(item => item.Key)
.Select(group => group
.OrderBy(item => item.ID)
.Aggregate(new Item(), (result, item) =>
{
result.Key = item.Key;
result.Data += item.Data;
return result;
}));
You don't want to Aggregate the groups. You want to aggregage the elements of each group unto itself.
If you want the query to do it, then
Dim data = result.Rows
.GroupBy( )
.Select(Function(g) g
.OrderBy( )
.Aggregate( )
)
If that anonymous function starts getting too hairy to write, just make a method that accepts an IGrouping<int, Row> and turns it into what you want. Then call it like:
Dim data = result.Rows
.GroupBy( )
.Select( myMethod )