Sort Lines & Remove Duplicates — TypeScript source
Alphabetize, reverse, shuffle, dedupe, or length-sort lines of text. Supports case-insensitive and natural sorting (file2 before file10).
This is the TypeScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Pure line-sorting logic - no React, no DOM, deterministic.
// Splits on newlines and applies the requested operation. Never throws.
export type SortMode =
| 'asc'
| 'desc'
| 'length-asc'
| 'length-desc'
| 'reverse'
| 'shuffle'
| 'unique';
export interface SortOptions {
caseSensitive?: boolean; // default true
trim?: boolean; // default false
natural?: boolean; // default false
seed?: number; // for shuffle, default 1
}
export interface SortResult {
lines: string[];
text: string;
removedDuplicates: number;
}
/** mulberry32 - small deterministic PRNG returning floats in [0, 1). */
export function mulberry32(seed: number): () => number {
let a = seed >>> 0;
return function () {
a |= 0;
a = (a + 0x6d2b79f5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
// Natural comparator: splits into alternating text/numeric chunks.
function naturalCompare(a: string, b: string, caseSensitive: boolean): number {
const ax = caseSensitive ? a : a.toLowerCase();
const bx = caseSensitive ? b : b.toLowerCase();
const re = /(\d+|\D+)/g;
const aa = ax.match(re) ?? [ax];
const bb = bx.match(re) ?? [bx];
const len = Math.min(aa.length, bb.length);
for (let i = 0; i < len; i++) {
const an = /^\d/.test(aa[i]);
const bn = /^\d/.test(bb[i]);
if (an !== bn) return aa[i] < bb[i] ? -1 : 1;
if (an) {
const diff = parseInt(aa[i], 10) - parseInt(bb[i], 10);
if (diff !== 0) return diff;
} else if (aa[i] !== bb[i]) {
return aa[i] < bb[i] ? -1 : 1;
}
}
return aa.length - bb.length;
}
export function sortLines(input: string, mode: SortMode, opts: SortOptions = {}): SortResult {
const caseSensitive = opts.caseSensitive ?? true;
const shouldTrim = opts.trim ?? false;
const natural = opts.natural ?? false;
const seed = opts.seed ?? 1;
let lines = (input ?? '').split('\n');
if (shouldTrim) lines = lines.map((l) => l.trim());
const norm = (s: string) => (caseSensitive ? s : s.toLowerCase());
let removedDuplicates = 0;
if (mode === 'unique') {
const seen = new Set<string>();
const out: string[] = [];
for (const l of lines) {
const key = norm(l);
if (seen.has(key)) {
removedDuplicates++;
} else {
seen.add(key);
out.push(l);
}
}
lines = out;
} else if (mode === 'shuffle') {
const rng = mulberry32(seed);
const arr = [...lines];
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
lines = arr;
} else if (mode === 'reverse') {
lines = [...lines].reverse();
} else if (mode === 'length-asc' || mode === 'length-desc') {
const dir = mode === 'length-asc' ? 1 : -1;
lines = [...lines]
.map((l, i) => ({ l, i }))
.sort((a, b) => a.l.length - b.l.length || a.i - b.i)
.map((x) => x.l);
if (dir === -1) lines.reverse();
} else {
// asc / desc
const dir = mode === 'asc' ? 1 : -1;
lines = [...lines].sort((a, b) => {
const c = natural
? naturalCompare(a, b, caseSensitive)
: norm(a).localeCompare(norm(b));
return c * dir;
});
}
return { lines, text: lines.join('\n'), removedDuplicates };
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →