在字符串中查找第N个单词的范围

时间:2015-12-23 22:12:32

标签: swift string lexical-analysis

我想要的是像

int main()
{

    char line[80];
    int count;
    printf(" Enter a line of text below:\n");
    scanf("%[^\n], &line");

    for(count=0;line[count]!='\0';++count){

        if(((line[count]>='0') && (line[count] < '9'))||
            ((line[count] >= 'A') && (line[count]< 'Z'))||
            ((line[count]>= 'a' ) && (line[count] <'z' )))
             putchar(line[count]+1);
             else if(line[count] =='9' ) putchar('0');
             else if(line[count] == 'Z')putchar('A');
             else if(line[count] == 'z')putchar('a');
             else putchar('.');

    }


    return 0;
}

结果可能是一个范围或一个元组或其他什么。

我宁愿不做抄袭角色和使用状态机的蛮力。为什么要重新发明词霸?还有更好的方法吗?

2 个答案:

答案 0 :(得分:2)

在您的示例中,您的单词是唯一的,您可以使用以下方法:

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

正如Andrew Duncan在下面的评论中所指出的,只有你的话语是独一无二的,上述内容才有效。如果你有非独特的单词,你可以使用这种不太简洁的方法:

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)

最后,让我们使用后者构建String的扩展名,如您在问题示例中所希望的那样:

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

如果单词分隔不唯一(例如,某些单词由两个空格.rangeOfNthWord(...)分隔),则可以调整" "方法。

在下面的评论中也指出,使用.rangeOfString(...)本身并不是 Swift。然而,这绝不是不好的做法。来自Swift Language Guide - Strings and Characters

  

Swift的String类型与Foundation的NSString类桥接。如果   你正在使用Cocoa的Foundation框架,整个   NSString API可用于调用您创建的任何String值   类型转换为NSString,如AnyObject中所述。 您也可以使用   任何需要NSString实例的API的字符串值

另请参阅NSString class reference for rangeOfString method

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

答案 1 :(得分:0)

我继续编写状态机。 (Grumble ..)FWIW,这里是:

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

 }