RosettaCodeData/Task/Dragon-curve/JavaScript/dragon-curve-4.js

349 lines
8.5 KiB
JavaScript

(() => {
'use strict';
// ------------------ DRAGON CURVE -------------------
// dragonCurve :: [[Int]] -> [[Int]]
const dragonCurve = xs => {
const
pivot = op => map(
zipWith(op)(last(xs))
),
r90 = [
[0, 1],
[-1, 0]
];
return compose(
append(xs),
pivot(add),
flip(matrixMultiply)(r90),
pivot(subtract),
reverse,
init
)(xs);
};
// ---------------------- TEST -----------------------
// main :: IO ()
const main = () =>
// SVG of 12th iteration.
console.log(
svgFromPointLists(512)(512)(
index(iterate(dragonCurve)([
[0, 0],
[0, -1]
]))(12)
)
);
// ----------------------- SVG -----------------------
// svgFromPointLists :: Int -> Int ->
// [[(Int, Int)]] -> String
const svgFromPointLists = cw => ch =>
xyss => {
const
polyline = xs =>
`<polyline points="${unwords(concat(xs).map(showJSON))}"/>`,
[x, y, mx, my] = ap([minimum, maximum])(
Array.from(unzip(concat(xyss)))
),
[wd, hd] = map(x => Math.floor(x / 10))([
mx - x, my - y
]);
return unlines([
'<?xml version="1.0" encoding="UTF-8"?>',
unwords([
'<svg',
`width="${cw}" height="${ch}"`,
`viewBox="${x - wd} ${y - hd} ${12 * wd} ${12 * hd}"`,
'xmlns="http://www.w3.org/2000/svg">'
]),
'<g stroke-width="0.2" stroke="red" fill="none">',
unlines(map(polyline)(xyss)),
'</g>',
'</svg>'
]);
};
// ---------------- GENERIC FUNCTIONS ----------------
// Just :: a -> Maybe a
const Just = x => ({
type: 'Maybe',
Nothing: false,
Just: x
});
// Nothing :: Maybe a
const Nothing = () => ({
type: 'Maybe',
Nothing: true,
});
// Tuple (,) :: a -> b -> (a, b)
const Tuple = a =>
b => ({
type: 'Tuple',
'0': a,
'1': b,
length: 2
});
// add (+) :: Num a => a -> a -> a
const add = a =>
// Curried addition.
b => a + b;
// ap (<*>) :: [(a -> b)] -> [a] -> [b]
const ap = fs =>
// The sequential application of each of a list
// of functions to each of a list of values.
xs => fs.flatMap(
f => xs.map(f)
);
// append (++) :: [a] -> [a] -> [a]
// append (++) :: String -> String -> String
const append = xs =>
// A list or string composed by
// the concatenation of two others.
ys => xs.concat(ys);
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
fs.reduce(
(f, g) => x => f(g(x)),
x => x
);
// concat :: [[a]] -> [a]
const concat = xs => [].concat(...xs);
// dotProduct :: Num a => [[a]] -> [[a]] -> [[a]]
const dotProduct = xs =>
compose(sum, zipWith(mul)(xs));
// enumFromTo :: Int -> Int -> [Int]
const enumFromTo = m =>
n => Array.from({
length: 1 + n - m
}, (_, i) => m + i);
// flip :: (a -> b -> c) -> b -> a -> c
const flip = f =>
x => y => f(y)(x);
// index (!!) :: [a] -> Int -> Maybe a
// index (!!) :: Generator (a) -> Int -> Maybe a
// index (!!) :: String -> Int -> Maybe Char
const index = xs =>
i => (
drop(i)(xs),
take(1)(xs)
);
// drop :: Int -> [a] -> [a]
// drop :: Int -> Generator [a] -> Generator [a]
// drop :: Int -> String -> String
const drop = n =>
xs => Infinity > length(xs) ? (
xs.slice(n)
) : (take(n)(xs), xs);
// init :: [a] -> [a]
const init = xs =>
// All elements of a list except the last.
0 < xs.length ? (
xs.slice(0, -1)
) : undefined;
// iterate :: (a -> a) -> a -> Gen [a]
const iterate = f =>
function* (x) {
let v = x;
while (true) {
yield(v);
v = f(v);
}
};
// last :: [a] -> a
const last = xs =>
// The last item of a list.
0 < xs.length ? xs.slice(-1)[0] : undefined;
// length :: [a] -> Int
const length = xs =>
// Returns Infinity over objects without finite
// length. This enables zip and zipWith to choose
// the shorter argument when one is non-finite,
// like cycle, repeat etc
(Array.isArray(xs) || 'string' === typeof xs) ? (
xs.length
) : Infinity;
// map :: (a -> b) -> [a] -> [b]
const map = f =>
// The list obtained by applying f
// to each element of xs.
// (The image of xs under f).
xs => xs.map(f);
// matrixMultiply :: Num a => [[a]] -> [[a]] -> [[a]]
const matrixMultiply = a =>
b => {
const cols = transpose(b);
return map(
compose(
flip(map)(cols),
dotProduct
)
)(a);
};
// minimum :: Ord a => [a] -> a
const minimum = xs =>
0 < xs.length ? (
xs.slice(1)
.reduce((a, x) => x < a ? x : a, xs[0])
) : undefined;
// maximum :: Ord a => [a] -> a
const maximum = xs =>
// The largest value in a non-empty list.
0 < xs.length ? (
xs.slice(1).reduce(
(a, x) => x > a ? (
x
) : a, xs[0]
)
) : undefined;
// mul (*) :: Num a => a -> a -> a
const mul = a =>
b => a * b;
// reverse :: [a] -> [a]
const reverse = xs =>
xs.slice(0).reverse();
// showJSON :: a -> String
const showJSON = x =>
// Indented JSON representation of the value x.
JSON.stringify(x, null, 2);
// subtract :: Num -> Num -> Num
const subtract = x =>
y => y - x;
// sum :: [Num] -> Num
const sum = xs =>
// The numeric sum of all values in xs.
xs.reduce((a, x) => a + x, 0);
// take :: Int -> [a] -> [a]
// take :: Int -> String -> String
const take = n =>
// The first n elements of a list,
// string of characters, or stream.
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];
}));
// transpose :: [[a]] -> [[a]]
const transpose = rows =>
// The columns of the input transposed
// into new rows.
// Simpler version of transpose, assuming input
// rows of even length.
0 < rows.length ? rows[0].map(
(x, i) => rows.flatMap(
x => x[i]
)
) : [];
// unlines :: [String] -> String
const unlines = xs =>
// A single string formed by the intercalation
// of a list of strings with the newline character.
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;
};
// unwords :: [String] -> String
const unwords = xs =>
// A space-separated string derived
// from a list of words.
xs.join(' ');
// unzip :: [(a,b)] -> ([a],[b])
const unzip = xys =>
xys.reduce(
(ab, xy) => Tuple(ab[0].concat(xy[0]))(
ab[1].concat(xy[1])
),
Tuple([])([])
);
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
// A list constructed by zipping with a
// custom function, rather than with the
// default tuple constructor.
xs => ys => {
const
lng = Math.min(length(xs), length(ys)),
vs = take(lng)(ys);
return take(lng)(xs)
.map((x, i) => f(x)(vs[i]));
};
// MAIN ---
return main();
})();