init = { all: {}, head: null, last: null } list = { all: { 1: { id: '1', prev: null, next: '2' }, 2: { id: '2', prev: '1', next: '3' }, 3: { id: '3', prev: '2', next: null } }, head: '1', last: '3' } get = flip(prop) insertAfter = (prev, node, list) => { const next = prev ? list.all[prev].next : list.head return evolve({ all: pipe( assoc(node.id, merge(node, { prev, next })), evolve({ [ prev ]: assoc('next', node.id), [ next ]: assoc('prev', node.id) }) ), head: prev || always(node.id), last: next || always(node.id) }, list) } remove = (id, list) => { const node = list.all[id] return evolve({ all: pipe( dissoc(id), evolve({ [ node.prev ]: assoc('next', node.next), [ node.next ]: assoc('prev', node.prev) }) ), head: list.head === node.id && always(node.next), last: list.last === node.id && always(node.prev) }, list) } flattenLL = node => { if (!node) return [] return [] .concat( evolve({ prev: node.prev && prop('id'), next: node.next && prop('id') }, node) ) .concat(flattenLL(node.next)) } toArray = compose(concat([]), flattenLL, toLL) toNode = all => { const inflate = compose(toNode(all), get(all)) return when(Boolean, evolve({ prev: inflate, next: inflate })) } toLL = list => toNode(list.all)(list.head) // insertHead = (node, list) => // evolve({ // all: pipe( // assoc(node.id, merge(node, { prev: null, next: list.head })), // evolve({ // [ list.head ]: assoc('prev', node.id) // }) // ), // head: always(node.id), // last: keys(list.all).length || always(node.id) // }, list) // insertLast = (node, list) => // evolve({ // all: pipe( // assoc(node.id, merge(node, { prev: list.last, next: null })), // evolve({ // [ list.last ]: assoc('next', node.id) // }) // ), // head: keys(list.all).length || always(node.id) // last: always(node.id) // }, list)