Last active
February 22, 2018 10:53
-
-
Save joshburgess/d4732fed6625b620d5b84762d0d62bbd to your computer and use it in GitHub Desktop.
Demonstrates how to traverse a tree of unknown subtrees for 'content' items of a specific type
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
// write a function to find all content items of a specific type | |
// under a specific topic, where a "topic" could be a top-level | |
// subject, a subcategory of that subject, or a particular concept | |
// falling under that subcategory | |
// possible example data structure | |
const EDUCATION_DATA = { | |
math: { | |
arithmetic: { | |
subtraction: { | |
content: [ | |
{ | |
// id properties could be used to lookup additional | |
// lesson information later on, but are not important | |
// to solving this problem | |
id: 'a2fc47f6-bb30-44f5-bf72-79a296b29ae9', | |
position: 0, | |
title: "subtraction title #1", | |
type: 'video', | |
}, | |
{ | |
id: '5e3d5f45-0fbd-4df9-82ba-4d83e382cfa1', | |
position: 1, | |
title: "subtraction title #2", | |
type: 'exercise', | |
}, | |
], | |
}, | |
addition: { | |
content: [ | |
{ | |
id: '4eb130ec-dd92-4395-b520-c4c92d4c75d0', | |
position: 0, | |
title: "addition title #1", | |
type: 'video', | |
}, | |
{ | |
id: '218ec0c4-3e61-4974-b2ef-648b2d874d72', | |
position: 2, | |
title: "addition title #3", | |
type: 'article', | |
}, | |
{ | |
id: 'd5e1dfef-75e2-4087-968d-365dfe508490', | |
position: 1, | |
title: "addition title #2", | |
type: 'video', | |
}, | |
], | |
}, | |
}, | |
calculus: { | |
derivatives: { | |
content: [ | |
{ | |
id: '77388929-8c66-4f7b-8670-00ffaffdeb5c', | |
position: 0, | |
title: "derivatives title #1", | |
type: 'video', | |
}, | |
], | |
}, | |
} | |
}, | |
science: { | |
biology: { | |
}, | |
chemistry: { | |
}, | |
}, | |
} | |
// flattens only one level deep | |
const flattenArray = array => ([]).concat(...array) | |
// composition utilities | |
const compose2 = (f, g) => x => f(g(x)) | |
const compose3 = (f, g, h) => x => f(g(h(x))) | |
const CONTENT_KEY = 'content' | |
const findValByKeySimple = key => tree => { | |
const foundSubtree = tree.hasOwnProperty(key) | |
const subtree = tree[key] | |
const nextTree = () => { | |
const values = Object.values(tree) | |
const nextTree = values | |
.reduce((acc, curr) => ({ ...acc, ...curr }), {}) | |
return nextTree | |
} | |
return foundSubtree | |
? subtree | |
: findValByKeySimple(key)(nextTree()) | |
} | |
const findValByKeyWithArrayProtections = key => tree => { | |
const foundSubtree = tree.hasOwnProperty(key) | |
const subtree = tree[key] | |
const nextTree = () => { | |
const values = Object.values(tree) | |
const isOnContent = key === CONTENT_KEY | |
&& Object.keys(values[0]).includes(CONTENT_KEY) | |
|| Array.isArray(tree) // protect against edgecase | |
return isOnContent | |
? { | |
[CONTENT_KEY]: values | |
.reduce((acc, curr) => | |
Array.isArray(tree) // protect against edgecase | |
? [ ...acc, curr ] | |
: [ ...acc, ...curr[CONTENT_KEY] ] | |
, []) | |
} | |
: values | |
.reduce((acc, curr) => { | |
return { ...acc, ...curr } | |
}, {}) | |
} | |
return foundSubtree | |
? subtree | |
: findValByKeyWithArrayProtections(key)(nextTree()) | |
} | |
const findContent = findValByKeyWithArrayProtections(CONTENT_KEY) | |
const findAllContent = tree => { | |
const subtrees = Object.values(tree) | |
const resultArray = subtrees.map(findContent) | |
return flattenArray(resultArray) | |
} | |
const getItems = root => topic => contentType => { | |
const foundSubtree = root.hasOwnProperty(topic) | |
const subtree = root[topic] | |
const findTopic = findValByKeySimple(topic) | |
const filterForContentType = array => | |
array.filter(({ type }) => type === contentType) | |
return foundSubtree && Object.keys(subtree).length | |
? compose2( | |
filterForContentType, | |
findAllContent | |
)(subtree) | |
: compose3( | |
filterForContentType, | |
findAllContent, | |
findTopic | |
)(root) | |
} | |
// helper functions (using currying to partially apply arguments) | |
const getEduItems = getItems(EDUCATION_DATA) | |
const getMathItemsOfContentType = getEduItems('math') | |
const getArithmeticItemsOfContentType = getEduItems('arithmetic') | |
const getAdditionItemsOfContentType = getEduItems('addition') | |
const getSubtractionItemsOfContentType = getEduItems('subtraction') | |
const getCalculusItemsOfContentType = getEduItems('calculus') | |
const getDerivativesItemsOfContentType = getEduItems('derivatives') | |
const getScienceItemsOfContentType = getEduItems('science') | |
const getBiologyItemsOfContentType = getEduItems('biology') | |
const getChemistryItemsOfContentType = getEduItems('chemistry') | |
const VIDEO = 'video' | |
const EXERCISE = 'exercise' | |
const ARTICLE = 'article' | |
// use these functions below for testing | |
const getMathVideos = () => getMathItemsOfContentType(VIDEO) | |
const getMathExercises = () => getMathItemsOfContentType(EXERCISE) | |
const getMathArticles = () => getMathItemsOfContentType(ARTICLE) | |
const getArithmeticVideos = () => getArithmeticItemsOfContentType(VIDEO) | |
const getArithmeticExercises = () => getArithmeticItemsOfContentType(EXERCISE) | |
const getArithmeticArticles = () => getArithmeticItemsOfContentType(ARTICLE) | |
const getCalculusVideos = () => getCalculusItemsOfContentType(VIDEO) | |
const getCalculusExercises = () => getCalculusItemsOfContentType(EXERCISE) | |
const getCalculusArticles = () => getCalculusItemsOfContentType(ARTICLE) | |
const getAdditionVideos = () => getAdditionItemsOfContentType(VIDEO) | |
const getAdditionExercises = () => getAdditionItemsOfContentType(EXERCISE) | |
const getAdditionArticles = () => getAdditionItemsOfContentType(ARTICLE) | |
const getSubtractionVideos = () => getSubtractionItemsOfContentType(VIDEO) | |
const getSubtractionExercises = () => getSubtractionItemsOfContentType(EXERCISE) | |
const getSubtractionArticles = () => getSubtractionItemsOfContentType(ARTICLE) | |
const getDerivativesVideos = () => getDerivativesItemsOfContentType(VIDEO) | |
const getDerivativesExercises = () => getDerivativesItemsOfContentType(EXERCISE) | |
const getDerivativesArticles = () => getDerivativesItemsOfContentType(ARTICLE) | |
const getScienceVideos = () => getScienceItemsOfContentType(VIDEO) | |
const getScienceExercises = () => getScienceItemsOfContentType(EXERCISE) | |
const getScienceArticles = () => getScienceItemsOfContentType(ARTICLE) | |
const getBiologyVideos = () => getBiologyItemsOfContentType(VIDEO) | |
const getBiologyExercises = () => getBiologyItemsOfContentType(EXERCISE) | |
const getBiologyArticles = () => getBiologyItemsOfContentType(ARTICLE) | |
const getChemistryVideos = () => getChemistryItemsOfContentType(VIDEO) | |
const getChemistryExercises = () => getChemistryItemsOfContentType(EXERCISE) | |
const getChemistryArticles = () => getChemistryItemsOfContentType(ARTICLE) | |
// swap the above functions out here to test different paths | |
const testItems = getMathVideos() | |
console.log('\n') | |
console.log('items', testItems) | |
console.log('\n') | |
console.log('items count', testItems.length) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment