Skip to content

Instantly share code, notes, and snippets.

@hcliff
Created February 23, 2013 22:04
Show Gist options
  • Save hcliff/5021557 to your computer and use it in GitHub Desktop.
Save hcliff/5021557 to your computer and use it in GitHub Desktop.
Clojure methods in coffeescript for dealing with nested data
###
Walkers, used in the mixins walk, preWalk and postWalk
Note: against underscore argument convention due to
issues with _.partial
Shamelessly copied from bit.ly/X3tk8A
###
preWalk = (f, c)->
_.walk f(c), _.partial(preWalk, f), _.identity
# Apply the function to the subform
postWalk = (f, c)->
_.walk c, _.partial(postWalk, f), f
# Extend underscore
_.mixin
# Takes a property name string or
# an array of property strings
# or a function
lookupIterator: (v)->
if _.isFunction(v)
return v
if _.isArray(v)
f = (k)->@[k]
return (o)-> _.map v, f, o
return (o)->
return o[v]
# Given a object and an array of keys return nested items
# _.getIn({"one" : {"two" {"three" : "hi!"}}}, ['one', 'two'])
# -> {"three" : "hi!"}
getIn : (c, k)->
reducer = (ret, x)->
# If the reducer found no key path previously
# ret is undefined and thus will not have the key x
return ret[x] if _.isObject(ret) and _.has(ret, x)
undefined
_.reduce(k, reducer, c)
# Given an object, an array of keys and a value, set the value
# creating the key path as needed
setIn : (c, k, v)->
reducer = (ret, x)->
ret[x] = {} unless _.has(ret, x)
ret[x]
# Set up the key path by creating objects where needed
o = _.reduce(k.slice(0, -1), reducer, c)
# Assign the actual value to the last key
o[_.last(k)] = v
# apply a function f, to the value found at the keypath k in c
# any extra arguments passed onto f
updateIn : (c, k, f, fa...)->
ov = _.getIn c, k
_.setIn c, k, f(ov, fa...)
c
# Implement a nested groupBy, takes a collection c and a
# array of arguments v
groupBy : (c, v) ->
f = _.lookupIterator(v || _.identity)
grouper = (o, v)->
(if _.isUndefined(o) then (o = []) else o).push v
o
reducer = (ret, x)->
_.updateIn(ret, f(x), grouper, x)
_.reduce(c, reducer, {})
walk : (c, inner, outer)->
# NOTE: arrays also are true for isObject
if _.isArray c
return outer _.map(c, inner)
if _.isObject c
return outer _.object(_.keys(c), _.map(c, inner))
outer(c)
# Take arguments in the underscore idiom (collection then function)
postWalk : (c, f)->
postWalk(f, c)
preWalk : (c, f)->
preWalk(f, c)
collection = [
{'n' : 1, 'v' : 'potato', 'c' : 'red'},
{'n' : 1, 'v' : 'potato', 'c' : 'yellow'},
{'n' : 2, 'v' : 'tomato', 'c' : 'purple'},
{'n' : 2, 'v' : 'potato', 'c' : 'red'},
]
console.time('group')
collection = _.groupBy(collection, ['n', 'v'])
console.timeEnd('group')
# trivial example
post = (value, key, list)->
if _.isString(value)
return value.toUpperCase()
return value
console.time('walk')
collection = _.postWalk(collection, post)
console.timeEnd('walk')
#_.getIn(collection, ['1', 'potato'])
# _.setIn(collection, ['2', 'tomato'], "orange")
# _.updateIn(collection, ['2', 'tomato'], (s)-> s.toUpperCase())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment