Как определить диапазон подстроки в одной строке, а затем использовать ее в другой

#swift

Вопрос:

В принципе, я хочу что-то вроде этого,

 NSString* foobar(NSString *input) {
    // say input is "1"
    NSString *string = @"0123456789";
    NSString *anotherString = @"零一二三四五六七八九";
    
    NSRange range = [string rangeOfString:input];
    
    // return "一" here
    return [anotherString substringWithRange:range];
}
 

Я попробовал то же самое в Swift,

 func foobar(input: String) -> String {
    // say input is "1"
    let string = "0123456789"
    
    let range = string.range(of: input, options: .anchored)
    let result = anotherString[range!]
    
    // return "012" here
    return String(result)
}
 

почему?
И как я могу этого достичь?

Ответ №1:

Строковые (или, как правило, коллекционные) индексы должны использоваться только с той коллекцией, с которой они были созданы. Чтобы найти те же позиции в другой строке, индексы должны быть преобразованы в (целочисленные) смещения и обратно в индексы целевой строки:

 func foobar(input: String) -> String? {
    let s1 = "0123456789"
    let s2 = "😀一二三四五六七八九";

    guard let range = s1.range(of: input) else {
        return nil
    }
    let pos = s1.distance(from: s1.startIndex, to: range.lowerBound)
    let len = s1.distance(from: range.lowerBound, to: range.upperBound)
    guard
        let lo = s2.index(s2.startIndex, offsetBy: pos, limitedBy: s2.endIndex),
        let hi = s2.index(lo, offsetBy: len, limitedBy: s2.endIndex)
    else {
        return nil
    }
    return String(s2[lo..<hi])
}

print(foobar(input: "1") as Any) // Optional("一")
print(foobar(input: "123") as Any) // Optional("一二三")
print(foobar(input: "124") as Any) // nil
 

Ваш код Objective-C работает до тех пор, пока все символы в строке используют одну кодовую единицу UTF-16 (потому что это NSRange считается). Это не будет корректно работать с смайликами, флагами и другими символами, которые представлены в виде пар заменителей UTF-16, например, с

 NSString *anotherString = @"😀一二三四五六七八九";
 

Комментарии:

1. вы также можете просто посчитать дальность действия и добавить ее к lo . Это позволит избежать startIndex двойного взаимозачета

2. let i2 = s1.distance(from: range.lowerBound, to: range.upperBound) и let hi = s2.index(lo, offsetBy: i2, limitedBy: s2.endIndex)

3. @LeoDabus: Вы совершенно правы, спасибо.

4. Спасибо, это очень помогает, теперь я могу продолжать.

Ответ №2:

Другой подход заключается в преобразовании строк в массив символов

 func find(_ str: Character) {
    let firstArr = Array("0123456789")
    let secondArr = Array("零一二三四五六七八九")
    guard let index = firstArr.firstIndex(of: str) else {
        print("Not found")
        return
    }
    print(firstArr[index]) // 2
    print(secondArr[index]) // 二
}
find("2")
 

Комментарии:

1. Спасибо! Это достаточно просто для моей ситуации.