web-dev-qa-db-ja.com

CharacterSetにSwift 4の文字が含まれているかどうかをテストする最良の方法は何ですか?

Swift 4)で、Characterが任意のCharacterSetのメンバーであるかどうかをテストする方法を探しています。このScannerクラスを使用して、軽量解析:クラス内の関数の1つは、現在の位置で、特定の可能な文字セットに属する文字をスキップすることです。

_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)\"")
_

上記のコードが_buzz fizz_を出力するように、skip(charactersIn:)関数を実装したいと思います。

トリッキーな部分はwhilecharacters.contains(str[idx]))です-.contains()には_Unicode.Scalar_が必要で、次のステップを見つけようとして途方に暮れています。

Stringskip関数に渡すことはできますが、すべての便利さのために、CharacterSetで機能させる方法を見つけたいと思います。静的メンバー(alphanumericswhitespacesなど)。

CharacterSetが含まれている場合、どのようにしてCharacterをテストしますか?

13
G-Mark

CharacterSetではなくStringを使用したいことを知っていますが、CharacterSetは複数のUnicode.Scalarで構成される文字を(少なくとも)サポートしていません。 「家族」キャラクター(???? ‍ ???? ‍ ???? ‍ ????)または国際旗キャラクター(例えば、「????????」または「????」 ???? ")that Apple WWDC 2017ビデオの文字列ディスカッションで示されています Swiftの新機能 。複数のスキントーン絵文字もこの動作を示しています(? ???????対????????)。

そのため、CharacterSet(「検索操作で使用するUnicode文字値のセット」)の使用には注意が必要です。または、便宜上このメソッドを提供する場合は、複数のUnicodeスカラーで表される文字では正しく機能しないことに注意してください。

したがって、CharacterSetメソッドのStringskipの両方のレンディションを提供するスキャナーを提供できます。

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"

ただし、スキップする文字に複数のUnicodeスカラーが含まれる場合は、Stringレンディションを使用します。

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

Michael Waterfallが以下のコメントで指摘したように、CharacterSetにはバグがあり、32ビットのUnicode.Scalar値を正しく処理することさえできません。つまり、値が0xffff(絵文字など)。ただし、上記のStringレンディションはこれらを正しく処理します。

6
Rob

それが最も効率的な方法かどうかはわかりませんが、新しいCharSetを作成して、それらがサブ/スーパーセットであるかどうかを確認できます(セットの比較はかなり速いです)

let newSet = CharacterSet(charactersIn: "a")
// let newSet = CharacterSet(charactersIn: "\(character)")
print(newSet.isSubset(of: CharacterSet.decimalDigits)) // false
print(newSet.isSubset(of: CharacterSet.alphanumerics)) // true
13
nathan

Swift 4.2CharacterSetが含まれているかどうかを確認するCharacter拡張関数:

extension CharacterSet {
    func containsUnicodeScalars(of character: Character) -> Bool {
        return character.unicodeScalars.allSatisfy(contains(_:))
    }
}

使用例:

CharacterSet.decimalDigits.containsUnicodeScalars(of: "3") // true
CharacterSet.decimalDigits.containsUnicodeScalars(of: "a") // false
4
Vadim Akhmerov