J. Hesters
J. Hesters

Reputation: 14786

Ramda: Find lowest occasions of one array in another array

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

Answers (2)

Scott Sauyet
Scott Sauyet

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

Ori Drori
Ori Drori

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

Related Questions