Swift String'de karakter dizini bulma


203

Yenilgiyi kabul etme zamanı ...

Objective-C'de şöyle bir şey kullanabilirim:

NSString* str = @"abcdefghi";
[str rangeOfString:@"c"].location; // 2

Swift'te benzer bir şey görüyorum:

var str = "abcdefghi"
str.rangeOfString("c").startIndex

... ama bu sadece String.Indexorijinal dizgeye geri abone olmak için kullanabileceğim, ancak bir konumdan ayıklayamadığım bir a veriyor .

FWIW, içinde doğru değeri olan String.Indexözel bir ivar'a _positionsahip. Sadece nasıl açığa çıktığını görmüyorum.

Kendimi kolayca String bu ekleyebilirsiniz biliyorum. Bu yeni API'da neyi kaçırdığımı daha çok merak ediyorum.


İşte Swift dize manipülasyonu için birçok uzantı yöntemi içeren bir GitHub projesi: github.com/iamjono/SwiftString
RenniePet 14:16 '

Bulduğum en iyi uygulama burada: stackoverflow.com/a/32306142/4550651
Carlos García

Unicode Kod Noktaları ve Genişletilmiş Grafik Kümeleri arasında ayrım yapmanız mı gerekiyor?
Ben Leggiero

Yanıtlar:


248

Çözümü bulamayan tek kişi sen değilsin.

