File: EditDistance.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (71 lines) | stat: -rw-r--r-- 2,777 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/*
 This source file is part of the Swift.org open source project

 Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
 Licensed under Apache License v2.0 with Runtime Library Exception

 See http://swift.org/LICENSE.txt for license information
 See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

/// Computes the number of edits needed to transform source string to target.
///
/// - Complexity: O(_n*m_), where *n* is the length of the source String and
///   *m* is the length of the target one.
public func editDistance(_ source: String, _ target: String) -> Int {
    // FIXME: We should use the new `CollectionDifference` API once the
    // deployment target is bumped.
    if #available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) {
        return collectionDiffEditDistance(source, target)
    }
    else {
        return internalEditDistance(source, target)
    }
}

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
func collectionDiffEditDistance(_ source: String, _ target: String) -> Int {
    let difference = target.difference(from: source)
    return max(difference.insertions.count, difference.removals.count)
}

func internalEditDistance(_ first: String, _ second: String) -> Int {
    let a = Array(first.utf16)
    let b = Array(second.utf16)
    var distance = [[Int]](repeating: [Int](repeating: 0, count: b.count + 1), count: a.count + 1)
    for i in 0...a.count {
        for j in 0...b.count {
            if i == 0 {
                distance[i][j] = j
            } else if j == 0 {
                distance[i][j] = i
            } else if a[i - 1] == b[j - 1] {
                distance[i][j] = distance[i - 1][j - 1]
            } else {
                let insertion = distance[i][ j - 1]
                let deletion = distance[i - 1][j]
                let replacement = distance[i - 1][j - 1]
                distance[i][j] = 1 + min(insertion, deletion, replacement)
            }
        }
    }
    return distance[a.count][b.count]
}

/// Finds the "best" match for a `String` from an array of possible options.
///
/// - Parameters:
///     - input: The input `String` to match.
///     - options: The available options for `input`.
///
/// - Returns: The best match from the given `options`, or `nil` if none were sufficiently close.
public func bestMatch(for input: String, from options: [String]) -> String? {
    return options
        .map { ($0, editDistance(input, $0)) }
        // Filter out unreasonable edit distances. Based on:
        // https://github.com/apple/swift/blob/37daa03b7dc8fb3c4d91dc560a9e0e631c980326/lib/Sema/TypeCheckNameLookup.cpp#L606
        .filter { $0.1 <= ($0.0.count + 2) / 3 }
        // Sort by edit distance
        .sorted { $0.1 < $1.1 }
        .first?.0
}