Functional coverage tree: Difference between revisions
Content added Content deleted
m (→{{header|Haskell}}: (+ basic check for input file existence)) |
(→{{header|JavaScript}}: Added a JavaScript draft.) |
||
Line 537: | Line 537: | ||
wine_cellar | 1 | 1.0000 | 0.0000 |
wine_cellar | 1 | 1.0000 | 0.0000 |
||
cinema | 1 | 0.7500 | 0.0167 </pre> |
cinema | 1 | 0.7500 | 0.0167 </pre> |
||
=={{header|JavaScript}}== |
|||
Parsing the outline text to a tree structure, and traversing this with two computations (one bottom-up, and one top-down), before serialising the updated tree to a new outline text. |
|||
{{Trans|Haskell}} |
|||
<lang javascript>(() => { |
|||
'use strict'; |
|||
// updatedCoverageOutline :: String -> String |
|||
const updatedCoverageOutline = outlineText => { |
|||
const |
|||
delimiter = '|', |
|||
indentedLines = indentLevelsFromLines(lines(outlineText)), |
|||
columns = init(tokenizeWith(delimiter)(snd(indentedLines[0]))); |
|||
// SERIALISATION OF UPDATED PARSE TREE (TO NEW OUTLINE TEXT) |
|||
return tabulation(delimiter)( |
|||
columns.concat('SHARE OF RESIDUE\n') |
|||
) + unlines( |
|||
indentedLinesFromTree( |
|||
showCoverage(delimiter))(' ')( |
|||
// TWO TRAVERSAL COMPUTATIONS |
|||
withResidueShares(1.0)( |
|||
foldTree(weightedCoverage)( |
|||
// PARSE TREE (FROM OUTLINE TEXT) |
|||
fmapTree(compose( |
|||
partialRecord, |
|||
tokenizeWith(delimiter) |
|||
))(fst( |
|||
forestFromLineIndents( |
|||
tail(indentedLines) |
|||
) |
|||
)) |
|||
) |
|||
)) |
|||
); |
|||
}; |
|||
// TEST ----------------------------------------------- |
|||
// main :: IO () |
|||
const main = () => |
|||
console.log( |
|||
// strOutline is included as literal text |
|||
// at the foot of this code listing. |
|||
updatedCoverageOutline(strOutline) |
|||
); |
|||
// COVERAGE AND SHARES OF RESIDUE --------------------- |
|||
// withResidueShares :: Float -> Tree Dict -> Tree Dict |
|||
const withResidueShares = shareOfTotal => tree => { |
|||
const go = fraction => node => { |
|||
const |
|||
nodeRoot = node.root, |
|||
forest = node.nest, |
|||
weights = forest.map(x => x.root.weight), |
|||
weightTotal = sum(weights); |
|||
return Node( |
|||
insertDict('share')( |
|||
fraction * (1 - nodeRoot.coverage) |
|||
)(nodeRoot) |
|||
)( |
|||
zipWith(go)( |
|||
weights.map(w => fraction * (w / weightTotal)) |
|||
)(forest) |
|||
); |
|||
}; |
|||
return go(shareOfTotal)(tree); |
|||
}; |
|||
// weightedCoverage :: Dict -> Forest Dict -> Tree Dict |
|||
const weightedCoverage = x => xs => { |
|||
const |
|||
cws = map(compose( |
|||
fanArrow(x => x.coverage)(x => x.weight), |
|||
root |
|||
))(xs), |
|||
totalWeight = cws.reduce((a, tpl) => a + snd(tpl), 0); |
|||
return Node( |
|||
insertDict('coverage')( |
|||
cws.reduce((a, tpl) => { |
|||
const [c, w] = Array.from(tpl); |
|||
return a + (c * w); |
|||
}, x.coverage) / ( |
|||
0 < totalWeight ? totalWeight : 1 |
|||
) |
|||
)(x) |
|||
)(xs); |
|||
}; |
|||
// OUTLINE PARSED TO TREE ----------------------------- |
|||
// forestFromLineIndents :: [(Int, String)] -> [Tree String] |
|||
const forestFromLineIndents = tuples => { |
|||
const go = xs => |
|||
0 < xs.length ? (() => { |
|||
const [n, s] = Array.from(xs[0]); |
|||
// Lines indented under this line, |
|||
// tupled with all the rest. |
|||
const [firstTreeLines, rest] = Array.from( |
|||
span(x => n < x[0])(xs.slice(1)) |
|||
); |
|||
// This first tree, and then the rest. |
|||
return [Node(s)(go(firstTreeLines))] |
|||
.concat(go(rest)); |
|||
})() : []; |
|||
return go(tuples); |
|||
}; |
|||
// indentLevelsFromLines :: [String] -> [(Int, String)] |
|||
const indentLevelsFromLines = xs => { |
|||
const |
|||
indentTextPairs = xs.map(compose( |
|||
firstArrow(length), span(isSpace) |
|||
)), |
|||
indentUnit = minimum(indentTextPairs.flatMap(pair => { |
|||
const w = fst(pair); |
|||
return 0 < w ? [w] : []; |
|||
})); |
|||
return indentTextPairs.map( |
|||
firstArrow(flip(div)(indentUnit)) |
|||
); |
|||
}; |
|||
// partialRecord :: [String] -> Dict |
|||
const partialRecord = xs => { |
|||
const [name, weightText, coverageText] = take(3)( |
|||
xs.concat(['', '', '']) |
|||
); |
|||
return { |
|||
name: name || '?', |
|||
weight: parseFloat(weightText) || 1.0, |
|||
coverage: parseFloat(coverageText) || 0.0, |
|||
share: 0.0 |
|||
}; |
|||
}; |
|||
// tokenizeWith :: String -> String -> [String] |
|||
const tokenizeWith = delimiter => |
|||
// A sequence of trimmed tokens obtained by |
|||
// splitting s on the supplied delimiter. |
|||
s => s.split(delimiter).map(x => x.trim()); |
|||
// TREE SERIALIZED TO OUTLINE ------------------------- |
|||
// indentedLinesFromTree :: (String -> a -> String) -> |
|||
// String -> Tree a -> [String] |
|||
const indentedLinesFromTree = showRoot => |
|||
strTab => tree => { |
|||
const go = indent => |
|||
node => [showRoot(indent)(node.root)] |
|||
.concat(node.nest.flatMap(go(strTab + indent))); |
|||
return go('')(tree); |
|||
}; |
|||
// showN :: Int -> Float -> String |
|||
const showN = p => |
|||
n => justifyRight(7)(' ')(n.toFixed(p)); |
|||
// showCoverage :: String -> String -> Dict -> String |
|||
const showCoverage = delimiter => |
|||
indent => x => tabulation(delimiter)( |
|||
[indent + x.name, showN(0)(x.weight)] |
|||
.concat([x.coverage, x.share].map(showN(4))) |
|||
); |
|||
// tabulation :: String -> [String] -> String |
|||
const tabulation = delimiter => |
|||
// Up to 4 tokens drawn from the argument list, |
|||
// as a single string with fixed left-justified |
|||
// white-space widths, between delimiters. |
|||
compose( |
|||
intercalate(delimiter + ' '), |
|||
zipWith(flip(justifyLeft)(' '))([31, 9, 9, 9]) |
|||
); |
|||
// GENERIC AND REUSABLE FUNCTIONS --------------------- |
|||
// Node :: a -> [Tree a] -> Tree a |
|||
const Node = v => xs => ({ |
|||
type: 'Node', |
|||
root: v, // any type of value (consistent across tree) |
|||
nest: xs || [] |
|||
}); |
|||
// Nothing :: Maybe a |
|||
const Nothing = () => ({ |
|||
type: 'Maybe', |
|||
Nothing: true, |
|||
}); |
|||
// Right :: b -> Either a b |
|||
const Right = x => ({ |
|||
type: 'Either', |
|||
Right: x |
|||
}); |
|||
// Tuple (,) :: a -> b -> (a, b) |
|||
const Tuple = a => b => ({ |
|||
type: 'Tuple', |
|||
'0': a, |
|||
'1': b, |
|||
length: 2 |
|||
}); |
|||
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c |
|||
const compose = (...fs) => |
|||
x => fs.reduceRight((a, f) => f(a), x); |
|||
// concat :: [[a]] -> [a] |
|||
// concat :: [String] -> String |
|||
const concat = xs => |
|||
0 < xs.length ? (() => { |
|||
const unit = 'string' !== typeof xs[0] ? ( |
|||
[] |
|||
) : ''; |
|||
return unit.concat.apply(unit, xs); |
|||
})() : []; |
|||
// div :: Int -> Int -> Int |
|||
const div = x => y => Math.floor(x / y); |
|||
// either :: (a -> c) -> (b -> c) -> Either a b -> c |
|||
const either = fl => fr => e => |
|||
'Either' === e.type ? ( |
|||
undefined !== e.Left ? ( |
|||
fl(e.Left) |
|||
) : fr(e.Right) |
|||
) : undefined; |
|||
// Compose a function from a simple value to a tuple of |
|||
// the separate outputs of two different functions |
|||
// fanArrow (&&&) :: (a -> b) -> (a -> c) -> (a -> (b, c)) |
|||
const fanArrow = f => g => x => Tuple(f(x))( |
|||
g(x) |
|||
); |
|||
// Lift a simple function to one which applies to a tuple, |
|||
// transforming only the first item of the tuple |
|||
// firstArrow :: (a -> b) -> ((a, c) -> (b, c)) |
|||
const firstArrow = f => xy => Tuple(f(xy[0]))( |
|||
xy[1] |
|||
); |
|||
// flip :: (a -> b -> c) -> b -> a -> c |
|||
const flip = f => |
|||
1 < f.length ? ( |
|||
(a, b) => f(b, a) |
|||
) : (x => y => f(y)(x)); |
|||
// fmapTree :: (a -> b) -> Tree a -> Tree b |
|||
const fmapTree = f => tree => { |
|||
const go = node => Node(f(node.root))( |
|||
node.nest.map(go) |
|||
); |
|||
return go(tree); |
|||
}; |
|||
// foldTree :: (a -> [b] -> b) -> Tree a -> b |
|||
const foldTree = f => tree => { |
|||
const go = node => f(node.root)( |
|||
node.nest.map(go) |
|||
); |
|||
return go(tree); |
|||
}; |
|||
// foldl1 :: (a -> a -> a) -> [a] -> a |
|||
const foldl1 = f => xs => |
|||
1 < xs.length ? xs.slice(1) |
|||
.reduce(uncurry(f), xs[0]) : xs[0]; |
|||
// fst :: (a, b) -> a |
|||
const fst = tpl => tpl[0]; |
|||
// init :: [a] -> [a] |
|||
const init = xs => |
|||
0 < xs.length ? ( |
|||
xs.slice(0, -1) |
|||
) : undefined; |
|||
// insertDict :: String -> a -> Dict -> Dict |
|||
const insertDict = k => v => dct => |
|||
Object.assign({}, dct, { |
|||
[k]: v |
|||
}); |
|||
// intercalate :: [a] -> [[a]] -> [a] |
|||
// intercalate :: String -> [String] -> String |
|||
const intercalate = sep => |
|||
xs => xs.join(sep); |
|||
// isSpace :: Char -> Bool |
|||
const isSpace = c => /\s/.test(c); |
|||
// justifyLeft :: Int -> Char -> String -> String |
|||
const justifyLeft = n => cFiller => s => |
|||
n > s.length ? ( |
|||
s.padEnd(n, cFiller) |
|||
) : s; |
|||
// justifyRight :: Int -> Char -> String -> String |
|||
const justifyRight = n => cFiller => s => |
|||
n > s.length ? ( |
|||
s.padStart(n, cFiller) |
|||
) : s; |
|||
// length :: [a] -> Int |
|||
const length = xs => |
|||
(Array.isArray(xs) || 'string' === typeof xs) ? ( |
|||
xs.length |
|||
) : Infinity; |
|||
// lines :: String -> [String] |
|||
const lines = s => s.split(/[\r\n]/); |
|||
// map :: (a -> b) -> [a] -> [b] |
|||
const map = f => xs => |
|||
(Array.isArray(xs) ? ( |
|||
xs |
|||
) : xs.split('')).map(f); |
|||
// minimum :: Ord a => [a] -> a |
|||
const minimum = xs => |
|||
0 < xs.length ? ( |
|||
foldl1(a => x => x < a ? x : a)(xs) |
|||
) : undefined; |
|||
// readFileLR :: FilePath -> Either String IO String |
|||
const readFileLR = fp => { |
|||
const |
|||
e = $(), |
|||
ns = $.NSString.stringWithContentsOfFileEncodingError( |
|||
$(fp).stringByStandardizingPath, |
|||
$.NSUTF8StringEncoding, |
|||
e |
|||
); |
|||
return ns.isNil() ? ( |
|||
Left(ObjC.unwrap(e.localizedDescription)) |
|||
) : Right(ObjC.unwrap(ns)); |
|||
}; |
|||
// root :: Tree a -> a |
|||
const root = tree => tree.root; |
|||
// showLog :: a -> IO () |
|||
const showLog = (...args) => |
|||
console.log( |
|||
args |
|||
.map(JSON.stringify) |
|||
.join(' -> ') |
|||
); |
|||
// snd :: (a, b) -> b |
|||
const snd = tpl => tpl[1]; |
|||
// span :: (a -> Bool) -> [a] -> ([a], [a]) |
|||
const span = p => xs => { |
|||
const iLast = xs.length - 1; |
|||
return splitAt( |
|||
until(i => iLast < i || !p(xs[i]))( |
|||
succ |
|||
)(0) |
|||
)(xs); |
|||
}; |
|||
// splitAt :: Int -> [a] -> ([a], [a]) |
|||
const splitAt = n => xs => |
|||
Tuple(xs.slice(0, n))( |
|||
xs.slice(n) |
|||
); |
|||
// succ :: Enum a => a -> a |
|||
const succ = x => |
|||
1 + x; |
|||
// sum :: [Num] -> Num |
|||
const sum = xs => |
|||
xs.reduce((a, x) => a + x, 0); |
|||
// tail :: [a] -> [a] |
|||
const tail = xs => |
|||
0 < xs.length ? xs.slice(1) : []; |
|||
// take :: Int -> [a] -> [a] |
|||
// take :: Int -> String -> String |
|||
const take = n => xs => |
|||
'GeneratorFunction' !== xs.constructor.constructor.name ? ( |
|||
xs.slice(0, n) |
|||
) : [].concat.apply([], Array.from({ |
|||
length: n |
|||
}, () => { |
|||
const x = xs.next(); |
|||
return x.done ? [] : [x.value]; |
|||
})); |
|||
// uncurry :: (a -> b -> c) -> ((a, b) -> c) |
|||
const uncurry = f => |
|||
(x, y) => f(x)(y); |
|||
// unlines :: [String] -> String |
|||
const unlines = xs => xs.join('\n'); |
|||
// until :: (a -> Bool) -> (a -> a) -> a -> a |
|||
const until = p => f => x => { |
|||
let v = x; |
|||
while (!p(v)) v = f(v); |
|||
return v; |
|||
}; |
|||
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] |
|||
const zipWith = f => xs => ys => |
|||
xs.slice( |
|||
0, Math.min(xs.length, ys.length) |
|||
).map((x, i) => f(x)(ys[i])); |
|||
// SOURCE OUTLINE ----------------------------------------- |
|||
const strOutline = `NAME_HIERARCHY |WEIGHT |COVERAGE | |
|||
cleaning | | | |
|||
house1 |40 | | |
|||
bedrooms | |0.25 | |
|||
bathrooms | | | |
|||
bathroom1 | |0.5 | |
|||
bathroom2 | | | |
|||
outside_lavatory | |1 | |
|||
attic | |0.75 | |
|||
kitchen | |0.1 | |
|||
living_rooms | | | |
|||
lounge | | | |
|||
dining_room | | | |
|||
conservatory | | | |
|||
playroom | |1 | |
|||
basement | | | |
|||
garage | | | |
|||
garden | |0.8 | |
|||
house2 |60 | | |
|||
upstairs | | | |
|||
bedrooms | | | |
|||
suite_1 | | | |
|||
suite_2 | | | |
|||
bedroom_3 | | | |
|||
bedroom_4 | | | |
|||
bathroom | | | |
|||
toilet | | | |
|||
attics | |0.6 | |
|||
groundfloor | | | |
|||
kitchen | | | |
|||
living_rooms | | | |
|||
lounge | | | |
|||
dining_room | | | |
|||
conservatory | | | |
|||
playroom | | | |
|||
wet_room_&_toilet | | | |
|||
garage | | | |
|||
garden | |0.9 | |
|||
hot_tub_suite | |1 | |
|||
basement | | | |
|||
cellars | |1 | |
|||
wine_cellar | |1 | |
|||
cinema | |0.75 |`; |
|||
// MAIN --- |
|||
return main(); |
|||
})();</lang> |
|||
{{Out}} |
|||
<pre>NAME_HIERARCHY | WEIGHT | COVERAGE | SHARE OF RESIDUE |
|||
cleaning | 1 | 0.4092 | 0.5908 |
|||
house1 | 40 | 0.3313 | 0.2675 |
|||
bedrooms | 1 | 0.2500 | 0.0375 |
|||
bathrooms | 1 | 0.5000 | 0.0250 |
|||
bathroom1 | 1 | 0.5000 | 0.0083 |
|||
bathroom2 | 1 | 0.0000 | 0.0167 |
|||
outside_lavatory | 1 | 1.0000 | 0.0000 |
|||
attic | 1 | 0.7500 | 0.0125 |
|||
kitchen | 1 | 0.1000 | 0.0450 |
|||
living_rooms | 1 | 0.2500 | 0.0375 |
|||
lounge | 1 | 0.0000 | 0.0125 |
|||
dining_room | 1 | 0.0000 | 0.0125 |
|||
conservatory | 1 | 0.0000 | 0.0125 |
|||
playroom | 1 | 1.0000 | 0.0000 |
|||
basement | 1 | 0.0000 | 0.0500 |
|||
garage | 1 | 0.0000 | 0.0500 |
|||
garden | 1 | 0.8000 | 0.0100 |
|||
house2 | 60 | 0.4611 | 0.3233 |
|||
upstairs | 1 | 0.1500 | 0.1700 |
|||
bedrooms | 1 | 0.0000 | 0.0500 |
|||
suite_1 | 1 | 0.0000 | 0.0125 |
|||
suite_2 | 1 | 0.0000 | 0.0125 |
|||
bedroom_3 | 1 | 0.0000 | 0.0125 |
|||
bedroom_4 | 1 | 0.0000 | 0.0125 |
|||
bathroom | 1 | 0.0000 | 0.0500 |
|||
toilet | 1 | 0.0000 | 0.0500 |
|||
attics | 1 | 0.6000 | 0.0200 |
|||
groundfloor | 1 | 0.3167 | 0.1367 |
|||
kitchen | 1 | 0.0000 | 0.0333 |
|||
living_rooms | 1 | 0.0000 | 0.0333 |
|||
lounge | 1 | 0.0000 | 0.0083 |
|||
dining_room | 1 | 0.0000 | 0.0083 |
|||
conservatory | 1 | 0.0000 | 0.0083 |
|||
playroom | 1 | 0.0000 | 0.0083 |
|||
wet_room_&_toilet | 1 | 0.0000 | 0.0333 |
|||
garage | 1 | 0.0000 | 0.0333 |
|||
garden | 1 | 0.9000 | 0.0033 |
|||
hot_tub_suite | 1 | 1.0000 | 0.0000 |
|||
basement | 1 | 0.9167 | 0.0167 |
|||
cellars | 1 | 1.0000 | 0.0000 |
|||
wine_cellar | 1 | 1.0000 | 0.0000 |
|||
cinema | 1 | 0.7500 | 0.0167 </pre> |
|||
=={{header|J}}== |
=={{header|J}}== |