I'm struggling because I don't understand how Swift's Range works.

Asked 2 years ago, Updated 2 years ago, 66 views

I just don't understand how Range works, and I don't know how to calculate the start index to the number of characters acquired.
What we want to implement is to get the start index to the end index.
I don't know how to implement the following syntax implemented by JAVA in Swift:
Could you please let me know?

//argContentHTML=Home Page HTML
// Parse HTML
// Get first and last index
intIDSearchIndex=argContentHTML.indexOf("tag1");
intIDFootSearchIndex=argContentHTML.indexOf("tag2",
                                            intIDSearchIndex+"tag1".length());

// Determine if Index Found
if(intIDSearchIndex==-1||
   intIDFootSearchIndex==-1){

    // Do not add data if either index is not found
} else {
    // If normal, set the data
    // Obtain ID from Entry Department
    strRet=argContentHTML.substring(intIDSearchIndex+"tag1".length(),
                                      intIDFootSearchIndex);
}

ios swift

2022-09-30 21:11

2 Answers

Assuming Swift2/Xcode7,

import Foundation

letstr="AAATAtag2BBBtag1CCCtag2DDD"
iflet
    tag1 = str.rangeOfString("tag1"),
    tag2 = str.rangeOfString("tag2", range:tag1.endIndex..<str.endIndex){

        let strRet = String (str [tag1.endIndex..<tag2.startIndex])
        // ->"CCC"
        // You can also str.substringWithRange (tag1.endIndex..<tag2.startIndex)
}

In str.rangeOfString("tag1"), obtain the range where "tag1" appears in str.
str.rangeOfString("tag2", range:tag1.endIndex..<str.endIndex) searches the "tag2" range from the str end of str to the end.
In , you use the standard subscript to obtain slices, which are String.CharacterView, not String, so you convert them to the String type on the String() initiator.

If you don't rely on Foundation and only do Swift standard libraries, this is what it looks like:

letstr="AAAtag2BBBtag1CCCtag2DDD"

let tag1 = "tag1".characters
let tag2 = "tag2".characters
letstr_=str.characters

iflet
    start=str_.indices.indexOf({str_.suffixFrom($0).startsWith(tag1)})?.advancedBy(tag1.count),
    end=(start..<str_.endIndex).indexOf({str_.suffixFrom($0).startsWith(tag2)}}){

        let strRet=String(str_[start..<end])
        // ->"CCC"
}

The readability is terrible.
Factor out looking for the start index of the substring for a bit more readability:

extension CollectionType where SubSequence.Generator.Element:Equatable{
    /// Returns the start index of `sub` in `self`.If not found, `nil`.
    /// By default, search the entire `self` by the range when given a range is given, it is searched within that range.
    func startIndexOf(sub:SubSequence, range:Range<Index>?=nil)->Index?{
        let range = range ???indices
        return range.indexOf{
            self.suffixFrom($0).startsWith(sub)
        }
    }
}

letstr="AAATAtag2BBBtag1CCCtag2DDD"

let tag1 = "tag1".characters
let tag2 = "tag2".characters
letstr_=str.characters

iflet
    start=str_.startIndexOf(tag1) ?.advancedBy(tag1.count),
    end=str_.startIndexOf(tag2,range:start..<str_.endIndex){

        let strRet=String(str_[start..<end])
        // ->"CCC"
}

Benchmarking
Regardless of the question, let me write it for my memorandum

Regardless of the question, let me write it for my memorandum
import XCTest

extension CollectionType where SubSequence.Generator.Element:Equatable{
    /// Returns the start index of `sub` in `self`.If not found, `nil`.
    /// By default, search the entire `self` by the range when given a range is given, it is searched within that range.
    func startIndexOf(sub:SubSequence, range:Range<Index>?=nil)->Index?{
        let range = range ???indices
        return range.indexOf{
            self.suffixFrom($0).startsWith(sub)
        }
    }
}

// Original Answer
func find_rangeOfString(str:String, s1:String, s2:String) - > String?{
    iflet
        tag1 = str.rangeOfString(s1),
        tag2 = str.rangeOfString(s2,range:tag1.endIndex..<str.endIndex){
            return String (str [tag1.endIndex..<tag2.startIndex])
    }
    return nil
}

// Search for standard libraries only in String.CharacterView
func find_characters(str:String, s1:String, s2:String) - > String?{
    lets1 = s1.characters
    lets2 = s2.characters
    let str = str.characters
    iflet
        start = str.startIndexOf(s1) ?.advancedBy(s1.count),
        end = str.startIndexOf(s2,range:start..<str.endIndex){
            return String (str [start..<end])
    }
    return nil
}

// Search for standard libraries only in String.UTF16View
func find_utf16(str:String, s1:String, s2:String) - > String?{
    lets1 = s1.utf16
    lets2 = s2.utf16
    let str = str.utf16
    iflet
        start = str.startIndexOf(s1) ?.advancedBy(s1.count),
        end = str.startIndexOf(s2,range:start..<str.endIndex){
            return String (str [start..<end])
    }
    return nil
}

