RosettaCodeData/Task/Perfect-shuffle/JavaScript/perfect-shuffle.js

123 lines
3.3 KiB
JavaScript

(() => {
'use strict';
// shuffleCycleLength :: Int -> Int
const shuffleCycleLength = deckSize =>
firstCycle(shuffle, range(1, deckSize))
.all.length;
// shuffle :: [a] -> [a]
const shuffle = xs =>
concat(zip.apply(null, splitAt(div(length(xs), 2), xs)));
// firstycle :: Eq a => (a -> a) -> a -> [a]
const firstCycle = (f, x) =>
until(
m => EqArray(x, m.current),
m => {
const fx = f(m.current);
return {
current: fx,
all: m.all.concat([fx])
};
}, {
current: f(x),
all: [x]
}
);
// Two arrays equal ?
// EqArray :: [a] -> [b] -> Bool
const EqArray = (xs, ys) => {
const [nx, ny] = [xs.length, ys.length];
return nx === ny ? (
nx > 0 ? (
xs[0] === ys[0] && EqArray(xs.slice(1), ys.slice(1))
) : true
) : false;
};
// GENERIC FUNCTIONS
// zip :: [a] -> [b] -> [(a,b)]
const zip = (xs, ys) =>
xs.slice(0, Math.min(xs.length, ys.length))
.map((x, i) => [x, ys[i]]);
// concat :: [[a]] -> [a]
const concat = xs => [].concat.apply([], xs);
// splitAt :: Int -> [a] -> ([a],[a])
const splitAt = (n, xs) => [xs.slice(0, n), xs.slice(n)];
// div :: Num -> Num -> Int
const div = (x, y) => Math.floor(x / y);
// until :: (a -> Bool) -> (a -> a) -> a -> a
const until = (p, f, x) => {
const go = x => p(x) ? x : go(f(x));
return go(x);
}
// range :: Int -> Int -> [Int]
const range = (m, n) =>
Array.from({
length: Math.floor(n - m) + 1
}, (_, i) => m + i);
// length :: [a] -> Int
// length :: Text -> Int
const length = xs => xs.length;
// maximumBy :: (a -> a -> Ordering) -> [a] -> a
const maximumBy = (f, xs) =>
xs.reduce((a, x) => a === undefined ? x : (
f(x, a) > 0 ? x : a
), undefined);
// transpose :: [[a]] -> [[a]]
const transpose = xs =>
xs[0].map((_, iCol) => xs.map((row) => row[iCol]));
// show :: a -> String
const show = x => JSON.stringify(x, null, 2);
// replicateS :: Int -> String -> String
const replicateS = (n, s) => {
let v = s,
o = '';
if (n < 1) return o;
while (n > 1) {
if (n & 1) o = o.concat(v);
n >>= 1;
v = v.concat(v);
}
return o.concat(v);
};
// justifyRight :: Int -> Char -> Text -> Text
const justifyRight = (n, cFiller, strText) =>
n > strText.length ? (
(replicateS(n, cFiller) + strText)
.slice(-n)
) : strText;
// TEST
return transpose(transpose([
['Deck', 'Shuffles']
].concat(
[8, 24, 52, 100, 1020, 1024, 10000]
.map(n => [n.toString(), shuffleCycleLength(n)
.toString()
])))
.map(col => { // Right-justified number columns
const width = length(
maximumBy((a, b) => length(a) - length(b), col)
) + 2;
return col.map(x => justifyRight(width, ' ', x));
}))
.map(row => row.join(''))
.join('\n');
})();