Tengo una matriz con valores repetidos y quiero obtener la frecuencia relativa (es decir, proporciones) para cada valor repetido.
Me parece natural tratar esto como un procedimiento de 2 pasos:
Para realizar el primer paso, podemos usar R.countBy() de 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} Ahora el segundo paso sería dividir counted por la longitud de myLetters :
counted / R.length(myLetters) // obviously this doesn't work because it's not mappedEstoy un poco perdido con la forma de mapear esto correctamente. Mi solución torpe actual que no me gusta:
// 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} Aunque esto funciona, tiene que haber una forma más directa con ramda para pasar de counted a {"a": 0.4, "b": 0.2, "c": 0.4} .
Me gusta mucho el enfoque de Ori Drori, usando el hecho de que ap (f, g) //~> (x) => f (x) (g (x)) . ( chain utilizada para las funciones es la chain (f, g) //~> (x) => f (g (x)) (x) .)
Mi pensamiento inicial fue similar, usando en su lugar lift , que eleva una función que opera en valores para convertirla en una que opera en contenedores de esos valores. Cuando los contenedores son funciones, opera algo como lift (f) (g, h) //~> (x) => f (g (x), h (x)) , aunque es más genérico, como lift (f) es variable, como lo son las funciones que se le proporcionan y, por lo tanto, también la función que genera, por ejemplo, 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))
Entonces, de manera muy similar, escribí:
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>No obstante, no me queda claro que un enfoque sin puntos ofrezca algún beneficio aquí. No estoy seguro de cuál prefiero, pero este Ramda sin puntos es tan legible para mi mente:
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>Debe combinar los resultados de contar los elementos de la matriz con la longitud de la matriz.
Puede usar R.ap como el combinador S proporcionándole 2 funciones. La firma del combinador S es S = (f, g) => x => f(x)(g(x)) , donde f y g son funciones.
En tu caso:
f - Crear un mapa curry con dividir por la longitud
g - Crear un objeto de cuentas
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>Creo que la respuesta de @Scott (segunda solución) me hizo caer algunos centavos.
Si definimos dos funciones auxiliares de forma preventiva para contar y dividir:
const myCount = R.countBy(R.identity); const myDivideBy = (divisor, arr) => R.map(elem => elem / divisor); // I was missing this partEntonces podríamos hacer:
const calcFreq = (arr) => { return R.pipe(myCount, myDivideBy(arr.length))(arr) }Que es exactamente el procedimiento de 2 pasos que imaginé desde el principio: primero contar, luego dividir.
calcFreq(myLetters) // gives {"a": 0.4, "b": 0.2, "c": 0.4}Una publicación relevante: javascript: forma eficiente de dividir una matriz por un valor