【问题标题】:Find the common prefix of two strings in idiomatic Swift在惯用的 Swift 中查找两个字符串的公共前缀
【发布时间】:2016-01-02 06:12:05
【问题描述】:

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

比如这个sn-p中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 "

它给人一种具有非常优雅的功能解决方案的感觉,但我看不出一种方法的最佳起点。

【问题讨论】:

  • 我认为迭代字符串字符并比较它们是最快和最优雅的解决方案。

标签: string swift text functional-programming


【解决方案1】:

只是想补充一点,Foundation 中实际上有一个方法(从 iOS 8/macOS 10.10 开始),它就是这样做的:

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

见https://developer.apple.com/reference/foundation/nsstring/1408169-commonprefix

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

【讨论】:

    【解决方案2】:

    这是另一种可能的“功能性”方法。作为一个工具,我们需要一个方法 根据谓词“截断”序列。以下用途 来自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) 枚举两个字符序列 并行创建一个(惰性求值的)pairs序列:

    ("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 快照进行测试。)

    【讨论】:

    • 感谢 Martin - 授予这个已接受的答案,因为它既干净又解释清楚。除了完成工作之外,我从您的回答中学到了很多东西。
    • 感谢 swift 4 的回答,即使没有可用的 swift 4 标签 :)
    【解决方案3】:

    我试图尽可能地考虑功能性:-]

    正确的版本

    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 }
            )
        }
    }
    

    【讨论】:

    • "a1b".commonPrefixWith("a2b") 返回 "ab" 而不是 "a" :)
    • @MartinR:好的,我更新了我的代码。现在它应该是正确的,但从计算复杂度的角度来看不是很好。
    • 酷 - 谢谢!我同样对它的工作原理感到困惑。我想我已经理解了filter(),但不是map()。请你能解释一下吗?我熟悉$0、$1 等,但不熟悉$0.1 符号,我正在努力查找。
    • 啊 - $0.1 正在访问元组中的元素。我现在明白了。
    • 你能把它转换成 Swift 4 吗?
    【解决方案4】:

    这是一个递归函数解决方案,它对字符使用简单的数组操作。我想这就是你要找的一个班轮。

    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())))
        }
    }
    

    【讨论】:

    • 优秀的答案。为了可读性,我可能会进一步简化它,尽管公认不再是真正的单行代码:``` 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()))) } } ```
    • 不需要数组转换,String(self.dropFirst()) 就可以了。但是还是会产生很多中间字符串,效率很低。
    • 你不应该delete your answer再用相同的内容转发,比较meta.stackoverflow.com/a/251732和meta.stackoverflow.com/a/297126。
    【解决方案5】:

    对于这个问题没有“优雅的功能解决方案”。请记住,你有一把锤子并不意味着每个问题都是钉子。但是,如果你想使用 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)'")
    

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

    【讨论】:

    • 感谢您的意见 Zoff。我确实认为接受的答案非常优雅,并且很好地展示了如何通过将问题分解为可重用的块来以实用的方式做事。
    【解决方案6】:

    这适用于字符串、数组和其他集合。像大多数字符串操作一样,它返回一个子字符串。如果你想要一个字符串,你可以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"
    

    【讨论】:

      猜你喜欢
      • 2017-08-03
      • 1970-01-01
      • 1970-01-01
      • 2021-02-14
      • 2011-10-21
      • 2012-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多