RosettaCodeData/Task/Hilbert-curve/JavaScript/hilbert-curve-2.js

205 lines
5.3 KiB
JavaScript

(() => {
"use strict";
// ------------------ HILBERT CURVE ------------------
// hilbertCurve :: Dict Char [(Int, Int)] ->
// Dict Char [Char] -> Int -> Int -> SVG string
const hilbertCurve = dictVector =>
dictRule => width => compose(
svgFromPoints(width),
hilbertPoints(dictVector)(width),
hilbertTree(dictRule)
);
// hilbertTree :: Dict Char [Char] -> Int -> Tree Char
const hilbertTree = rule =>
n => {
const go = tree => {
const xs = tree.nest;
return Node(tree.root)(
0 < xs.length
? xs.map(go)
: rule[tree.root].map(
flip(Node)([])
)
);
};
const seed = Node("a")([]);
return 0 < n
? take(n)(
iterate(go)(seed)
)
.slice(-1)[0]
: seed;
};
// hilbertPoints :: Size -> Tree Char -> [(x, y)]
// hilbertPoints :: Int -> Tree Char -> [(Int, Int)]
const hilbertPoints = dict =>
w => tree => {
const go = d => (xy, t) => {
const
r = Math.floor(d / 2),
centres = dict[t.root]
.map(v => [
xy[0] + (r * v[0]),
xy[1] + (r * v[1])
]);
return 0 < t.nest.length
? zipWith(
go(r)
)(centres)(t.nest).flat()
: centres;
};
const d = Math.floor(w / 2);
return go(d)([d, d], tree);
};
// svgFromPoints :: Int -> [(Int, Int)] -> String
const svgFromPoints = w => xys => [
"<svg xmlns=\"http://www.w3.org/2000/svg\"",
`width="500" height="500" viewBox="5 5 ${w} ${w}">`,
`<path d="M${(xys).flat().join(" ")}" `,
// eslint-disable-next-line quotes
'stroke-width="2" stroke="red" fill="transparent"/>',
"</svg>"
].join("\n");
// -------------------- TEST ---------------------
const main = () =>
hilbertCurve({
"a": [
[-1, 1],
[-1, -1],
[1, -1],
[1, 1]
],
"b": [
[1, -1],
[-1, -1],
[-1, 1],
[1, 1]
],
"c": [
[1, -1],
[1, 1],
[-1, 1],
[-1, -1]
],
"d": [
[-1, 1],
[1, 1],
[1, -1],
[-1, -1]
]
})({
a: ["d", "a", "a", "b"],
b: ["c", "b", "b", "a"],
c: ["b", "c", "c", "d"],
d: ["a", "d", "d", "c"]
})(1024)(6);
// ---------------- GENERIC FUNCTIONS ----------------
// Node :: a -> [Tree a] -> Tree a
const Node = v =>
// Constructor for a Tree node which connects a
// value of some kind to a list of zero or
// more child trees.
xs => ({
type: "Node",
root: v,
nest: xs || []
});
// compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
const compose = (...fs) =>
// A function defined by the right-to-left
// composition of all the functions in fs.
fs.reduce(
(f, g) => x => f(g(x)),
x => x
);
// flip :: (a -> b -> c) -> b -> a -> c
const flip = op =>
// The binary function op with
// its arguments reversed.
1 !== op.length
? (a, b) => op(b, a)
: (a => b => op(b)(a));
// iterate :: (a -> a) -> a -> Gen [a]
const iterate = f =>
// An infinite list of repeated applications
// of f, starting with the seed value x.
function* (x) {
let v = x;
while (true) {
yield v;
v = f(v);
}
};
// 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
"GeneratorFunction" !== xs.constructor
.constructor.name ? (
xs.length
) : Infinity;
// 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)
) : Array.from({
length: n
}, () => {
const x = xs.next();
return x.done ? [] : [x.value];
}).flat();
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
const zipWith = f =>
xs => ys => {
const
n = Math.min(length(xs), length(ys)),
as = take(n)(xs),
bs = take(n)(ys);
return Array.from({
length: n
}, (_, i) => f(as[i], bs[i]));
};
// MAIN ---
return main();
})();