Created
November 11, 2020 10:54
-
-
Save rdipardo/751d4bc216fa3c85ad23a899769ac3be to your computer and use it in GitHub Desktop.
itertools.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @module itertools | |
* @description Functional transformers for object collections using ES6 features (e.g. computed property names) | |
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Computed_property_names | |
*/ | |
/** | |
* Sorts the given `list` of objects according to the given `key` | |
* @param {Array<Object>} list - An object collection | |
* @param {string} key - The property to sort by | |
* @param {boolean} [desc=false] - Option to sort in reverse order | |
* @returns {Array<Object>} | |
*/ | |
const sortBy = (list, key, desc = false) => { | |
const sorted = list | |
.map((obj, idx) => { | |
return { [key]: obj[`${key}`], idx } | |
}) | |
.sort((a, b) => { | |
let first = a[`${key}`] | |
let second = b[`${key}`] | |
if (typeof a[`${key}`] === 'string' | |
&& typeof b[`${key}`] === 'string') { | |
first = a[`${key}`].toLowerCase() | |
second = b[`${key}`].toLowerCase() | |
} | |
if (first < second) { | |
return -1 | |
} | |
if (first > second) { | |
return 1 | |
} | |
return 0 | |
}) | |
.map(s => list[s.idx]) | |
return desc ? sorted.reverse() : sorted | |
} | |
/** | |
* Collects all the values at the given `key` and totals them by applying the | |
* given aggregate function (`xform`); returns an ordered summary of totals | |
* for each `group` | |
* @param {Array<Object.<string, number>>} list - An object collection | |
* @param {string} group - A common property to group the results under | |
* @param {string} key - A common property whose (numeric) value will be totalled | |
* @param {module:itertools~Aggregate} [xform=(x, _, k) => x[`${k}`] += 1] - An | |
* aggregating function | |
* @returns {Array<Object.<string, number>>} | |
*/ | |
const frequencyOf = (list, group, key, xform = (x, _, k) => x[`${k}`] += 1) => { | |
return sortBy(list, group).reduce((freqs, item) => { | |
const included = | |
freqs.find(it => it[`${group}`] === item[`${group}`]) | |
if (included) { | |
xform(included, item, `${key}`) | |
} else { | |
freqs = freqs.concat({ | |
[group]: item[`${group}`], | |
[key]: item[`${key}`] || 1 | |
}) | |
} | |
return freqs | |
}, [{ [group]: '', [key]: 0 }]).slice(1) | |
} | |
/** | |
* Takes two objects and the name of a common property, returning the sum of | |
* their respective values | |
* @typedef Aggregate | |
* @type {function(Object.<string, number>, | |
Object.<string, number>, | |
string): number} | |
* @param {Object.<string, number>} - The first object | |
* @param {Object.<string, number>} - The second object | |
* @param {string} - The name of a property common to both | |
* @returns The sum the values of each object's common property | |
* @example | |
* const fn = (x, y, k) => x[`${k}`] += y[`${k}`] | |
* const h1 = {'height': 7} | |
* const h2 = {'height': 3} | |
* const totalHeight = fn(h1, h2, 'height') | |
* // returns 10 | |
*/ | |
module.exports = { sortBy, frequencyOf } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { sortBy, frequencyOf } = require('itertools') | |
const blogs = [ | |
{ | |
'title': 'First class tests', | |
'author': 'Robert C. Martin', | |
'url': 'http://blog.cleancoder.com/uncle-bob/2017/05/05/TestDefinitions.htmll', | |
'likes': 10 | |
}, | |
{ | |
'title': 'React patterns', | |
'author': 'Michael Chan', | |
'url': 'https://reactpatterns.com/', | |
'likes': 7 | |
}, | |
{ | |
'title': 'Go To Statement Considered Harmful', | |
'author': 'Edsger W. Dijkstra', | |
'url': 'http://www.u.arizona.edu/~rubinson/copyright_violations/Go_To_Considered_Harmful.html', | |
'likes': 5 | |
}, | |
{ | |
'title': 'TDD harms architecture', | |
'author': 'Robert C. Martin', | |
'url': 'http://blog.cleancoder.com/uncle-bob/2017/03/03/TDD-Harms-Architecture.html', | |
'likes': 0 | |
}, | |
{ | |
'title': 'Parsing CSS for EPUB', | |
'author': 'Charles Petzold', | |
'url': 'http://ftp.charlespetzold.com/blog/2011/12/Parsing-CSS-for-EPUB.html', | |
'likes': 1 | |
}, | |
{ | |
'title': 'Canonical string reduction', | |
'author': 'Edsger W. Dijkstra', | |
'url': 'http://www.cs.utexas.edu/~EWD/transcriptions/EWD08xx/EWD808.html', | |
'likes': 12 | |
}, | |
{ | |
'title': 'Type wars', | |
'author': 'Robert C. Martin', | |
'url': 'http://blog.cleancoder.com/uncle-bob/2016/05/01/TypeWars.html', | |
'likes': 2 | |
} | |
] | |
// | |
// sortBy() | |
// | |
console.log('Unsorted:') | |
console.table(blogs, ['author']) | |
// Unsorted: | |
// +---------+----------------------+ | |
// │ (index) │ author │ | |
// +---------+----------------------+ | |
// │ 0 │ 'Robert C. Martin' │ | |
// │ 1 │ 'Michael Chan' │ | |
// │ 2 │ 'Edsger W. Dijkstra' │ | |
// │ 3 │ 'Robert C. Martin' │ | |
// │ 4 │ 'Charles Petzold' │ | |
// │ 5 │ 'Edsger W. Dijkstra' │ | |
// │ 6 │ 'Robert C. Martin' │ | |
// +---------+----------------------+ | |
byAuthor = sortBy(blogs, 'author', true) | |
console.log('Alphabetically (Z-A) :') | |
console.table(byAuthor, ['author']) | |
// Alphabetically (Z-A) : | |
// +---------+----------------------+ | |
// | (index) | author | | |
// +---------+----------------------+ | |
// | 0 | 'Robert C. Martin' | | |
// | 1 | 'Robert C. Martin' | | |
// | 2 | 'Robert C. Martin' | | |
// | 3 | 'Michael Chan' | | |
// | 4 | 'Edsger W. Dijkstra' | | |
// | 5 | 'Edsger W. Dijkstra' | | |
// | 6 | 'Charles Petzold' | | |
// +---------+----------------------+ | |
// | |
// frequencyOf() | |
// | |
const byBlogsPublished = frequencyOf(blogs, 'author', 'blogs') | |
console.log('Rankings by Blogs Published:') | |
console.table(sortBy(byBlogsPublished, 'blogs')) | |
// Rankings by Blogs Published: | |
// +----------------------------------------+ | |
// | (index) | author | blogs | | |
// +----------------------------------------+ | |
// | 0 | 'Charles Petzold' | 1 | | |
// | 1 | 'Michael Chan' | 1 | | |
// | 2 | 'Edsger W. Dijkstra' | 2 | | |
// | 3 | 'Robert C. Martin' | 3 | | |
// +----------------------------------------+ | |
let max = Math.max(...byBlogsPublished.map(f => f.blogs)) | |
let winner = byBlogsPublished.find(f => f.blogs === max) | |
console.log(`${winner.author} has the most blogs at ${winner.blogs}`) | |
// Robert C. Martin has the most blogs at 3 | |
const countLikes = (x, y, k) => x[`${k}`] += y[`${k}`] | |
const byLikes = frequencyOf(blogs, 'author', 'likes', countLikes) | |
console.log('Rankings by Likes Received:') | |
console.table(sortBy(byLikes, 'likes')) | |
// Rankings by Likes Received: | |
// +----------------------------------------+ | |
// | (index) | author | likes | | |
// +----------------------------------------+ | |
// | 0 | 'Charles Petzold' | 1 | | |
// | 1 | 'Michael Chan' | 7 | | |
// | 2 | 'Robert C. Martin' | 12 | | |
// | 3 | 'Edsger W. Dijkstra' | 17 | | |
// +----------------------------------------+ | |
max = Math.max(...byLikes.map(f => f.likes)) | |
winner = byLikes.find(f => f.likes === max) | |
console.log(`${winner.author} has the most likes at ${winner.likes}`) | |
// Edsger W. Dijkstra has the most likes at 17 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment