Sort Lines & Remove Duplicates — C source
Alphabetize, reverse, shuffle, dedupe, or length-sort lines of text. Supports case-insensitive and natural sorting (file2 before file10).
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* sort-lines — multi-mode line sorter. Language: C (C11). Port of src/lib/sortLines.ts — same contract as this dir's go.go (the live Go twin): split on '\n', apply the mode (asc/desc/length-asc/length-desc/reverse/shuffle/unique), join back. Insertion sort is stable, so ties keep input order like the TS sort; shuffle uses mulberry32, so a seed reproduces the TS order. */
#include <ctype.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum mode { ASC, DESC, LEN_ASC, LEN_DESC, REVERSE, SHUFFLE, UNIQUE };
struct opts { int case_sensitive, trim, natural, seed; }; /* TS defaults: 1, 0, 0, 1 */
struct result { char **lines; size_t n; int removed; char *text; };
/* mulberry32 — deterministic PRNG (not cryptographic); a seed reproduces the same shuffle. */
static double rng_next(uint32_t *a)
{
*a += 0x6d2b79f5u;
uint32_t t = (*a ^ (*a >> 15)) * (1u | *a);
t = (t + (t ^ (t >> 7)) * (61u | t)) ^ t;
return (double)(t ^ (t >> 14)) / 4294967296.0;
}
static int isdig(char c) { return c >= '0' && c <= '9'; }
/* Plain order: byte compare, case-folded unless case_sensitive — localeCompare for ASCII. */
static int plain_cmp(const char *a, const char *b, int cs)
{
while (*a && *b) {
int x = cs ? (unsigned char)*a : tolower((unsigned char)*a);
int y = cs ? (unsigned char)*b : tolower((unsigned char)*b);
if (x != y) return x < y ? -1 : 1;
a++, b++;
}
return *a ? 1 : *b ? -1 : 0;
}
/* Natural order: walk both strings in ASCII digit / non-digit runs, comparing chunk-wise so numbers order by value — "file2" sorts before "file10". Digit runs compare by numeric value: strip leading zeros, longer run wins, then lex (an overflow-free parseInt diff). */
static int natural_cmp(const char *a, const char *b, int cs)
{
while (*a || *b) {
int da = isdig(*a), db = isdig(*b);
const char *pa = a, *pb = b;
while (da ? isdig(*a) : *a && !isdig(*a)) a++;
while (db ? isdig(*b) : *b && !isdig(*b)) b++;
size_t la = (size_t)(a - pa), lb = (size_t)(b - pb);
if (da != db) { /* digit run vs text run: raw byte compare, shorter prefix first */
int c = strncmp(pa, pb, la < lb ? la : lb);
return c ? (c < 0 ? -1 : 1) : (la < lb ? -1 : la > lb ? 1 : 0);
}
if (da) {
while (la && *pa == '0') pa++, la--;
while (lb && *pb == '0') pb++, lb--;
if (la != lb) return la < lb ? -1 : 1;
int c = la ? strncmp(pa, pb, la) : 0;
if (c) return c < 0 ? -1 : 1;
} else {
for (size_t i = 0; i < la && i < lb; i++) {
int x = cs ? (unsigned char)pa[i] : tolower((unsigned char)pa[i]);
int y = cs ? (unsigned char)pb[i] : tolower((unsigned char)pb[i]);
if (x != y) return x < y ? -1 : 1;
}
if (la != lb) return la < lb ? -1 : 1;
}
}
return 0;
}
static struct opts O; /* options + direction for the comparators (single-threaded demo) */
static int Dir;
static int cmp_text(const char *x, const char *y)
{
int c = O.natural ? natural_cmp(x, y, O.case_sensitive) : plain_cmp(x, y, O.case_sensitive);
return c * Dir;
}
static int cmp_len(const char *x, const char *y)
{
size_t lx = strlen(x), ly = strlen(y);
return lx < ly ? -1 : lx > ly ? 1 : 0;
}
/* Stable insertion sort — ties keep input order, like the TS (stable) sort. */
static void sort_stable(char **v, size_t n, int (*cmp)(const char *, const char *))
{
for (size_t i = 1; i < n; i++) {
char *key = v[i];
size_t j = i;
while (j > 0 && cmp(v[j - 1], key) > 0) { v[j] = v[j - 1]; j--; }
v[j] = key;
}
}
static void reverse_lines(char **v, size_t n)
{
for (size_t i = 0; i < n / 2; i++) { char *t = v[i]; v[i] = v[n - 1 - i]; v[n - 1 - i] = t; }
}
static struct result sort_lines(const char *input, enum mode m, struct opts o)
{
struct result r = { NULL, 0, 0, NULL };
O = o, Dir = m == DESC ? -1 : 1;
/* split on '\n' — the trailing empty line is kept, like JS split("\n") */
size_t n = 1;
for (const char *p = input; *p; p++) n += *p == '\n';
r.lines = malloc(n * sizeof *r.lines), r.n = n;
size_t i = 0;
const char *s = input;
for (;;) {
const char *nl = strchr(s, '\n');
size_t len = nl ? (size_t)(nl - s) : strlen(s);
char *line = malloc(len + 1);
memcpy(line, s, len), line[len] = '\0';
if (o.trim) { /* trim spaces / tabs / CR both ends */
size_t b = 0, e = len;
while (b < e && (line[b] == ' ' || line[b] == '\t' || line[b] == '\r')) b++;
while (e > b && (line[e - 1] == ' ' || line[e - 1] == '\t' || line[e - 1] == '\r')) e--;
memmove(line, line + b, e - b), line[e - b] = '\0';
}
r.lines[i++] = line;
if (!nl) break;
s = nl + 1;
}
if (m == UNIQUE) { /* keep each normalized line's first occurrence; count the rest */
char **out = malloc(n * sizeof *out);
size_t no = 0;
for (size_t k = 0; k < n; k++) {
size_t t = 0;
while (t < no && plain_cmp(r.lines[k], out[t], o.case_sensitive) != 0) t++;
if (t < no) r.removed++;
else out[no++] = r.lines[k];
}
free(r.lines), r.lines = out, r.n = no;
} else if (m == SHUFFLE) { /* Fisher-Yates with the seeded PRNG -> reproducible order */
uint32_t a = (uint32_t)o.seed;
for (size_t k = n; k-- > 1; ) {
size_t j = (size_t)(rng_next(&a) * (double)(k + 1));
char *t = r.lines[k]; r.lines[k] = r.lines[j], r.lines[j] = t;
}
} else if (m == REVERSE) {
reverse_lines(r.lines, r.n);
} else if (m == LEN_ASC || m == LEN_DESC) { /* stable by length, then reverse for desc */
sort_stable(r.lines, r.n, cmp_len);
if (m == LEN_DESC) reverse_lines(r.lines, r.n);
} else { /* ASC / DESC — stable, ties keep input order in both directions */
sort_stable(r.lines, r.n, cmp_text);
}
size_t total = 1; /* join back with '\n' */
for (size_t k = 0; k < r.n; k++) total += strlen(r.lines[k]) + 1;
r.text = malloc(total);
char *w = r.text;
for (size_t k = 0; k < r.n; k++) {
size_t l = strlen(r.lines[k]);
memcpy(w, r.lines[k], l), w += l;
if (k + 1 < r.n) *w++ = '\n';
}
*w = '\0';
return r;
}
int main(void)
{
const char *text = "pear\napple\nBanana\napple\nfig10\nfig2";
struct opts def = { 1, 0, 0, 1 }, ci = { 0, 0, 0, 1 }, sh = { 1, 0, 0, 7 }, nat = { 0, 0, 1, 1 };
struct result r = sort_lines(text, ASC, def);
printf("asc: %s\n", r.text);
r = sort_lines(text, ASC, ci);
printf("ci-asc: %s\n", r.text);
r = sort_lines(text, UNIQUE, ci);
printf("uniq: %s (removed %d)\n", r.text, r.removed);
r = sort_lines(text, SHUFFLE, sh);
printf("shuf-7: %s\n", r.text);
r = sort_lines(text, ASC, nat);
printf("nat-ci: %s\n", r.text);
}
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 →