Reputation: 14786
I have two arrays containing strings. The second array can only contain strings, that are in the first, but multiple times. I'm trying to find the words, with the lowest occasions in the second array. Here are some examples:
const original = ['foo', 'bar', 'baz'];
const arr1 = ['foo', 'bar']; // => ['baz'] because 'baz' is 0 times in arr1.
const arr2 = ['foo', 'foo', 'bar']; // => ['baz'] because 'baz' is 0 times in arr2.
const arr3 = ['foo', 'foo', 'bar', 'bar', 'baz']; // => ['baz'] because 'baz' in 1 time in arr3.
const arr4 = ['foo', 'bar', 'baz']; // => ['foo', 'bar', 'baz']; because they are all the lowest (1 time).
const arr5 = ['foo', 'foo', 'bar', 'bar', 'bar', 'baz', 'baz']; // => ['foo', 'baz'] because they are both only 2 times in arr5
How would you do this with Ramda? (or in general in JS)? I feel like it can be solved with R.countBy
but then I don't know how to get all the keys with the lowest values.
Upvotes: 0
Views: 59
Reputation: 50797
A plain ES6 version is not too bad:
const minOccurences = (orig, xs) =>
Object .entries (xs .reduce (
(a, x) => ({... a, [x]: (a [x] || 0) + 1}),
Object .assign (... orig .map (x => ({[x]: 0})))
)).reduce(
({vs, m}, [v, c]) => c < m ? {vs: [v], m: c} : c == m ? {vs: [...vs, v], m} : {vs, m},
{vs: [], m: Infinity}
).vs
const original = ['foo', 'bar', 'baz'];
console.log (minOccurences(original, ['foo', 'bar'])); // => ['baz'] because 'baz' is 0 times in arr1.
console.log (minOccurences(original, ['foo', 'foo', 'bar'])); // => ['baz'] because 'baz' is 0 times in arr2.
console.log (minOccurences(original, ['foo', 'foo', 'bar', 'bar', 'baz'])); // => ['baz'] because 'baz' in 1 time in arr3.
console.log (minOccurences(original, ['foo', 'bar', 'baz'])); // => ['foo', 'bar', 'baz']; because they are all the lowest (1 time).
console.log (minOccurences(original, ['foo', 'foo', 'bar', 'bar', 'bar', 'baz', 'baz'])); // => ['foo', 'baz'] because they are both only 2 times in arr5
.as-console-wrapper {min-height: 100% !important; top: 0}
We first create a base counts object, {foo: 0, bar: 0, baz: 0}
, from the original array, in the Obect.assign
call. We feed that as the initial value into the first reduce
call, folding the second array into a something like {foo: 2, bar: 3, baz: 2}
. Then our second reduce
call folds this into something like {vs: ['foo', 'baz'], m: 2}
, where m
is the minimum count and vs
the items with that count. Then we just extract the vs
from the result and we have our answer.
Upvotes: 1
Reputation: 191976
You can use R.countBy, but you'll have to create a defaults object, where each item in the originals
has the value of 0, and then merge them. Afterwards, find the minimum value, and filter the original
array:
const { chain, flip, zipObj, map, always, curry, pipe, countBy, identity, mergeRight, values } = R;
const getDefaults = chain(flip(zipObj), map(always(0)));
const countWithDefaults = curry((dflts, arr) => pipe(
countBy(identity),
mergeRight(dflts),
)(arr));
const fn = curry((orig, arr) => {
const counts = countWithDefaults(getDefaults(orig), arr);
const min = Math.min(...values(counts));
return original.filter(k => counts[k] === min);
});
const original = ['foo', 'bar', 'baz'];
const fnO = fn(original);
console.log(fnO(['foo', 'bar'])); // => ['baz'] because 'baz' is 0 times in arr1.
console.log(fnO(['foo', 'foo', 'bar'])); // => ['baz'] because 'baz' is 0 times in arr2.
console.log(fnO(['foo', 'foo', 'bar', 'bar', 'baz'])); // => ['baz'] because 'baz' in 1 time in arr3.
console.log(fnO(['foo', 'bar', 'baz'])); // => ['foo', 'bar', 'baz']; because they are all the lowest (1 time).
console.log(fnO(['foo', 'foo', 'bar', 'bar', 'bar', 'baz', 'baz'])); // => ['foo', 'baz'] because they are both only 2 times in arr5
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
Upvotes: 1