RosettaCodeData/Task/Top-rank-per-group/JavaScript/top-rank-per-group-3.js

159 lines
4.9 KiB
JavaScript

(() => {
'use strict';
// topNSalariesPerDept :: Int -> [[String]] -> [String]
const topNSalariesPerDept = (n, records) =>
foldl(
(a, k, i) => (a[toLower(k)] = x => x[i], a),
this,
head(records)
) && map(intercalate(','),
concatMap(take(n),
reverse(
groupBy(
on(same, department),
sortBy(
flip(
mappendComparing([
department,
salary
])
),
tail(records)
)
)
)
)
);
// GENERIC FUNCTIONS -----------------------------------------------------
// comparing :: (a -> b) -> (a -> a -> Ordering)
const comparing = f =>
(x, y) => {
const
a = f(x),
b = f(y);
return a < b ? -1 : (a > b ? 1 : 0);
};
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = (f, xs) =>
xs.length > 0 ? (() => {
const unit = typeof xs[0] === 'string' ? '' : [];
return unit.concat.apply(unit, xs.map(f));
})() : [];
// curry :: Function -> Function
const curry = (f, ...args) => {
const go = xs => xs.length >= f.length ? (f.apply(null, xs)) :
function () {
return go(xs.concat(Array.from(arguments)));
};
return go([].slice.call(args, 1));
};
// flip :: (a -> b -> c) -> b -> a -> c
const flip = f => (a, b) => f.apply(null, [b, a]);
// foldl :: (b -> a -> b) -> b -> [a] -> b
const foldl = (f, a, xs) => xs.reduce(f, a);
// groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
const groupBy = (f, xs) => {
const dct = xs.slice(1)
.reduce((a, x) => {
const
h = a.active.length > 0 ? a.active[0] : undefined,
blnGroup = h !== undefined && f(h, x);
return {
active: blnGroup ? a.active.concat([x]) : [x],
sofar: blnGroup ? a.sofar : a.sofar.concat([a.active])
};
}, {
active: xs.length > 0 ? [xs[0]] : [],
sofar: []
});
return dct.sofar.concat(dct.active.length > 0 ? [dct.active] : []);
};
// head :: [a] -> a
const head = xs => xs.length ? xs[0] : undefined;
// intercalate :: String -> [a] -> String
const intercalate = curry((s, xs) => xs.join(s));
// map :: (a -> b) -> [a] -> [b]
const map = (f, xs) => xs.map(f);
// mappendComparing :: [(a -> b)] -> (a -> a -> Ordering)
const mappendComparing = fs => (x, y) =>
fs.reduce((ord, f) => (ord !== 0) ? (
ord
) : (() => {
const
a = f(x),
b = f(y);
return a < b ? -1 : a > b ? 1 : 0
})(), 0);
// on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
const on = (f, g) => (a, b) => f(g(a), g(b));
// reverse :: [a] -> [a]
const reverse = xs =>
typeof xs === 'string' ? (
xs.split('')
.reverse()
.join('')
) : xs.slice(0)
.reverse();
// same :: a -> a -> Bool
const same = (a, b) => a === b
// show :: Int -> a -> Indented String
// show :: a -> String
const show = (...x) =>
JSON.stringify.apply(
null, x.length > 1 ? [x[1], null, x[0]] : x
);
// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
const sortBy = (f, xs) =>
xs.slice()
.sort(f);
// tail :: [a] -> [a]
const tail = xs => xs.length ? xs.slice(1) : undefined;
// take :: Int -> [a] -> [a]
const take = curry((n, xs) => xs.slice(0, n));
// toLower :: Text -> Text
const toLower = s => s.toLowerCase();
// TEST ------------------------------------------------------------------
const xs = [
["Employee Name", "Employee ID", "Salary", "Department"],
["Tyler Bennett", "E10297", "32000", "D101"],
["John Rappl", "E21437", "47000", "D050"],
["George Woltman", "E00127", "53500", "D101"],
["Adam Smith", "E63535", "18000", "D202"],
["Claire Buckman", "E39876", "27800", "D202"],
["David McClellan", "E04242", "41500", "D101"],
["Rich Holcomb", "E01234", "49500", "D202"],
["Nathan Adams", "E41298", "21900", "D050"],
["Richard Potter", "E43128", "15900", "D101"],
["David Motsinger", "E27002", "19250", "D202"],
["Tim Sampair", "E03033", "27000", "D101"],
["Kim Arlich", "E10001", "57000", "D190"],
["Timothy Grove", "E16398", "29900", "D190"]
];
return show(2,
topNSalariesPerDept(3, xs)
);
})();