Since I don’t use functional programming at work, I tend to have an “aesthetic” style when writing JavaScript functional style. Performance has never been an issue in my small, JavaScript hobby projects so far.
To remind me that functional programming using JavaScript often produce poor performance if you don’t make performance a parameter to consider, I made two versions of the same problem: surrounding the most frequent word in a text with ‘foo’ and ‘bar’.
Both solutions could be improved, but in relative terms I find my functional solution more elegant. But the performance is quite bad. I've exluded loremIpsum, a very long string (92208 words) containing the 'lorem ipsum'-text.
const loremIpsum = require("./lorem-ipsum.js");
const countWord = (words, word) => {
const lowerCaseWord = word.toLowerCase();
if (lowerCaseWord in words) {
words[lowerCaseWord] = words[lowerCaseWord] + 1;
} else {
words[lowerCaseWord] = 1;
}
return words;
};
const isNotEmpty = (word) => word !== "";
const countWords = (string) =>
string
.split(/[ ,;!?.'"\t\n]+/)
.filter(isNotEmpty)
.reduce(countWord, {});
const compareWordLength = ([_, v1], [__, v2]) => v2 - v1;
const sortWordList = (wordList) =>
Object.entries(wordList).sort(compareWordLength);
const head = (ws) => ws[0];
const getNWords = (n) => (li) => li.slice(0, n).map(head);
const surroundWord = (before, after) => (word) => `${before}${word}${after}`;
const surroundWordInText = (before, after, str) => (word) => {
const wordRegEx = new RegExp(word, "gi");
return str.replace(wordRegEx, surroundWord(before, after));
};
const compose = (...fns) => (initial) =>
fns.reduceRight((a, b) => b(a), initial);
const result = compose(
surroundWordInText("foo", "bar", loremIpsum),
head,
getNWords(1),
sortWordList,
countWords
)(loremIpsum);
console.log(result);
function compareWordLength([_, v1], [__, v2]) {
return v1 < v2;
}
function countWords(string) {
let wordList = {};
const words = string.split(/[ ,;!?.'"\t\n]+/);
for (let word of words) {
if (word === "") continue;
word.toLowerCase();
if (word in wordList) {
wordList[word]++;
continue;
}
wordList[word] = 0;
}
const wordListSorted = Object.entries(wordList).sort(compareWordLength);
const formattedList = [];
for (let i = 0; i < wordListSorted.length; i++)
formattedList.push(wordListSorted[i][0]);
return formattedList;
}
function getNOf(n, listOfWords) {
let list = [];
for (let i = 0; i < n; i++) {
list.push(listOfWords[i]);
}
return list;
}
function surroundWordWith(word, before, after, str) {
const wordRegEx = new RegExp(word, "gi");
return str.replace(wordRegEx, (w) => `${before}${w}${after}`);
}
const wordList = countWords(loremIpsum);
const mostCommonWord = getNOf(1, wordList);
const result = surroundWordWith(mostCommonWord, "foo", "bar", loremIpsum);
I’ve not made a systematic benchmark; these results are to some extent therefore unreliable. For measurements I’ve used the Linux utility time:
Imperative version
real 0m0.096s
user 0m0.083s
sys 0m0.017s
Fp version
real 0m6.334s
user 0m6.353s
sys 0m0.081s