packages feed

salmon-ops-recipes-0.2.0.0: test/Test/PathListReference.hs

{- | 'Salmon.Actions.Query.outlinePaths' as it was before paths got a length
cap (the profiles' finding 4), kept as the reference "Test.ListedPathsSpec"
compares the current one with: at most @limit@ paths per node, every one in
full, shortest first by (rendered length, segments).

It holds nodes x depth segments on a deep graph: keep its inputs small.
Nothing outside the tests and the @graph-profiles@ benchmark uses it.

Written against the exported 'Query.Outline' functions: a node's shorthand
is the last segment of its first path, its children are 'outlineChildren',
and the walk visits the roots and children in the order the original did
(last met first), which only matters for an edge that closes a cycle.
-}
module Test.PathListReference (
    outlinePathsUncapped,
    renderedWidth,
) where

import qualified Data.List as List
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as Text

import qualified Salmon.Actions.Query as Query
import Salmon.Op.Ref (Ref)

-- | A path's rendered length as @\/a\/b@: what the listing sorts by first.
renderedWidth :: [Text] -> Int
renderedWidth p = sum (map Text.length p) + length p

outlinePathsUncapped :: Int -> Query.Outline -> Map Ref [[Text]]
outlinePathsUncapped limit o = List.foldl' place Map.empty topDown
  where
    shorthands :: Map Ref Text
    shorthands = Map.fromList [(ref, last path) | (path, ref, _) <- Query.outlineFirstPaths o, not (null path)]

    childrenRev ref = reverse (Query.outlineChildren o ref)

    rootsRev = reverse (Query.outlineRoots o)

    parents :: Map Ref [Ref]
    parents = Map.fromListWith (<>) [(c, [p]) | p <- Set.toList (Query.outlineRefs o), c <- childrenRev p]

    roots = Set.fromList rootsRev

    topDown :: [Ref]
    topDown = snd (List.foldl' visit (Set.empty, []) rootsRev)

    visit :: (Set Ref, [Ref]) -> Ref -> (Set Ref, [Ref])
    visit (seen, done) ref
        | ref `Set.member` seen = (seen, done)
        | otherwise =
            let (seen', done') = List.foldl' visit (Set.insert ref seen, done) (childrenRev ref)
             in (seen', ref : done')

    place :: Map Ref [[Text]] -> Ref -> Map Ref [[Text]]
    place acc ref = case Map.lookup ref shorthands of
        Nothing -> acc
        Just seg ->
            let own = [[seg] | ref `Set.member` roots]
                inherited =
                    [ p <> [seg]
                    | parent <- Map.findWithDefault [] ref parents
                    , p <- Map.findWithDefault [] parent acc
                    ]
             in Map.insert ref (shortest (own <> inherited)) acc

    shortest :: [[Text]] -> [[Text]]
    shortest = take limit . map snd . Set.toAscList . Set.fromList . map (\p -> (renderedWidth p, p))