在惯用的Swift中找到两个字符串的公共前缀

时间:2015-10-05 14:31:20

标签: string swift text functional-programming

除了对字符串字符进行迭代并比较它们的蛮力方法之外,在Swift中找到两个字符串中最长公共前缀的最惯用方法是什么?

例如,此代码段中commonPrefixWith()的实现:

let firstString = "The quick brown fox jumps over the lazy dog" let secondString = "The quick brown fox has a pogo stick" let result = firstString.commonPrefixWith(secondString) // result == "The quick brown fox "

对于具有非常优雅的功能解决方案的东西有这种感觉,但我看不出方法的最佳起点。

6 个答案:

答案 0 :(得分:9)

只是想补充一点,基金会实际上有一种方法(自iOS 8 / macOS 10.10起),它确实如此:

func commonPrefix(with str: String, 
                  options mask: NSString.CompareOptions = []) -> String

请参阅https://developer.apple.com/reference/foundation/nsstring/1408169-commonprefix

虽然这对找到惯用/功能实现方法没有帮助,但它可能会帮助那些只需要完成工作的人。 :)

答案 1 :(得分:5)

这是另一种可能的“功能”方法。作为一种工具,我们需要一种方法 根据谓词“截断”序列。以下用途 来自https://github.com/oisdk/SwiftSequence/blob/master/SwiftSequence/TakeDrop.swift的想法。

首先为生成器类型定义takeWhile

extension GeneratorType {
    /// Returns a new generator whose `next()` function returns the elements
    /// from the given generator as long they satisfy the predicate,
    /// and then returns `nil`.
    func takeWhile(predicate : (Element) -> Bool) -> AnyGenerator<Element> {
        var gen = self
        return anyGenerator( { gen.next().flatMap( { predicate($0) ? $0 : nil }) })
    }
}

现在“解除”方法以序列类型:

extension SequenceType {
    /// Returns a new sequence with all initial elements from the given sequence
    /// satisfying the predicate.
    func takeWhile(predicate : (Generator.Element) -> Bool) -> AnySequence<Generator.Element> {
        return AnySequence( { self.generate().takeWhile(predicate) })
    }
}

这可以非常普遍地使用,这是一个简单的例子:

for i in [1, 4, 2, 5, 3].takeWhile( {$0 < 5} ) {
    print(i)
}
// Output: 1 4 2

“公共前缀”功能现在可以定义为

extension String {
    func commonPrefixWith(other: String) -> String {
        return String(zip(self.characters, other.characters).takeWhile({$0 == $1}).map({ $1 }))
    }
}

示例:

let firstString = "abc1xy"
let secondString = "abc2x"
let common = firstString.commonPrefixWith(secondString)
print(common) // abc

说明:

zip(self.characters, other.characters)枚举两个字符序列 并行创建(懒惰评估)的序列:

("a", "a"), ("b", "b"), ("c", "c"), ("1", "2"), ("x", "x")

.takeWhile({$0 == $1})将此序列限制为初始部分 两个字符串中的相同字符:

("a", "a"), ("b", "b"), ("c", "c")

.map({ $1 })将每个元组映射到第二个元素,返回数组

[ "a", "b", "c"]

最后,String(...)将字符组合成一个字符串。

Swift 4开始,序列采用prefix(while:)方法 布尔谓词,可以在这里使用而不是定义 自定义takeWhile方法:

extension String {
    func commonPrefix(with other: String) -> String {
        return String(zip(self, other).prefix(while: { $0.0 == $0.1 }).map { $0.0 })
    }
}

字符串也是(再次)它们的字符集合。 (从17.05.2017开始使用Swift 4.0快照测试。)

答案 2 :(得分:2)

我尽量认为功能正常: - ]

正确版本

extension String {
    func commonPrefixWith(another: String) -> String {
        let a = Array(self.characters)
        let b = Array(another.characters)
        return String(
            a.enumerate()
                .filter { b.count > $0.index && b[0...$0.index] == a[0...$0.index] }
                .map { $0.1 }
        )
    }
}

错误的版本

extension String {
    func commonPrefixWith(another: String) -> String {
        let b = Array(another.characters)
        return String(
            Array(self.characters)
            .enumerate()
            .filter { b.count > $0.index && b[$0.index] == $0.element }
            .map { $0.1 }
        )
    }
}

答案 3 :(得分:1)

这是一个递归函数解决方案,它对字符使用简单的数组操作。我认为这是你正在寻找的一个班轮。

extension String {
   func sharedPrefix(with other: String) -> String {
      return characters.isEmpty || other.characters.isEmpty ? "" : (characters.first! != other.characters.first! ? "" : "\(characters.first!)" + String(Array(characters.dropFirst())).sharedPrefix(with: String(Array(other.characters.dropFirst()))))
   }
}

编辑(通过OP)可能会进一步归结为此,为了便于阅读,尽管不可否认它不再是真正的单行:

extension String {
    func sharedPrefix(with other: String) -> String {
        return (self.isEmpty || other.isEmpty || self.first! != other.first!) ? "" :
            "\(self.first!)" + String(Array(self.dropFirst())).sharedPrefix(with: String(Array(other.dropFirst())))
    }
}

答案 4 :(得分:0)

这个问题没有“优雅的功能解决方案”。请记住,你有锤子并不意味着每个问题都是钉子。但是,如果您想使用Swift的高阶函数,可以解决这个问题:

extension String {
    func commonPrefixWith(aStr: String) -> String {
        var i = 0
        var stop = false

        return self.characters.reduce("") {
            aggregate, char in
            let index = self.startIndex.advancedBy(i)
            if !stop && index <= aStr.endIndex && char == aStr[index] {
                i++
                return aggregate + String(char)
            } else {
                stop = true
                return aggregate
            }
        }
    }
}


let firstString = "The quick brown fox jumps over the lazy dog"
let secondString = "The quick brown fox has a pogo stick"
let result = firstString.commonPrefixWith(secondString) // result == "The quick brown fox "

print("'\(result)'")

它不是纯粹的功能,因为它有副作用(istop),但它很接近。

答案 5 :(得分:0)

这适用于字符串,数组和其他集合。像大多数String操作一样,它返回SubString。如果需要字符串,可以String(result)

extension Collection where Element: Equatable {

    func shared_prefix( with other: Self ) -> SubSequence {

        var a = dropLast(0), b = other.dropLast(0)

        while !(a.isEmpty || b.isEmpty) 
              && (a.first == b.first) {

            ( a, b ) = ( a.dropFirst(), b.dropFirst() )

        }

        return dropLast( a.count )

    }

}

用法:

let
    a = "It works, which is nice!",
    b = "It works and that's a fact.",
    
    result = a.shared_prefix(with:b)

print( result ) // "It works"
相关问题