Sort Lines & Remove Duplicates — Ruby source
Alphabetize, reverse, shuffle, dedupe, or length-sort lines of text. Supports case-insensitive and natural sorting (file2 before file10).
This is the Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# sort-lines — multi-mode line sorter. Language: Ruby (3.1+, stdlib only). 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. Ruby's sort is not stable, so every ordered mode decorates with the original index (the TS sort is stable — ties keep input order). Shuffle uses mulberry32, so a seed reproduces the TS order.
module SortLines
# Deterministic PRNG (not cryptographic); a seed reproduces the same shuffle.
# Ints are arbitrary precision, so each wrapping step is masked to 32 bits —
# exactly the JS |0 / Math.imul semantics of the TS original.
def self.mulberry32(seed)
a = seed & 0xFFFFFFFF
lambda do
a = (a + 0x6D2B79F5) & 0xFFFFFFFF
t = ((a ^ (a >> 15)) * (1 | a)) & 0xFFFFFFFF
t = ((t + ((t ^ (t >> 7)) * (61 | t))) ^ t) & 0xFFFFFFFF
((t ^ (t >> 14)) & 0xFFFFFFFF) / 4294967296.0
end
end
# Natural order: compare ASCII digit / non-digit runs chunk-wise, digit runs by
# value — "file2" sorts before "file10".
def self.natural_compare(a, b, case_sensitive)
ax = case_sensitive ? a : a.downcase
bx = case_sensitive ? b : b.downcase
aa = ax.scan(/[0-9]+|[^0-9]+/); aa << '' if aa.empty? # "" -> [""], like the TS ?? fallback
bb = bx.scan(/[0-9]+|[^0-9]+/); bb << '' if bb.empty?
(0...[aa.length, bb.length].min).each do |i|
x, y = aa[i], bb[i]
dn = x.match?(/\A[0-9]/)
dm = y.match?(/\A[0-9]/)
if dn != dm # digit run vs text run: raw compare
return x < y ? -1 : 1
elsif dn
vx = x.sub(/\A0+/, ''); vy = y.sub(/\A0+/, '') # numeric value: strip leading zeros
c = vx.length <=> vy.length
c = vx <=> vy if c.zero?
return c unless c.zero?
elsif x != y
return x < y ? -1 : 1
end
end
aa.length - bb.length
end
# All modes return a { lines:, text:, removed_duplicates: } hash. keyword args
# carry the four TS options; omitted means "use the TS default".
def self.sort_lines(input, mode, case_sensitive: true, trim: false, natural: false, seed: 1)
norm = ->(s) { case_sensitive ? s : s.downcase }
lines = (input || '').split("\n", -1) # -1 keeps the trailing empty line, like JS
lines = lines.map(&:strip) if trim
removed = 0
case mode
when 'unique' # keep each normalized line's first occurrence; count the rest
seen = {}
out = []
lines.each do |l|
if seen.key?(norm.call(l)) then removed += 1 else seen[norm.call(l)] = true; out << l end
end
lines = out
when 'shuffle' # Fisher-Yates with the seeded PRNG -> reproducible order
rng = mulberry32(seed)
(lines.length - 1).downto(1) do |i|
j = (rng.call * (i + 1)).floor
lines[i], lines[j] = lines[j], lines[i]
end
when 'reverse'
lines = lines.reverse
when 'length-asc', 'length-desc' # stable by length, then reverse for desc
lines = lines.each_with_index.sort_by { |l, i| [l.length, i] }.map(&:first)
lines.reverse! if mode == 'length-desc'
else # asc / desc — stable in both directions, ties keep input order
dir = mode == 'asc' ? 1 : -1
lines = lines.each_with_index.sort { |(x, xi), (y, yi)|
c = natural ? natural_compare(x, y, case_sensitive) : norm.call(x) <=> norm.call(y)
c.zero? ? xi <=> yi : c * dir
}.map(&:first)
end
{ lines: lines, text: lines.join("\n"), removed_duplicates: removed }
end
end
text = "pear\napple\nBanana\napple\nfig10\nfig2"
puts "asc: #{SortLines.sort_lines(text, 'asc')[:text]}"
puts "ci-asc: #{SortLines.sort_lines(text, 'asc', case_sensitive: false)[:text]}"
u = SortLines.sort_lines(text, 'unique', case_sensitive: false)
puts "uniq: #{u[:text]} (removed #{u[:removed_duplicates]})"
puts "shuf-7: #{SortLines.sort_lines(text, 'shuffle', seed: 7)[:text]}"
puts "nat-ci: #{SortLines.sort_lines(text, 'asc', case_sensitive: false, natural: true)[: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 →