DearSAIS

1.1.0

SA-IS algorithm for building suffix arrays in O(n). Public domain license.
obscure-tools/dear-sais

What's New

1.1.0

2026-07-28T06:59:59Z

Changed

  • Relicensed from The Unlicense to MIT-0. The earlier tag remains
    available under The Unlicense.
  • Moved to the obscure-tools organisation. The package URL is now
    https://github.com/obscure-tools/dear-sais.git.
  • Declared tvOS, watchOS and visionOS support. Existing macOS and iOS minimums
    are unchanged.

dear-sais — suffix arrays in O(n)

swift platforms release tests license

Suffix arrays in O(n), via SA-IS — from text or from bytes.

part of dear — small Swift libraries from obscure tools.

install

.package(url: "https://github.com/obscure-tools/dear-sais.git", from: "1.0.0")
.product(name: "DearSAIS", package: "dear-sais")

use

import DearSAIS

SuffixArray.from(string: "banana")            // [5, 3, 1, 0, 4, 2]
SuffixArray.from(Array("banana".utf8))        // [5, 3, 1, 0, 4, 2]

The result is the starting index of every suffix, in sorted order. For banana, suffix 5 is a, suffix 3 is ana, suffix 1 is anana, and so on.

Because the suffixes are sorted, every occurrence of a needle occupies one contiguous run of the array, so substring search becomes a binary search:

let haystack = Array("banana".utf8)
let needle = Array("ana".utf8)
let sa = SuffixArray.from(haystack)

var lo = 0, hi = sa.count
while lo < hi {
    let mid = (lo + hi) / 2
    if haystack[sa[mid]...].lexicographicallyPrecedes(needle) { lo = mid + 1 } else { hi = mid }
}

let matches = sa[lo...].prefix { haystack[$0...].starts(with: needle) }
Array(matches).sorted()   // [1, 3]

how it works

SA-IS classifies each suffix as S (smaller than its successor) or L (larger), sorts only the left-most S suffixes, and induces the position of everything else from those — recursing on a reduced problem that is at most half the size. Total cost is linear.

The naive alternative sorts all n suffixes against each other at O(n × log n), with each comparison itself potentially O(n).

Ported to idiomatic Swift from Chromium's zucchini.

why it exists

Suffix arrays underpin binary diffing. This one was written for a bsdiff-style patcher that ships low-footprint over-the-air updates to the dictionaries in an electronic dictionary app.

performance

Roughly a millisecond for 5 KiB of random bytes on an M1 Pro — enough to say the constant factor is small, not enough to quote to two decimal places. Measure on your own data and hardware:

swift test --filter Performance -Xswiftc -O

The -O is not optional. Debug builds of this code are slower by an order of magnitude and tell you nothing.

compatibility

Swift 6 · macOS 10.15+ · iOS 15+ · tvOS 15+ · watchOS 8+ · visionOS 1+ · Linux

contributing

Open an issue before a pull request — see CONTRIBUTING.md.

license

MIT-0 — MIT with the attribution clause removed. Use it, ship it, no credit required.

Description

  • Swift Tools 6.0.0
View More Packages from this Author

Dependencies

  • None
Last updated: Mon Oct 12 2026 19:06:14 GMT-0900 (Hawaii-Aleutian Daylight Time)