// Search by NSScanner
func find_scanner(str:String, s1:String, s2:String) - > String?{
    let scanner = NSScanner (string:str)
    var result —NSString?
    scanner.scanUpToString(s1, intoString:nil)
    scanner.scanString(s1, intoString:nil)
    scanner.scanUpToString(s2, intoString:&result)
    return scanner.atEnd?nil:result as?String
}

// Search by NSRegularExpression
func find_regex(str:String, s1:String, s2:String) - > String?{
    letregex=try!NSRegularExpression(
        pattern —NSRegularExpression.escapedPatternForString(s1)
            + "(.*?)"
            + NSRegularExpression.escapedPatternForString(s2),
        options: [ ]
    )
    iflet range=regex.firstMatchInString(str, options:[], range:NSMakeRange(0,str.utf16.count)) ?.rangeAtIndex(1){
        return(stras NSSstring).substringWithRange(range)
    }
    return nil
}

class StringTestTests:XCTestCase{

    private func doTest(fn:(String, String, String) - > String?){
        letstr="My favorite food is <em> grilled fish </em>."
        let tag1 = "<em>"
        let tag2 = "</em>"
        XCTASertEqual (fn(str, tag1, tag2), "grilled fish")
        measureBlock{
            for_in0..<10000{
                fn(str, tag1, tag2)
            }
        }
    }

    functestRangeOfString() {doTest(find_rangeOfString)}
    functestCharacters() {doTest(find_characters)}
    functestUTF16() {doTest(find_utf16)}
    functestScanner() {doTest(find_scanner)}
    functestRegex() {doTest(find_regex)}
}

Result iPhone 6/iOS 9.1/Release Build (Same trend after multiple runs)

testRangeOfString—0.060sec
testCharacters: 0.453sec
testUTF16—0.020sec
testScanner: 0.144sec
testRegex —0.498sec

Of course, it depends on the quality of the original string, so one of them should not always be the fastest, but this is the result of this pattern.
It's obvious that the regular expression version is slow because it creates a pattern every time, but surprisingly, the find_utf16 is the fastest, and there's a big difference between using CharacterView and UTF16ViewI can't say what I should use because it depends on what I want to do and the nature of the original string, but there is a slight functional difference in this variation.

find_rangeOfString("My favorite food is 🇯🇵 grilled fish🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//-> "Grilled fish"
find_characters("My favorite food is 🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//->nil
find_utf16("My favorite food is 🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//-> "Grilled fish"
find_scanner("My favorite food is 🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//-> "Grilled fish"
find_regex("My favorite food is 🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//-> "Grilled fish"

find_rangeOfString("My favorite food is 🇯🇵🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//-> "🇯🇵 Grilled fish"
find_characters("My favorite food is 🇯🇵🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//->nil
find_utf16("My favorite food is 🇯🇵🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//->nil
find_scanner("My favorite food is 🇯🇵🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//-> "🇯🇵 Grilled fish"
find_regex("My favorite food is 🇯🇵🇯🇵 grilled fish 🇫🇷🇫🇷.", s1: "🇯🇵", s2: "🇫🇷")//-> "🇯🇵 Grilled fish"

The reason why this happens is that in CharacterView, continuous national flags Emoji are treated as a single character, but for more information https://stackoverflow.com/q/26862282/3804019


2022-09-30 21:11

Perhaps what the questioner wants to do is to use the NSScanner class for iOS/OS X.NSScanner class is mostly based on Objective-C even Apple documents, so it's hard to learn, but once you get used to it, you'll be able to parse with very few program codes.
Put the sample code that you want to run in Playground.

import UIKit

// Prepare HTML text for parsing.
lettmlString="<html>\n>head>><title>Title>/title>body>>>p>Hello, world.</p>>\n>p>hogehog>>>>>>>>>
// Created an NSScanner instance for htmlString.
let scanner = NSScanner (string:htmlString)
// Prepare a text to store the results.
var resultString=""
// Save scanned text temporarily.NSSstring and Optional.
US>vartmpString: NSSString?=""
// Scan started.Repeat until the end of the text arrives.
while scanner.atEnd==false{
    // Search <p> and stop scanning.
    scanner.scanUpToString("<p>", intoString:nil)
    // Read <p>.
    scanner.scanString("<p>", intoString:nil)
    // Go to </p> to get text between them.Note that there is an address operator &.
    scanner.scanUpToString("</p>", intoString:&tmpString)
    // Added tmpString to resultString.
    if let theString = tmpString as ? US>String{
        resultString+=theString
        // Added commas and spaces at the end.
        resultString+=","
    }
    // Initialize tmpString.
    tmpString=nil
}
print(resultString)

Results:

Hello, world., hoge hoge, fugafuga, foo foo, bar bar,

似た There is a similar class called NSXMLParser.XML HTHTML, so I think it can also be used to parse HTML, but if you use a single tag, <br>, <img>, etc., it doesn't work well and is not recommended (<br/>, ).


2022-09-30 21:11

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.