Jump to content

Functional coverage tree: Difference between revisions

→‎{{header|JavaScript}}: Added a JavaScript draft.
m (→‎{{header|Haskell}}: (+ basic check for input file existence))
(→‎{{header|JavaScript}}: Added a JavaScript draft.)
Line 537:
wine_cellar | 1 | 1.0000 | 0.0000
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}}==
9,655

edits

Cookies help us deliver our services. By using our services, you agree to our use of cookies.