All files / src/codec/edges map.ts

100% Statements 82/82
100% Branches 14/14
100% Functions 13/13
100% Lines 82/82

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 1211x                                             1x 378x       1x 1438x 1438x 1438x 1438x 1438x 1438x 1438x           1x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x     1x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x     1x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x 1438x   1x 1x 1438x 1438x   1438x 1060x 1060x 1060x 1060x 1060x 1060x 1060x 1060x 1060x 1060x     1x 378x 378x 378x 378x  
import {
  Array,
  flow,
  HashMap,
  HashSet,
  identity,
  Option,
  pipe,
  Tuple,
} from 'effect'
import type {
  ChildToParent,
  EdgeList,
  EdgeMap,
  ParentToChildren,
  TreeEdge,
} from './types.js'
 
/**
 * Build a `EdgeMap` index from a non-empty list of tree edges.
 * @category internal
 * @function
 */
export const indexParents = <A>(edges: EdgeList<A>): EdgeMap<A> =>
  pipe(edges, Array.reduce(emptyEdgeMap(), addEdgeMap<A>))
 
// Add a previously unknown node and its optional parent to an
// `EdgeMap` map.
const addEdgeMap = <A>(index: EdgeMap<A>, edge: TreeEdge<A>): EdgeMap<A> =>
  pipe(
    [edge, index] as EdgeAndMap<A>,
    updateRoots,
    updateChildToParent,
    updateParentToChildren,
    Tuple.getSecond,
  )
 
type EdgeAndMap<A> = [TreeEdge<A>, EdgeMap<A>]
type EdgeAndMapEndo = <A>(edgeAndMap: EdgeAndMap<A>) => EdgeAndMap<A>
 
// Update the roots map of a `EdgeMap` index with a new edge.
const updateRoots: EdgeAndMapEndo = ([[child, parent], {roots, ...rest}]) => [
  [child, parent],
  {
    ...rest,
    roots: pipe(
      roots,
      pipe(
        parent,
        Option.match({
          onNone: () => HashSet.add,
          onSome: () => HashSet.remove,
        }),
      )(child),
    ),
  },
]
 
// Update the child to parent map of a `EdgeMap` index with a new edge.
const updateChildToParent: EdgeAndMapEndo = ([
  [child, parent],
  {toParent, ...rest},
]) => [
  [child, parent],
  {
    ...rest,
    toParent: pipe(
      toParent,
      pipe(
        parent,
        Option.match({
          onNone: () => identity<ChildToParent<typeof child>>,
          onSome: parent => HashMap.set(child, parent),
        }),
      ),
    ),
  },
]
 
// Update the parent to children map of an `EdgeMap` index with a new edge.
const updateParentToChildren: EdgeAndMapEndo = ([
  [child, parent],
  {toChildren, ...rest},
]) => [
  [child, parent],
  {
    ...rest,
    toChildren: pipe(
      parent,
      Option.match({
        onNone: () => toChildren,
        onSome: addParent(toChildren, child),
      }),
    ),
  },
]
 
const addParent =
  <A>(
    toChildren: ParentToChildren<A>,
    child: A,
  ): ((a: A) => ParentToChildren<A>) =>
  parent =>
    pipe(
      toChildren,
      HashMap.modifyAt(
        parent,
        flow(
          Option.match({onNone: () => [child], onSome: Array.append(child)}),
          Option.some,
        ),
      ),
    )
 
// Create a new `EdgeMap` index.
const emptyEdgeMap = () => ({
  roots: HashSet.empty(),
  toParent: HashMap.empty(),
  toChildren: HashMap.empty(),
})