Reputation: 4201
I have an array with repeating values, and I want to get the relative frequency (i.e., proportions) for each repeating value.
It seems natural to me to treat this as a 2-step procedure:
To accomplish the first step we can use R.countBy()
from ramda.js
:
const R = require("ramda")
const myLetters = ["a", "a", "a", "a", "b", "b", "c", "c", "c", "c"]
const counted = R.countBy(R.identity)(myLetters)
counted // => gives {"a": 4, "b": 2, "c": 4}
Now the second step would be to divide counted
by the length of myLetters
:
counted / R.length(myLetters) // obviously this doesn't work because it's not mapped
I'm a bit lost with how to map this correctly. My current clunky solution that I dislike:
// 1. manually calculate the length and store to a variable
const nvals = R.length(myLetters)
// 2. create a custom division function
const divide_by_length = (x) => R.divide(x, nvals)
// 3. map custom function to `counted`
R.map(divide_by_length, counted) // gives {"a": 0.4, "b": 0.2, "c": 0.4}
Although this works, there's gotta be a more straightforward way with ramda
to get from counted
to {"a": 0.4, "b": 0.2, "c": 0.4}
.
Upvotes: 1
Views: 185
Reputation: 4201
I think that @Scott's answer (2nd solution) caused some pennies to drop for me.
If we define two helper functions preemptively for counting and for dividing:
const myCount = R.countBy(R.identity);
const myDivideBy = (divisor, arr) => R.map(elem => elem / divisor); // I was missing this part
Then we could do:
const calcFreq = (arr) => {
return R.pipe(myCount, myDivideBy(arr.length))(arr)
}
Which is exactly the 2-step procedure I imagined from the beginning: first count, then divide.
calcFreq(myLetters) // gives {"a": 0.4, "b": 0.2, "c": 0.4}
A relevant post: javascript: efficient way to divide an array by a value
Upvotes: 0
Reputation: 50797
I very much like the approach from Ori Drori, using the fact that ap (f, g) //~> (x) => f (x) (g (x))
. (chain
used for functions is the related chain (f, g) //~> (x) => f (g (x)) (x)
.)
My initial thought was similar, using instead lift
, which lifts a function that operates on values up to become a one that operates on containers of those values. When the containers are functions, it operates something like lift (f) (g, h) //~> (x) => f (g (x), h (x))
, although it's more generic, as lift (f)
is variadic, as are the functions supplied to it and therefore also the function it generates, e.g. lift (f) (g, h, i, j) //~> (a, b, c) => f (g (a, b, c), h (a, b, c), i (a, b, c), j (a, b, c))
So, very similarly, I wrote:
const frequencies = lift (map) (
pipe (length, flip (divide)),
countBy (identity)
)
const myLetters = ["a", "a", "a", "a", "b", "b", "c", "c", "c", "c"]
console .log (frequencies (myLetters))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {lift, map, pipe, length, flip, divide, countBy, identity} = R </script>
Nonetheless, it's not clear to me that a point-free approach offers any benefit here. I'm not sure which I prefer, but this non-point-free Ramda is about as readable to my mind:
const frequencies = (letters, total = letters.length) =>
map (n => n / total) (countBy (identity) (letters) )
const myLetters = ["a", "a", "a", "a", "b", "b", "c", "c", "c", "c"]
console .log (frequencies (myLetters))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script> const {lift, map, pipe, length, flip, divide, countBy, identity} = R </script>
Upvotes: 2
Reputation: 191976
You need to combine the results of counting the items in the array, with the length of the array.
You can use R.ap
as the S combinator by supplying it with 2 functions. The S combinator signature is S = (f, g) => x => f(x)(g(x))
, where f
and g
are functions.
In your case:
f - Create a map curried with divide by the length
g - Create an object of counts
const { ap, pipe, length, divide, __, map, countBy, identity } = R
const fn = ap(
pipe(length, divide(__), map), // curry a map by divide by length
countBy(identity), // create the counts
)
const myLetters = ["a", "a", "a", "a", "b", "b", "c", "c", "c", "c"]
const counted = fn(myLetters)
console.log(counted)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js" integrity="sha512-t0vPcE8ynwIFovsylwUuLPIbdhDj6fav2prN9fEu/VYBupsmrmk9x43Hvnt+Mgn2h5YPSJOk7PMo9zIeGedD1A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
Upvotes: 2