Skip to content

Instantly share code, notes, and snippets.

@joshburgess
Last active February 22, 2018 10:53
Show Gist options
  • Save joshburgess/d4732fed6625b620d5b84762d0d62bbd to your computer and use it in GitHub Desktop.
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
// 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