Stringuygulanmaz RandomAccessIndexType. Muhtemelen farklı bayt uzunluklarına sahip karakterleri etkinleştirdikleri için. Bu yüzden karakter sayısını elde etmek için kullanmalıyız string.characters.count( countveya countElementsSwift 1.x'te). Bu durum pozisyonlar için de geçerlidir. _positionMuhtemelen bayt ham diziye bir endeksidir ve onlar maruz istemiyorum. Bu String.Index, bizi karakterlerin ortasında baytlara erişmekten korumak içindir.

Bu, aldığınız herhangi bir dizinin String.startIndexveya String.endIndex( String.Indexuygular BidirectionalIndexType) dizininden oluşturulması gerektiği anlamına gelir . Diğer indeksler successorveya predecessoryöntemler kullanılarak oluşturulabilir .

Şimdi endekslerde bize yardımcı olmak için bir dizi yöntem var (Swift 1.x'teki fonksiyonlar):

Swift 4.x

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.index(text.startIndex, offsetBy: 2)
let lastChar2 = text[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 3.0

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.characters.index(text.characters.startIndex, offsetBy: 2)
let lastChar2 = text.characters[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 2.x

let text = "abc"
let index2 = text.startIndex.advancedBy(2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!
let lastChar2 = text.characters[index2] //will do the same as above

let range: Range<String.Index> = text.rangeOfString("b")!
let index: Int = text.startIndex.distanceTo(range.startIndex) //will call successor/predecessor several times until the indices match

Swift 1.x

let text = "abc"
let index2 = advance(text.startIndex, 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let range = text.rangeOfString("b")
let index: Int = distance(text.startIndex, range.startIndex) //will call succ/pred several times

Gerçek endekslemenin verimsizliğini gizlediği için birlikte çalışmak String.Indexhantaldır, ancak tamsayılarla dizin oluşturmak için bir sarıcı kullanmak (bkz. Https://stackoverflow.com/a/25152652/669586 ) tehlikelidir.

Swift dizin oluşturma uygulamasında, bir dize için oluşturulan dizinlerin / aralıkların farklı bir dize için güvenilir bir şekilde kullanılamadığı sorunu olduğunu unutmayın, örneğin:

Swift 2.x

let text: String = "abc"
let text2: String = "🎾🏇🏈"

let range = text.rangeOfString("b")!

//can randomly return a bad substring or throw an exception
let substring: String = text2[range]

//the correct solution
let intIndex: Int = text.startIndex.distanceTo(range.startIndex)
let startIndex2 = text2.startIndex.advancedBy(intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]

Swift 1.x

let text: String = "abc"
let text2: String = "🎾🏇🏈"

let range = text.rangeOfString("b")

//can randomly return nil or a bad substring 
let substring: String = text2[range] 

//the correct solution
let intIndex: Int = distance(text.startIndex, range.startIndex)    
let startIndex2 = advance(text2.startIndex, intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]  

1
Garip olabileceğini düşündüm, cevap bu gibi görünüyor. Umarım bu iki aralık işlevi, son sürümden bir süre önce belgelere girer.
Matt Wilding

Ne tür bir rangeyervar range = text.rangeOfString("b")
zaph

5
@Zaph Herkesin Collectionbir typealias IndexType. Diziler için, olarak tanımlanır Int, Stringçünkü olarak tanımlanır String.Index. Hem diziler hem de dizeler de aralıkları kullanabilir (alt diziler ve alt dizeler oluşturmak için). Aralık özel bir tiptir Range<T>. Dizeler Range<String.Index>için, diziler için Range<Int>.
Sulthan

1
In Swift 2.0, distance(text.startIndex, range.startIndex)olurtext.startIndex.distanceTo(range.startIndex)
superarts.org

1
@devios String, tıpkı NSStringVakıf'ta olduğu gibi bir yöntem var hasPrefix(_:).
Sulthan

86

Swift 3.0 bunu biraz daha ayrıntılı hale getiriyor:

let string = "Hello.World"
let needle: Character = "."
if let idx = string.characters.index(of: needle) {
    let pos = string.characters.distance(from: string.startIndex, to: idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

Uzantı:

extension String {
    public func index(of char: Character) -> Int? {
        if let idx = characters.index(of: char) {
            return characters.distance(from: startIndex, to: idx)
        }
        return nil
    }
}

In Swift 2.0 bu daha kolay hale gelmiştir:

let string = "Hello.World"
let needle: Character = "."
if let idx = string.characters.indexOf(needle) {
    let pos = string.startIndex.distanceTo(idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

Uzantı:

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = self.characters.indexOf(char) {
            return self.startIndex.distanceTo(idx)
        }
        return nil
    }
}

Swift 1.x uygulaması:

Saf bir Swift çözümü için aşağıdakiler kullanılabilir:

let string = "Hello.World"
let needle: Character = "."
if let idx = find(string, needle) {
    let pos = distance(string.startIndex, idx)
    println("Found \(needle) at position \(pos)")
}
else {
    println("Not found")
}

Bir uzantısı olarak String:

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = find(self, char) {
            return distance(self.startIndex, idx)
        }
        return nil
    }
}

1
karakter kullanımdan kaldırıldı !!
Shivam Pokhriyal

23
extension String {

    // MARK: - sub String
    func substringToIndex(index:Int) -> String {
        return self.substringToIndex(advance(self.startIndex, index))
    }
    func substringFromIndex(index:Int) -> String {
        return self.substringFromIndex(advance(self.startIndex, index))
    }
    func substringWithRange(range:Range<Int>) -> String {
        let start = advance(self.startIndex, range.startIndex)
        let end = advance(self.startIndex, range.endIndex)
        return self.substringWithRange(start..<end)
    }

    subscript(index:Int) -> Character{
        return self[advance(self.startIndex, index)]
    }
    subscript(range:Range<Int>) -> String {
        let start = advance(self.startIndex, range.startIndex)
            let end = advance(self.startIndex, range.endIndex)
            return self[start..<end]
    }


    // MARK: - replace
    func replaceCharactersInRange(range:Range<Int>, withString: String!) -> String {
        var result:NSMutableString = NSMutableString(string: self)
        result.replaceCharactersInRange(NSRange(range), withString: withString)
        return result
    }
}

7
Bunu yapmayı düşündüm, ancak dize erişiminin semantiğini gizlemesinin bir sorun olduğunu düşünüyorum. Bir dizinin API'sine benzeyen bağlantılı listelere erişmek için bir API oluşturduğunuzu düşünün. İnsanlar korkunç verimsiz kodlar yazmak istiyorlar.
Erik Engheim

16

Ben swift2 için bu çözümü bulduk:

var str = "abcdefghi"
let indexForCharacterInString = str.characters.indexOf("c") //returns 2

str = "abcdefgchi" olduğunda dizin ne olacak
Shukla

10

Swift 5.0

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return firstIndex(of: char)?.utf16Offset(in: self)
  }
}

Swift 4.0

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return index(of: char)?.encodedOffset        
  }
}

dönüş dizini (/: element) .map {target.distance (başlangıç: startIndex, - 0 $)}
frogcjn

8

String.Index konumundan konumu nasıl ayıklayacağınızdan emin değilim, ancak bazı Objective-C çerçevelerine geri düşmek istiyorsanız, obj-c'ye köprü oluşturabilir ve bunu eskisi gibi yapabilirsiniz.

"abcdefghi".bridgeToObjectiveC().rangeOfString("c").location

Bazı NSString yöntemleri henüz String'e taşınmamış (veya belki de olmayacak) gibi görünüyor. İçerir akla.


Aslında, derleyicinin bir NSString türünü çıkarması için dönüş değerinin location özelliğine erişmenin yeterli olduğu görülüyor, bu nedenle bridgeToObjectiveC()çağrı gerekli değil. Benim sorunum sadece rangeOfStringönceden var olan Swift String'i çağırırken ortaya çıkıyor gibi görünüyor . Bir API sorunu gibi görünüyor ...
Matt Wilding

ilginç. Bu durumlarda ortaya çıktığını bilmiyordum. Zaten bir String olduğunda köprüyü her zaman kullanabilirsiniz.
Connor

8

İşte soruyu cevaplayan temiz bir String uzantısı:

Hızlı 3:

extension String {
    var length:Int {
        return self.characters.count
    }

    func indexOf(target: String) -> Int? {

        let range = (self as NSString).range(of: target)

        guard range.toRange() != nil else {
            return nil
        }

        return range.location

    }
    func lastIndexOf(target: String) -> Int? {



        let range = (self as NSString).range(of: target, options: NSString.CompareOptions.backwards)

        guard range.toRange() != nil else {
            return nil
        }

        return self.length - range.location - 1

    }
    func contains(s: String) -> Bool {
        return (self.range(of: s) != nil) ? true : false
    }
}

Hızlı 2.2:

extension String {    
    var length:Int {
        return self.characters.count
    }

    func indexOf(target: String) -> Int? {

        let range = (self as NSString).rangeOfString(target)

        guard range.toRange() != nil else {
            return nil
        }

        return range.location

    }
    func lastIndexOf(target: String) -> Int? {



        let range = (self as NSString).rangeOfString(target, options: NSStringCompareOptions.BackwardsSearch)

        guard range.toRange() != nil else {
            return nil
        }

        return self.length - range.location - 1

    }
    func contains(s: String) -> Bool {
        return (self.rangeOfString(s) != nil) ? true : false
    }
}

7

Ayrıca, bir karakterin dizinlerini şöyle tek bir dizede bulabilirsiniz,

extension String {

  func indexes(of character: String) -> [Int] {

    precondition(character.count == 1, "Must be single character")

    return self.enumerated().reduce([]) { partial, element  in
      if String(element.element) == character {
        return partial + [element.offset]
      }
      return partial
    }
  }

}

Hangi sonucu [String.Distance] yani verir. [Int], gibi

"apple".indexes(of: "p") // [1, 2]
"element".indexes(of: "e") // [0, 2, 4]
"swift".indexes(of: "j") // []

5

Tanıdık bir NSString kullanmak istiyorsanız, bunu açıkça bildirebilirsiniz:

var someString: NSString = "abcdefghi"

var someRange: NSRange = someString.rangeOfString("c")

Swift'te bunu nasıl yapacağımdan henüz emin değilim.


1
Bu kesinlikle işe yarıyor ve derleyici sizin için NSString türlerini çıkarmada oldukça agresif görünüyor. Bunu yapmanın gerçekten hızlı bir yolunu umuyordum, çünkü yeterince yaygın bir kullanım durumu gibi görünüyor.
Matt Wilding

Evet, etrafa bakıyorum, ama görmüyorum. ObjC tarafından desteklenmeyen alanlara odaklanmış olmaları mümkündür, çünkü bu boşlukları çok fazla yetenek kaybetmeden doldurabilirler. Sadece yüksek sesle düşünmek :)
Logan

4

Bir dizedeki bir karakterin int değeri olarak konumunu bilmek istiyorsanız bunu kullanın:

let loc = newString.range(of: ".").location

Swift 5: value of type 'Range<String.Index>?' has no member 'location'.
Neph

3

Bu eski ve bir cevap kabul edildi biliyorum, ama kullanarak kod birkaç satırda dizenin dizin bulabilirsiniz:

var str : String = "abcdefghi"
let characterToFind: Character = "c"
let characterIndex = find(str, characterToFind)  //returns 2

Swift dizeleri hakkında diğer bazı büyük bilgiler Burada Dizeler Swift


findSwift 3
AamirR'de

2

Bu benim için çalıştı,

var loc = "abcdefghi".rangeOfString("c").location
NSLog("%d", loc);

bu da işe yaradı,

var myRange: NSRange = "abcdefghi".rangeOfString("c")
var loc = myRange.location
NSLog("%d", loc);

Bunların her ikisi de NSString'in davranışına bir şekilde geri geliyor gibi görünüyor. Oyun alanımda, dize için bir ara değişken kullanıyordum. var str = "abcdef"; str.rangeOfString("c").locationString.Index'in location adlı bir üyesi olmadığı konusunda bir hata ortaya çıkıyor ...
Matt Wilding

1
Sadece "string" NSString olduğu ve Swift'in
Stringi

2

Swift'teki değişken türü Dize, Objective-C'deki NSString ile karşılaştırıldığında farklı işlevler içeriyor. Ve Sulthan'ın belirttiği gibi,

Swift String RandomAccessIndex'i uygulamıyor

Yapabileceğiniz şey String türündeki değişkeninizi NSString'e küçültmektir (bu, Swift'te geçerlidir). Bu, NSString'deki işlevlere erişmenizi sağlar.

var str = "abcdefghi" as NSString
str.rangeOfString("c").locationx   // returns 2

2

Bunu düşünürseniz, aslında konumun tam Int sürümüne gerçekten ihtiyacınız yoktur. Range veya hatta String.Index, gerekirse alt dizeyi yeniden çıkarmak için yeterlidir:

let myString = "hello"

let rangeOfE = myString.rangeOfString("e")

if let rangeOfE = rangeOfE {
    myString.substringWithRange(rangeOfE) // e
    myString[rangeOfE] // e

    // if you do want to create your own range
    // you can keep the index as a String.Index type
    let index = rangeOfE.startIndex
    myString.substringWithRange(Range<String.Index>(start: index, end: advance(index, 1))) // e

    // if you really really need the 
    // Int version of the index:
    let numericIndex = distance(index, advance(index, 1)) // 1 (type Int)
}

Benim için en iyi cevap func indexOf (target: String) -> Int {return (NSString olarak self) .rangeOfString (target)
.location

2

En Basit Yol:

In Swift 3 :

 var textViewString:String = "HelloWorld2016"
    guard let index = textViewString.characters.index(of: "W") else { return }
    let mentionPosition = textViewString.distance(from: index, to: textViewString.endIndex)
    print(mentionPosition)

1

Dize, NSString için bir köprü türüdür, bu nedenle

import Cocoa

ve tüm "eski" yöntemleri kullanın.


1

Düşünme açısından buna DÖNÜŞ adı verilebilir. Dünyanın düz yerine yuvarlak olduğunu keşfediyorsunuz. "Onunla bir şeyler yapmak için karakterin INDEX'ini bilmenize gerek yok." Ve bir C programcısı olarak da almamı zor buldum! Satırınız "let index = letters.characters.indexOf (" c ")!" tek başına yeterli. Örneğin, kullanabileceğiniz c'yi kaldırmak için ... (oyun alanı macunu)

    var letters = "abcdefg"
  //let index = letters.rangeOfString("c")!.startIndex //is the same as
    let index = letters.characters.indexOf("c")!
    range = letters.characters.indexOf("c")!...letters.characters.indexOf("c")!
    letters.removeRange(range)
    letters

Ancak, bir indeks istiyorsanız, Int değeri olarak Int yerine gerçek bir INDEX döndürmeniz gerekir, herhangi bir pratik kullanım için ek adımlar gerektirir. Bu uzantılar bir dizin, belirli bir karakter sayısı ve bu oyun alanı eklentisi kodunun göstereceği bir aralık döndürür.

extension String
{
    public func firstIndexOfCharacter(aCharacter: Character) -> String.CharacterView.Index? {

        for index in self.characters.indices {
            if self[index] == aCharacter {
                return index
            }

        }
        return nil
    }

    public func returnCountOfThisCharacterInString(aCharacter: Character) -> Int? {

        var count = 0
        for letters in self.characters{

            if aCharacter == letters{

                count++
            }
        }
        return count
    }


    public func rangeToCharacterFromStart(aCharacter: Character) -> Range<Index>? {

        for index in self.characters.indices {
            if self[index] == aCharacter {
                let range = self.startIndex...index
                return range
            }

        }
        return nil
    }

}



var MyLittleString = "MyVery:important String"

var theIndex = MyLittleString.firstIndexOfCharacter(":")

var countOfColons = MyLittleString.returnCountOfThisCharacterInString(":")

var theCharacterAtIndex:Character = MyLittleString[theIndex!]

var theRange = MyLittleString.rangeToCharacterFromStart(":")
MyLittleString.removeRange(theRange!)

1

Swift 4 Komple Çözüm:

OffsetIndexableCollection (Int Dizini kullanan dize)

https://github.com/frogcjn/OffsetIndexableCollection-String-Int-Indexable-

let a = "01234"

print(a[0]) // 0
print(a[0...4]) // 01234
print(a[...]) // 01234

print(a[..<2]) // 01
print(a[...2]) // 012
print(a[2...]) // 234
print(a[2...3]) // 23
print(a[2...2]) // 2

if let number = a.index(of: "1") {
    print(number) // 1
    print(a[number...]) // 1234
}

if let number = a.index(where: { $0 > "1" }) {
    print(number) // 2
}

1

uzantı Dizesi {

//Fucntion to get the index of a particular string
func index(of target: String) -> Int? {
    if let range = self.range(of: target) {
        return characters.distance(from: startIndex, to: range.lowerBound)
    } else {
        return nil
    }
}
//Fucntion to get the last index of occurence of a given string
func lastIndex(of target: String) -> Int? {
    if let range = self.range(of: target, options: .backwards) {
        return characters.distance(from: startIndex, to: range.lowerBound)
    } else {
        return nil
    }
}

}


1

Hızlı 5

Alt dize dizinini bulun

let str = "abcdecd"
if let range: Range<String.Index> = str.range(of: "cd") {
    let index: Int = str.distance(from: str.startIndex, to: range.lowerBound)
    print("index: ", index) //index: 2
}
else {
    print("substring not found")
}

Karakterin dizinini bul

let str = "abcdecd"
if let firstIndex = str.firstIndex(of: "c") {
    let index: Int = str.distance(from: str.startIndex, to: firstIndex)
    print("index: ", index)   //index: 2
}
else {
    print("symbol not found")
}


0

Swift 2 ile bir dizede bir alt dizenin dizinini almak için:

let text = "abc"
if let range = text.rangeOfString("b") {
   var index: Int = text.startIndex.distanceTo(range.startIndex) 
   ...
}

0

Hızlı 2.0'da

var stringMe="Something In this.World"
var needle="."
if let idx = stringMe.characters.indexOf(needle) {
    let pos=stringMe.substringFromIndex(idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

0
let mystring:String = "indeep";
let findCharacter:Character = "d";

if (mystring.characters.contains(findCharacter))
{
    let position = mystring.characters.indexOf(findCharacter);
    NSLog("Position of c is \(mystring.startIndex.distanceTo(position!))")

}
else
{
    NSLog("Position of c is not found");
}

0

Aşağıdaki ile oynarım

extension String {
    func allCharactes() -> [Character] {
         var result: [Character] = []
         for c in self.characters {
             result.append(c)
         }
         return 
    }
}

sağlanan kişinin şimdi sadece Karakter dizisi olduğunu anlayana kadar

Ve birlikte

let c = Array(str.characters)

Bu neyi başarıyor? İlk kod bloğunuz ikinciden nasıl daha iyi?
Ben Leggiero

0

Sadece bir karakterin indeksine ihtiyacınız varsa (Pascal tarafından zaten belirtildiği gibi) en basit, hızlı çözüm:

let index = string.characters.index(of: ".")
let intIndex = string.distance(from: string.startIndex, to: index)

Karakterler şimdi amortismana tabi
tutuldu

0

A'yı String.Indexan biçimine dönüştürme konusunda Intbu uzantı benim için çalışıyor:

public extension Int {
    /// Creates an `Int` from a given index in a given string
    ///
    /// - Parameters:
    ///    - index:  The index to convert to an `Int`
    ///    - string: The string from which `index` came
    init(_ index: String.Index, in string: String) {
        self.init(string.distance(from: string.startIndex, to: index))
    }
}

Bu soru ile ilgili örnek kullanım:

var testString = "abcdefg"

Int(testString.range(of: "c")!.lowerBound, in: testString)     // 2

testString = "🇨🇦🇺🇸🇩🇪👩‍👩‍👧‍👦\u{1112}\u{1161}\u{11AB}"

Int(testString.range(of: "🇨🇦🇺🇸🇩🇪")!.lowerBound, in: testString) // 0
Int(testString.range(of: "👩‍👩‍👧‍👦")!.lowerBound, in: testString)    // 1
Int(testString.range(of: "한")!.lowerBound, in: testString)    // 5

Önemli:

Anlayacağınız gibi, genişletilmiş grafik kümelerini gruplandırır ve karakterleri farklı şekilde birleştirir String.Index. Tabii ki, bu yüzden var String.Index. Bu yöntemin kümeleri düzeltmeye daha yakın olan tekil karakterler olarak gördüğünü unutmamalısınız. Amacınız bir dizeyi Unicode kod noktasına bölmekse, bu sizin için bir çözüm değildir.


0

In Swift 2.0 , aşağıdaki fonksiyonu verilen bir karakterin önüne bir alt döndürür.

func substring(before sub: String) -> String {
    if let range = self.rangeOfString(sub),
        let index: Int = self.startIndex.distanceTo(range.startIndex) {
        return sub_range(0, index)
    }
    return ""
}

0

Bir dizede bir karakterin dizin numarasını şu şekilde bulabilirsiniz:

var str = "abcdefghi"
if let index = str.firstIndex(of: "c") {
    let distance = str.distance(from: str.startIndex, to: index)
    // distance is 2
}

0

Benim bakış açımdan, mantığın kendisini tanımanın daha iyi yolu aşağıda

 let testStr: String = "I love my family if you Love us to tell us I'm with you"
 var newStr = ""
 let char:Character = "i"

 for value in testStr {
      if value == char {
         newStr = newStr + String(value)
   }

}
print(newStr.count)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.