2017-08-24 108 views
5

我正在寻找一种方法,在Swift 4中测试一个Character是否是任意CharacterSet的成员。我有这个Scanner类将用于一些轻量级的解析。该类中的一个功能是跳过属于某组可能字符的当前位置的任何字符。测试Swift 4中CharacterSet是否包含字符的最佳方法是什么?

class MyScanner { 
    let str: String 
    var idx: String.Index 
    init(_ string: String) { 
    str = string 
    idx = str.startIndex 
    } 
    var remains: String { return String(str[idx..<str.endIndex])} 

    func skip(charactersIn characters: CharacterSet) { 
    while idx < str.endIndex && characters.contains(str[idx])) { 
     idx = source.index(idx, offsetBy: 1) 
    } 
    } 
} 

let scanner = MyScanner("fizz buzz fizz") 
scanner.skip(charactersIn: CharacterSet.alphanumerics) 
scanner.skip(charactersIn: CharacterSet.whitespaces) 
print("what remains: \"\(scanner.remains)\"") 

我想实现skip(charactersIn:)功能,使上面的代码将打印buzz fizz

棘手的部分是characters.contains(str[idx]))while - .contains()需要Unicode.Scalar,我很茫然,试图找出下一步。

我知道我可以在String传递给skip功能,但我想找到一种方法,使其与一个CharacterSet工作,因为所有的便利静态成员(alphanumericswhitespaces等)。

如何测试CharacterSet如果包含Character

+0

有一个名为'NSScanner'的系统类,以'Scanner'的形式桥接到Swift中。你检查过它吗? –

+0

NSScanner肯定看起来像我正在重新发明的轮子。不是疯狂的NS语义(使用in'NSString?'参数),但它可能会伎俩。出于好奇,我浏览了[source](https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/Scanner.swift),并将'String'转换为'Array ',它是'skip'函数,然后使用'set.contains(UnicodeScalar(currentCharacter)!)'。 – PocketLogic

+0

如果您不喜欢'NSScanner'的NS语义,请使用Foundation的'Scanner',它不使用NS类型。当然,不要用现有类的名称来定义自己的类。这只会让人困惑。 – Rob

回答

3

我知道你想使用CharacterSet而不是String,但CharacterSet不支持(但至少)支持由多个Unicode.Scalar组成的字符。请参阅Apple在WWDC 2017视频What's New in Swift的字符串讨论中演示的“家庭”字符()或国际标记字符(例如“”或“”)。多肤色表情符号也表现出这种行为(例如vs)。

因此,我会谨慎使用CharacterSet(这是一组“用于搜索操作的Unicode字符值”)。或者,如果您想为方便起见而提供此方法,请注意,它将无法正确使用由多个unicode标量表示的字符。

所以,你可能会提供一个扫描仪,提供了skip方法既CharacterSetString引渡:

class MyScanner { 
    let string: String 
    var index: String.Index 

    init(_ string: String) { 
     self.string = string 
     index = string.startIndex 
    } 

    var remains: String { return String(string[index...]) } 

    /// Skip characters in a string 
    /// 
    /// This rendition is safe to use with strings that have characters 
    /// represented by more than one unicode scalar. 
    /// 
    /// - Parameter skipString: A string with all of the characters to skip. 

    func skip(charactersIn skipString: String) { 
     while index < string.endIndex, skipString.contains(string[index]) { 
      index = string.index(index, offsetBy: 1) 
     } 
    } 

    /// Skip characters in character set 
    /// 
    /// Note, character sets cannot (yet) include characters that are represented by 
    /// more than one unicode scalar (e.g. ‍‍‍ or or). If you want to test 
    /// for these multi-unicode characters, you have to use the `String` rendition of 
    /// this method. 
    /// 
    /// This will simply stop scanning if it encounters a multi-unicode character in 
    /// the string being scanned (because it knows the `CharacterSet` can only represent 
    /// single-unicode characters) and you want to avoid false positives (e.g., mistaking 
    /// the Jamaican flag, , for the Japanese flag,). 
    /// 
    /// - Parameter characterSet: The character set to check for membership. 

    func skip(charactersIn characterSet: CharacterSet) { 
     while index < string.endIndex, 
      string[index].unicodeScalars.count == 1, 
      let character = string[index].unicodeScalars.first, 
      characterSet.contains(character) { 
       index = string.index(index, offsetBy: 1) 
     } 
    } 

} 

因此,您简单的例子仍然可以工作:

let scanner = MyScanner("fizz buzz fizz") 
scanner.skip(charactersIn: CharacterSet.alphanumerics) 
scanner.skip(charactersIn: CharacterSet.whitespaces) 
print(scanner.remains) // "buzz fizz" 

但使用String如果要跳过的字符可能包含多个Unicode标量:

let family = "\u{200D}\u{200D}\u{200D}" // ‍‍‍ 
let boy = "" 

let charactersToSkip = family + boy 

let string = boy + family + "foobar" // ‍‍‍foobar 

let scanner = MyScanner(string) 
scanner.skip(charactersIn: charactersToSkip) 
print(scanner.remains)    // foobar 

正如迈克尔瀑布下面的评论中指出,CharacterSet有缺陷,甚至不正确地处理32位Unicode.Scalar值,这意味着它甚至不正确,如果该值超过0xffff处理单个标字符(包括表情符号等)。然而,上面的String演绎处理正确。

+2

有趣的是'CharacterSet'甚至不处理用单个unicode表示的表情符号标量(= 128518)然而这返回'false': 'CharacterSet(charactersIn:“ABC”)。contains(UnicodeScalar(128518)!)' –

+0

是的,超出单个字符集的标量限制,显然有一个bug在'CharacterSet'中处理32位标量,将它们处理为16位标量。例如。试着在你的字符串中查找'Unicode.Scalar(62982)'(即'128518 && 0xffff');大声笑。这一切都可以用16位标量正常工作,但是当您尝试使用超过“UInt16.max”值的32位标量时,这是一个火车残骸。我们应该提交一个错误报告。我很乐意这样做,除非你愿意这样做。 – Rob

1

不知道这是否是最有效的方式,但你可以创建一个新的字符集,并检查它们是否子/超集(集比较是相当快)

let newSet = CharacterSet(charactersIn: "a") 
// let newSet = CharacterSet(charactersIn: "\(character)") 
print(newSet.isSubset(of: CharacterSet.decimalDigits)) // false 
print(newSet.isSubset(of: CharacterSet.alphanumerics)) // true 
相关问题