UNPKG

union-find-ts

Version:
127 lines (126 loc) 4.79 kB
import { repeat, tail, prepend, groupBy, values, clone } from 'ramda'; const lenGtOne = (x) => x.length > 1; /** * Group the items in a UF structure according to the components built using `link()`. * @param uf The UnionFind to group * @returns A list of lists, each one of which is a component in [uf]. */ export const toGroups = (uf) => values(groupBy(item => `${find(uf, uf.map(item))}`, tail(uf.items))); /** * * @param uf A component map * @returns The groups that are greater than size one. */ export const toConnectedGroups = (uf) => toGroups(uf).filter(lenGtOne); /** * Determines whether the item has been grouped yet (might be itself) * @param uf A Component Map * @param num Item number in the component * @returns true if the roots object has a nonzero value for this item. */ export const hasGroup = (uf, num) => uf.roots[typeof num === 'number' ? num : uf.map(num)] !== 0; /** * Build an array of size [topNumber + 1] filled with numbers ascending 0 to topNumber. * @param topNumber The last index of the array to create. * @returns An array of size [topNumber + 1] filled with numbers ascending 0 to topNumber. */ const sequenceArray = (topNumber) => [...Array(topNumber + 1).keys()]; /** * Build an array of length [topIndex + 1], full of zeroes. * @param topIndex the last index of the array. Created array will have length == [topIndex + 1] * @returns An array of length [topIndex + 1], full of zeroes. */ const zeroesArray = (topIndex) => repeat(0, topIndex + 1); export function unionFind(itemsOrSize, map, linker) { const items = (typeof itemsOrSize === 'number' ? sequenceArray(itemsOrSize) : prepend(null, itemsOrSize)); const uf1 = { items, map, ranks: zeroesArray(items.length - 1), roots: zeroesArray(items.length - 1) }; return linker ? flatten(typeof linker === 'function' ? tail(items).reduce((uf, item) => { return linkItemAll(uf, item, linker(item)); }, uf1) : linker.reduce((uf, [left, right]) => linkItem(uf, left, right), uf1)) : uf1; } /** * Copy the given list, putting [val] at index [idx] in replacement of whatever was there. * @param list * @param idx * @param val * @returns */ const replaceAt = (list, idx, val) => [...list.slice(0, idx), val, ...list.slice(idx + 1)]; /** * Gives the item a group number equal to itself, in the event that it * is undefined (has not been linked.) * @param roots roots object from a UnionFind * @param item item ordinal * @returns The value at roots[item] or else [item], if roots[item] is zero */ const rootOf = (roots, item) => (roots[item] === 0 ? item : roots[item]); /** * Find group of item in the given UnionFind. * @param param0 UnionFind object * @param item Item to find group. * @returns Group number of [item] */ export const find = ({ roots }, item) => rootOf(roots, item) === item ? item : find({ roots }, rootOf(roots, item)); export const findItem = (uf, item) => find(uf, uf.map(item)); /** * Remap the component names in the UnionFind, so that each item directly * references its component. * @param uf * @returns an equivalent UnionFind with its paths shortened. */ const flatten = (uf, { roots } = uf) => ({ ...uf, roots: roots.map(it => find(uf, it)) }); /** * When linkAll is called with an empty array, we simply define the * one item in order to mark it later. * @param uf * @param item * @returns */ function defineOne(uf, item) { return { ...uf, ranks: replaceAt(uf.ranks, item, item) }; } export function link(uf, left, right, leftComponent = find(uf, left), rightComponent = find(uf, right), { roots, ranks } = uf) { if (leftComponent === rightComponent) { return uf; } const newRoots = clone(roots); const newRanks = clone(ranks); // you want to set both components to the lower ranked of the two. If equal we will choose the right. const rankComparison = ranks[leftComponent] - ranks[rightComponent]; const newParent = rankComparison < 0 ? leftComponent : rightComponent; const setParent = (idx) => { newRoots[idx] = newParent; }; [left, right, leftComponent, rightComponent].forEach(setParent); if (rankComparison === 0) { newRanks[newParent]++; } return { ...uf, roots: newRoots, ranks: newRanks }; } export const linkItem = (uf, left, right) => link(uf, uf.map(left), uf.map(right)); export const linkItemAll = (uf, left, right) => { return linkAll(uf, uf.map(left), right.map(uf.map)); }; export const linkAll = (uf, left, right) => { return right.length === 0 ? defineOne(uf, left) : right.reduce((uf1, right1) => link(uf1, left, right1), uf); };