Skip to content

Instantly share code, notes, and snippets.

@numist
Last active July 12, 2025 20:03
Show Gist options
  • Save numist/9ad8a1996e8aabe4d74ab6aca4c82a14 to your computer and use it in GitHub Desktop.
Save numist/9ad8a1996e8aabe4d74ab6aca4c82a14 to your computer and use it in GitHub Desktop.
Sublinear solution for identifying inserted elements
func findInsertions<C: Collection>(old: C, new: C) -> [C.Index]
where C.Element: Equatable, C.Indices.Index == Int
{
var result: [C.Index] = .init()
var offsetInOld = 0
while offsetInOld < old.count {
let nextInsertInNew = firstOffset(in: offsetInOld..<old.count) { off in
return new[offset: off + result.count] != old[offset: off]
} + result.count
if nextInsertInNew >= new.count {
break
}
offsetInOld = nextInsertInNew - result.count
let subsequentMatchInNew: Int
if offsetInOld < old.count && (nextInsertInNew + 1) < new.count {
subsequentMatchInNew = firstOffset(in: (nextInsertInNew + 1)..<new.count) { off in
new[offset: off] == old[offset: offsetInOld]
}
} else {
subsequentMatchInNew = new.count
}
let newInsertedIndexes = new.index(offset: nextInsertInNew)..<new.index(offset: subsequentMatchInNew)
for index in newInsertedIndexes {
}
result.append(contentsOf: newInsertedIndexes)
offsetInOld = subsequentMatchInNew - result.count
}
return result
}
// MARK: Scaffolding and Jigs
// 2log(n) search
private func firstOffset(in range: Range<Int>, where cond: (Int) -> Bool) -> Int {
guard !range.isEmpty else {
return range.upperBound
}
if cond(range.lowerBound) {
return range.lowerBound
}
var stride = 1
var index = range.lowerBound + stride
while index < range.upperBound && !cond(index) {
stride *= 2
index += stride
}
while stride > 1 {
stride /= 2
if index >= range.upperBound || !cond(index) {
index -= stride
} else {
index += stride
}
}
return index
}
private extension Collection {
func index(offset: Int) -> Index {
return index(startIndex, offsetBy: offset)
}
subscript(offset offset: Int) -> Element {
return self[index(offset: offset)]
}
}
@gregomni
Copy link

The add inserts until next match needs to be:

        // add inserts until next match
        while new[offset: offset + result.count] != old[offset: offset] {
            result.append(new.index(offset: offset + result.count))
        }
        offset += 1

@numist
Copy link
Author

numist commented Jul 12, 2025

After all this time I ought to have known better than to mix index domains between collections, yet here we are

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment