简体   繁体   English

如何使用 Ramda.js 计算数组元素的相对频率?

[英]How to calculate relative frequency of array elements using Ramda.js?

I have an array with repeating values, and I want to get the relative frequency (ie, proportions) for each repeating value.我有一个包含重复值的数组,我想获取每个重复值的相对频率(即比例)。

It seems natural to me to treat this as a 2-step procedure:对我来说,将其视为一个两步过程似乎很自然:

  1. count the occurrences of each value;计算每个值的出现次数; and
  2. divide that count by the length of the original array.将该计数除以原始数组的长度

To accomplish the first step we can use R.countBy() from ramda.js :为了完成第一步,我们可以使用 ramda.js 中的ramda.js R.countBy()

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 :现在第二步是除以countedmyLetters

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.我对如何正确地 map 有点迷茫。 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} .虽然这行得通,但ramda必须有一种更直接的方法来从counted{"a": 0.4, "b": 0.2, "c": 0.4}

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.您可以使用R.ap作为 S 组合子,方法是为其提供 2 个函数。 The S combinator signature is S = (f, g) => x => f(x)(g(x)) , where f and g are functions. S 组合器签名是S = (f, g) => x => f(x)(g(x)) ,其中fg是函数。

In your case:在你的情况下:

f - Create a map curried with divide by the length f - 创建一个 map curried 除以长度

g - Create an object of counts g - 创建一个 object 的计数

 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>

I very much like the approach from Ori Drori, using the fact that ap (f, g) //~> (x) => f (x) (g (x)) .我非常喜欢 Ori Drori 的方法,使用ap (f, g) //~> (x) => f (x) (g (x))的事实。 ( chain used for functions is the related chain (f, g) //~> (x) => f (g (x)) (x) .) (用于函数的chain是相关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.我最初的想法是相似的,而是使用lift ,它将对值进行操作的 function 提升为对这些值的容器进行操作的对象。 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, eg 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))当容器是函数时,它的操作类似于lift (f) (g, h) //~> (x) => f (g (x), h (x)) ,尽管它更通用,如lift (f)是可变的,提供给它的函数也是可变的,因此它生成的 function 也是可变的,例如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>

N.netheless, it's not clear to me that a point-free approach offers any benefit here. N.netheless,我不清楚无点方法在这里提供任何好处。 I'm not sure which I prefer, but this non-point-free Ramda is about as readable to my mind:我不确定我更喜欢哪个,但这个非点自由 Ramda 在我看来是可读的:

 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>

I think that @Scott's answer (2nd solution) caused some pennies to drop for me.我认为@Scott 的回答(第二个解决方案)让我损失了一些钱。

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相关帖子: javascript:按值划分数组的有效方法

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM