The code
The task is a real computation: find the number in a range whose Collatz chain is the longest. Every number in the range has to be tried, so the work grows with the range.
export interface Chain {
/** The number the chain starts from. */
start: number;
/** How many steps it takes that number to reach 1. */
steps: number;
}
/**
* Collatz: halve an even number, turn an odd one into 3n + 1, repeat until 1.
* Finds the number in `[from, to)` that takes the most steps. Every number has to be tried.
* `onProgress` receives the percentage searched, about once per percent.
*/
export function longestChain(from: number, to: number, onProgress?: (percentage: number) => void): Chain {
const longest: Chain = { start: Math.max(from, 1), steps: 0 };
const every = Math.max(1, Math.floor((to - from) / 100));
let nextReport = from + every;
for (let candidate = longest.start; candidate < to; candidate++) {
if (candidate === nextReport) {
onProgress?.(((candidate - from) / (to - from)) * 100);
nextReport += every;
}
let value = candidate;
let steps = 0;
while (value !== 1) {
value = value % 2 === 0 ? value / 2 : 3 * value + 1;
steps += 1;
}
if (steps > longest.steps) {
longest.start = candidate;
longest.steps = steps;
}
}
return longest;
}
/** Splits `[0, limit)` in equal parts, one per task. */
export const segments = (limit: number, parts: number): [from: number, to: number][] =>
Array.from({ length: parts }, (_, part) => [Math.floor((limit * part) / parts), Math.floor((limit * (part + 1)) / parts)]);The worker searches one segment and returns the answer with its own name. One worker can only search one segment at a time.
import { defineWorker } from 'easy-web-worker/defineWorker';
import { longestChain } from './collatz';
const worker = defineWorker(({ onMessage }) => ({
/** Searches one segment, reports how far it is, and says which worker did it. */
longestChain: onMessage(([from, to]: [from: number, to: number], message) => ({
...longestChain(from, to, (percentage) => message.reportProgress(percentage, { worker: self.name })),
worker: self.name,
})),
}));
export type CollatzWorker = typeof worker;The pool is the maxWorkers option. warmUpWorkers creates every Worker up front, so the first batch does not
pay for starting them.
import { createWorker } from 'easy-web-worker/createWorker';
import workerUrl from './collatz.worker?worker&url';
import type { CollatzWorker } from './collatz.worker';
/** The same worker file; `maxWorkers` decides how many native Workers share the calls. */
export const createCollatzWorker = (maxWorkers: number) =>
createWorker<CollatzWorker>(workerUrl, {
maxWorkers,
warmUpWorkers: true,
workerOptions: { type: 'module', name: 'worker' },
});