-
-
Save wdiasvargas/10282eee7f93d65ed5a58f365b321809 to your computer and use it in GitHub Desktop.
'use strict' | |
/**/ | |
function moda (arr) { | |
return ((arr.sort((a, b) => | |
(arr.filter(v => v === a).length) - (arr.filter(v => v === b).length)) | |
).pop()) | |
} | |
module.exports = moda | |
console.log(moda([1,2,3,4,5])) //amodal nao tem moda(nao deveria aparecer nada) | |
console.log(moda([1,2,3,4,5,5])) //modal tem moda(5) pois aparece mais vezes | |
console.log(moda([1,2,3,3,4,4,5,5])) //plurimodal tem mais de 1 moda(3,4,5)pois aparecem mais vezes |
Adicionada lógica para retornar 0 caso seja amodal:
function moda(arr) {
const contagem = arr.sort(ordenaMenorParaMaior).map(mapearOcorencias).reduce(contarOcorrencia, {})
const filtrada = filtraModa(contagem, acharMaior(mapearParaArray(contagem)))
return filtrada.length ? filtrada : 0
}
Código completo:
'use strict'
const acharMaior = (counter) => Math.max.apply(null, counter)
const ordenarMenorParaMaior = (a, b) => a - b
const mapearOcorencias = (name) => {
return {count: 1, name: name}
}
const contarOcorrencia = (a, b) => {
a[b.name] = (a[b.name] || 0) + b.count
return a
}
const mapearParaArray = (contagem) => {
const counter = []
Object.keys(contagem).filter((a) => {
counter.push(contagem[a])
})
return counter
}
const filtrarModa = (contagem, MAX) => Object.keys(contagem).filter((a) => {
return (contagem[a] === MAX && contagem[a] > 1) ? contagem[a] : null
})
function moda(arr) {
const contagem = arr.sort(ordenarMenorParaMaior).map(mapearOcorencias).reduce(contarOcorrencia, {})
const filtrada = filtrarModa(contagem, acharMaior(mapearParaArray(contagem)))
return filtrada.length ? filtrada : 0
}
module.exports = moda
console.log(moda([1,2,3,4,5])) //amodal nao tem moda(nao deveria aparecer nada)
console.log(moda([1,2,3,4,5,5])) //modal tem moda(5) pois aparece mais vezes
console.log(moda([1,2,3,3,4,4,5,5])) //plurimodal tem mais de 1 moda(3,4,5)pois aparecem mais vezes
console.log(moda([1,3,4,4,5,2,3,5]))
console.log(moda([1,3,4,4,5,2,3,5,3]))
Deixando um pouco mais fácil de ler:
function moda(arr) {
const contagem = arr
.sort(ordenarMenorParaMaior)
.map(mapearOcorencias)
.reduce(contarOcorrencia, {})
const filtrada = filtrarModa(contagem, acharMaior(mapearParaArray(contagem)))
return filtrada.length ? filtrada : 0
}
Infelizmente como uso Object.keys
e Math.max não deu para deixar mais funcional sem complicar.
Ainda creio que daria para refatorar o mapearParaArray
para uma maneira puramente funcional e imutável:
// Chamada funcional recursiva não otimizada e imutável
var objToArray = (obj, inputKeys) => {
var keys = undefined === inputKeys
? Object.keys(obj)
: inputKeys
var [head, ...tail] = keys
return 0 === keys.length
? []
: [obj[head]].concat(objToArray(obj, tail))
}
var test = { a: 10, b: 20, c: 30 }
console.log(objToArray(test, Object.keys(test)))
// Chamada funcional recursiva otimizada usando acumuladores da stack
var objToArray = (obj, inputKeys, acc = []) => {
var keys = undefined === inputKeys
? Object.keys(obj)
: inputKeys
var [head, ...tail] = keys
return 0 === keys.length
? acc
: objToArray(obj, tail, acc.concat([obj[head]]))
}
var test = { a: 10, b: 20, c: 30 }
console.log(objToArray(test, Object.keys(test)))
Daria para abstrair de maneira curta e sucinta, mas não com vanilla-js. Monads poderiam resolver grande parte dessa situação:
var objToArray = (obj, inputKeys) =>
maybe(maybe(inputKeys).fromMaybe(() => Object.keys(obj)))
.bind((keys) => {
var [head, ...tail] = keys
return 0 === keys.length
? []
: [obj[head]].concat(objToArray(obj, tail))
})
.fromJust()
Também seria legal, no caso de um código puramente funcional e ausente de efeitos colaterais, evitar o uso de =
quando não for na atribuição de uma constante:
const contarOcorrencia = (a, b) => {
a[b.name] = (a[b.name] || 0) + b.count
return a
}
No caso, talvez moldar a lógica de uma maneira que ela não mudasse o valor da referência, mas retornasse o valor processado.
Eu também evitaria o uso de {}
em funções que só possuem uma expressão e não são void
.
const mapearOcorencias = (name) => {
return {count: 1, name: name}
}
// Poderia ser:
const mapearOcorrencias = (name) => ({ count: 1, name })
@haskellcamargo BOAAA!
const count = (arr) => (arr.reduce(reducer, {}));
const reducer = (obj, value) => {
obj[value] = obj[value] ? obj[value] + 1 : 1;
return obj;
};
const findMaxes = (counting) =>
(Object.keys(counting)
.reduce((maxes, key) => {
if (maxes.max == counting[key]) return {max: maxes.max, values: maxes.values.concat(Number(key))};
if (maxes.max > counting[key]) return maxes;
if (maxes.max < counting[key]) return {max: counting[key], values: [key]};
return maxes; // just in case
},
{max: -1, values: []}));
const getValues = (length, maxes) => (maxes.values.length == length ? [] : maxes.values);
const mode = (arr) => getValues(arr.length, findMaxes(count(arr)));
console.log(mode([1,2,3,4,5])); // []
console.log(mode([1,2,3,4,5,5])); // [5]
console.log(mode([1,2,3,3,4,4,5,5])); // [3, 4, 5]
Eu achei melhor dar uma enxugada e diminuir o número de computações, por ex, eu não achava que era necessário sortear e mapear várias vezes os dados. Eu fiz usando js puro, talvez desse para condensar mais usando algumas libs.
@cyberglot não creio que há necessidade de usar uma lib. Iria ficar mais condensado essa função em si, porem o "projeto" ficaria mais inchado.
Nesse caso aqui tem alguma forma de continuar na sequencia do reduce? @haskellcamargo
const filtrada = filtrarModa(contagem, acharMaior(mapearParaArray(contagem)))
@lukaswilkeer mas ramda é uma boa opção para projetos, e vc pode carregar só o que precisa.
@suissa 😉
Expliquei parte a parte aqui http://nomadev.com.br/node-js-aprendendo-um-pouco-de-funcional-com-estatistica/
Ficou boa a explicação @lukaswilkeer @cyberglot @haskellcamargo @wdiasvargas ???
Esse reduce da @cyberglot humilhou hein! Eu não tenho nem a lógica de pensar dessa forma AINDA.
Apenas uma sugestão:
const toArray = obj => Object.keys(obj).map(key => obj[key]);
const summarize = numbers => toArray(numbers.reduce(summarizeFn, {}));
const summarizeFn = function(summary, num) {
summary[num] = summary[num]
? { num, count: summary[num].count + 1 }
: { num, count: 1 }
return summary;
};
const findMode = summary => summary.reduce(findModeFn, 0);
const findModeFn = (max, { count }) => count > max ? count : max;
const filterMode = (summary, mode) => summary.filter(({ count }) => count == mode);
const getValues = items => items.map(({ num }) => num);
const mode = function(numbers) {
const summary = summarize(numbers);
const mode = findMode(summary);
const modeItems = filterMode(summary, mode);
return modeItems.length == summary.length ? [] : getValues(modeItems);
}
console.log(mode([])); // []
console.log(mode([1, 2, 3, 4, 5])); // []
console.log(mode([1, 1, 3, 3, 4, 4, 5, 5])); // []
console.log(mode([1, 1, 3, 4, 5])); // [1]
console.log(mode([1, 3, 3, 3, 5])); // [3]
console.log(mode([1, 1, 3, 3, 4, 5])); // [1, 3]
MUITO BOM @ribaptista
Só no final o [] tem q retornar 0 por ser amodal, mas ficou MUITO SIMPLES e legível!
Muito obrigado, @suissa!
Mas eu acredito que num cenário real seja desejável a função retornar []
ao invés de zero quando o input é amodal. Assim a função é consistente quanto ao tipo de dado do retorno (sempre um array). Facilita para o código que chamar essa função lidar sempre com o mesmo tipo de retorno.
Escrevi um e ficou uma merda rs
const histogram = arr => arr.reduce( (result, item) => {
result[item] = (result[item] || 0) + 1
return result
}, {})
const pairs = obj => Object.keys(obj).map( key => [key, obj[key]] )
function mode(arr) {
let result = pairs(histogram(arr))
.sort( (a,b) => b[1] - a[1] )
.filter( (item, index, source) => item[1] === source[0][1] )
.map( item => item[0] )
return result.length === arr.length ? [] : result
}
console.log( mode([1,2,3,4,5]) )
console.log( mode([1,2,3,4,5,5]) )
console.log( mode([1,2,3,4,4,5,5]) )
Sobre "código puramente funcional", é só extrair os constructos imperativos em funções? Pois em baixo nível sempre será imperativo... Dá na mesma...
Agora diminuindo mais um pouco a função da moda:
Ou assim: