2015-12-23 2 views
0

Ce que je veux est quelque chose commeTrouver la portée du mot Nième dans une chaîne

"word1 word2 word3".rangeOfWord(2) => 6 to 10 

Le résultat pourrait venir d'une plage ou un tuple ou autre.

Je préfère ne pas faire la force brute de l'itération sur les caractères et en utilisant une machine à états. Pourquoi réinventer le lexer? Y a-t-il un meilleur moyen?

+0

Ce n'est pas vraiment un "lexer" que vous implémentez! – Noldorin

+0

Salut, Andrew - Connaissez-vous NSLinguisticTagger? - Ou, dans votre exemple plutôt simple, NSRegularExpression ne serait-il pas suffisant? – matt

+0

Vous savez, en tant que (ancien) Perl hacker, j'aurais dû penser à REs. Bien que je ne sois pas intéressé par trouver le mot Nième, mais trouver sa gamme. Est-ce que je pourrais faire ça avec un RE? Ce n'est pas une pure ER informatique, bien sûr, mais peut-être même des améliorations. –

Répondre

2

Dans votre exemple, vos mots sont uniques, et vous pouvez utiliser la méthode suivante:

let myString = "word1 word2 word3" 
let wordNum = 2 
let myRange = myString.rangeOfString(myString.componentsSeparatedByString(" ")[wordNum-1]) 
    // 6..<11 

Comme l'a souligné Andrew Duncan dans les commentaires ci-dessous, ce qui précède est valable uniquement si vos mots sont uniques. Si vous avez des mots non uniques, vous pouvez utiliser cette méthode un peu moins plus propre:

let myString = "word1 word2 word3 word2 word1 word3 word1" 
let wordNum = 7 // 2nd instance (out of 3) of "word1" 
let arr = myString.componentsSeparatedByString(" ") 
var fromIndex = arr[0..<wordNum-1].map { $0.characters.count }.reduce(0, combine: +) + wordNum - 1 

let myRange = Range<String.Index>(start: myString.startIndex.advancedBy(fromIndex), end: myString.startIndex.advancedBy(fromIndex+arr[wordNum-1].characters.count)) 
let myWord = myString.substringWithRange(myRange) 
    // string "word1" (from range 36..<41) 

Enfin, permet d'utiliser ce dernier pour construire une extension de String que vous avez souhaité dans votre exemple de question:

extension String { 
    private func rangeOfNthWord(wordNum: Int, wordSeparator: String) -> Range<String.Index>? { 
     let arr = myString.componentsSeparatedByString(wordSeparator) 

     if arr.count < wordNum { 
      return nil 
     } 
     else { 
      let fromIndex = arr[0..<wordNum-1].map { $0.characters.count }.reduce(0, combine: +) + (wordNum - 1)*wordSeparator.characters.count 
      return Range<String.Index>(start: myString.startIndex.advancedBy(fromIndex), end: myString.startIndex.advancedBy(fromIndex+arr[wordNum-1].characters.count)) 
     } 
    } 
} 

let myString = "word1 word2 word3 word2 word1 word3 word1" 
let wordNum = 7 // 2nd instance (out of 3) of "word1" 

if let myRange = myString.rangeOfNthWord(wordNum, wordSeparator: " ") { 
     // myRange: 36..<41 
    print(myString.substringWithRange(myRange)) // prints "word1" 
} 

Vous pouvez modifier la méthode .rangeOfNthWord(...) si la séparation des mots n'est pas unique (par exemple, certains mots sont séparés par deux espaces vides " ").


a également souligné dans les commentaires ci-dessous, l'utilisation de .rangeOfString(...) n'est pas, en soi, pur Swift. Ce n'est cependant pas une mauvaise pratique. De Swift Language Guide - Strings and Characters:

Le type de chaîne de Swift est ponté avec la classe NSString de Foundation. Si vous travaillez avec le framework Foundation dans Cocoa, l'intégralité de l'API NSString est disponible pour appeler toute valeur de chaîne que vous créez lorsque le type est transtypé en NSString, comme décrit dans AnyObject. Vous pouvez également utiliser une valeur de chaîne avec n'importe quelle API nécessitant une instance NSString.

Voir aussi la NSString class reference for rangeOfString method:

// Swift Declaration: 
func rangeOfString(_ searchString: String) -> NSRange 
+0

Notez que ce n'est pas pur Swift, car il utilise les méthodes 'NSString' (Foundation). – Noldorin

+0

Je pense que cela échouerait avec le texte source comme "aaa bbb aaa ccc". Chercher le troisième mot retournerait le premier. –

+0

@AndrewDuncan Vous avez raison, merci. J'ai modifié la réponse pour inclure un correctif pour une chaîne contenant des mots non uniques (séparés par ""). – dfri

0

je suis allé de l'avant et écrit la machine d'état. (Grumble ..) FWIW, le voici:

extension String { 
    private func halfOpenIntervalOfBlock(n:Int, separator sep:Character? = nil) -> (Int, Int)? { 
     enum State { 
      case InSeparator 
      case InPrecedingSeparator 
      case InWord 
      case InTarget 
      case Done 
     } 

     guard n > 0 else { 
      return nil 
     } 

     var state:State 
     if n == 1 { 
      state = .InPrecedingSeparator 
     } else { 
      state = .InSeparator 
     } 

     var separatorNum = 0 
     var startIndex:Int = 0 
     var endIndex:Int = 0 

     for (i, c) in self.characters.enumerate() { 
      let inSeparator:Bool 
      // A bit inefficient to keep doing this test. 
      if let s = sep { 
       inSeparator = c == s 
      } else { 
       inSeparator = c == " " || c == "\n" 
      } 
      endIndex = i 

      switch state { 
      case .InPrecedingSeparator: 
       if !inSeparator { 
        state = .InTarget 
        startIndex = i 
       } 

      case .InTarget: 
       if inSeparator { 
        state = .Done 
       } 

      case .InWord: 
       if inSeparator { 
        separatorNum += 1 
        if separatorNum == n - 1 { 
         state = .InPrecedingSeparator 
        } else { 
         state = .InSeparator 
        } 
       } 

      case .InSeparator: 
       if !inSeparator { 
        state = .InWord 
       } 

      case .Done: 
       break 
      } 

      if state == .Done { 
       break 
      } 
     } 

     if state == .Done { 
      return (startIndex, endIndex) 
     } else if state == .InTarget { 
      return (startIndex, endIndex + 1) // We ran off end. 
     } else { 
      return nil 
     } 
    } 

    func rangeOfWord(n:Int) -> Range<Index>? { 
     guard let (s, e) = self.halfOpenIntervalOfBlock(n) else { 
      return nil 
     } 
     let ss = self.startIndex.advancedBy(s) 
     let ee = self.startIndex.advancedBy(e) 
     return Range(start:ss, end:ee) 
    } 

} 
+0

Aargh! String.enumerateSubstringsInRange (..., options: .byWord) –