Jaki jest najbardziej zwięzły sposób usunięcia pierwszego znaku z ciągu w języku Swift?

122

Chcę usunąć pierwszy znak z ciągu. Jak dotąd najbardziej zwięzła rzecz, na którą wpadłem, to:

display.text = display.text!.substringFromIndex(advance(display.text!.startIndex, 1))

Wiem, że nie możemy indeksować do ciągu znaków z Intpowodu Unicode, ale to rozwiązanie wydaje się okropnie rozwlekłe. Czy jest inny sposób, który pomijam?

SSteve
źródło
3
Właściwie możesz tego uniknąć advance, przesyłając display.text!do NSString. Nie mówię, że to dobre rozwiązanie - po prostu skorygowanie możliwego nieporozumienia. Dzięki NSString możesz go indeksować za pomocą Int. - A powodem, dla którego nie możesz indeksować za pomocą Int, nie jest Unicode; dzieje się tak, ponieważ Postać może składać się z wielu złożonych punktów kodowych.
mat.
Jeśli robisz to w celu wykorzystania wielkich liter, Stringto Swift 3 wprowadził capitalizedfunkcję do String.
Vince O'Sullivan

Odpowiedzi:

208

Jeśli używasz Swift 3 , możesz zignorować drugą część tej odpowiedzi. Dobra wiadomość jest taka, że ​​teraz jest to znowu zwięzłe! Po prostu używając nowej metody remove (at :) Stringa.

var myString = "Hello, World"
myString.remove(at: myString.startIndex)

myString // "ello, World"

Podoba mi się dropFirst()funkcja globalna .

let original = "Hello" // Hello
let sliced = dropFirst(original) // ello

Jest krótki, przejrzysty i działa w przypadku wszystkiego, co jest zgodne z protokołem Sliceable.

Jeśli używasz Swift 2 , ta odpowiedź uległa zmianie. Nadal można używać dropFirst, ale nie bez usunięcia pierwszego znaku z characterswłaściwości ciągów, a następnie przekonwertowania wyniku z powrotem na String. dropFirst stał się również metodą, a nie funkcją.

let original = "Hello" // Hello
let sliced = String(original.characters.dropFirst()) // ello

Inną alternatywą jest użycie funkcji sufiksu do łączenia łańcucha UTF16View. Oczywiście to również musi zostać później przekonwertowane z powrotem na String.

let original = "Hello" // Hello
let sliced = String(suffix(original.utf16, original.utf16.count - 1)) // ello

Wszystko po to, aby powiedzieć, że rozwiązanie, które pierwotnie zapewniłem, okazało się nie być najbardziej zwięzłym sposobem na zrobienie tego w nowszych wersjach Swifta. removeAtIndex()Jeśli szukasz krótkiego i intuicyjnego rozwiązania, polecam skorzystać z rozwiązania @chris .

var original = "Hello" // Hello
let removedChar = original.removeAtIndex(original.startIndex)

original // ello

Jak wskazał @vacawama w komentarzach poniżej, kolejną opcją, która nie modyfikuje oryginalnego ciągu, jest użycie substringFromIndex.

let original = "Hello" // Hello
let substring = original.substringFromIndex(advance(original.startIndex, 1)) // ello

Lub jeśli chcesz usunąć znak z początku i końca ciągu, możesz użyć substringWithRange. Tylko pamiętaj, aby chronić się przed stanem, kiedy startIndex + n > endIndex - m.

let original = "Hello" // Hello

let newStartIndex = advance(original.startIndex, 1)
let newEndIndex = advance(original.endIndex, -1)

let substring = original.substringWithRange(newStartIndex..<newEndIndex) // ell

Ostatni wiersz można również zapisać za pomocą notacji z indeksem dolnym.

let substring = original[newStartIndex..<newEndIndex]
Mick MacCallum
źródło
2
I nie wymaga od Ciebie wkraczania w świat Fundacji.
mat.
2
I to jest bardzo szybkie / fp.
David Berry,
Dzięki, to dużo bardziej eleganckie. Od lat programuję w Objective C i biorę udział w kursie programowania iTunes U Stanford iOS w języku Swift. Nadal muszę się wiele nauczyć o nowym paradygmacie.
SSteve
1
Pamiętaj, że używając dropLast lub dropFirst, rozpakuj ciąg, w przeciwnym razie nie zadziała.
Bhavuk Jain
3
Świetna odpowiedź! Myślę, że warto zwrócić uwagę na wartość zwracaną remove(at:)metody: jest to usunięty znak, a nie nowy ciąg. Na przykład let removedCharacter = myString.remove(at: myString.startIndex). Popełniłem błąd zwracając wynik wywołania metody, zapominając, że tak było w tym podejściu. Miłego kodowania!
kbpontius
119

Aktualizacja dla Swift 4

W Swift 4 Stringdostosowuje się Collectionponownie, dzięki czemu można używać dropFirsti dropLastprzycinać początki i końce strun. Wynik jest typu Substring, więc musisz przekazać go Stringkonstruktorowi, aby otrzymać String:

let str = "hello"
let result1 = String(str.dropFirst())    // "ello"
let result2 = String(str.dropLast())     // "hell"

dropFirst()a dropLast()także wybierz, Intaby określić liczbę znaków do upuszczenia:

let result3 = String(str.dropLast(3))    // "he"
let result4 = String(str.dropFirst(4))   // "o"

Jeśli określisz więcej znaków do usunięcia niż w ciągu, wynikiem będzie pusty ciąg ( "").

let result5 = String(str.dropFirst(10))  // ""

Aktualizacja dla Swift 3

Jeśli chcesz tylko usunąć pierwszy znak i chcesz zmienić oryginalny ciąg w miejscu, zobacz odpowiedź @ MickMacCallum. Jeśli chcesz w tym procesie utworzyć nowy ciąg, użyj substring(from:). Dzięki rozszerzeniu do Stringmożesz ukryć brzydotę substring(from:)i substring(to:)stworzyć przydatne dodatki, aby przyciąć początek i koniec String:

extension String {
    func chopPrefix(_ count: Int = 1) -> String {
        return substring(from: index(startIndex, offsetBy: count))
    }

    func chopSuffix(_ count: Int = 1) -> String {
        return substring(to: index(endIndex, offsetBy: -count))
    }
}

"hello".chopPrefix()    // "ello"
"hello".chopPrefix(3)   // "lo"

"hello".chopSuffix()    // "hell"
"hello".chopSuffix(3)   // "he"

Podobnie jak dropFirsti dropLastwcześniej, te funkcje ulegną awarii, jeśli w ciągu znaków nie ma wystarczającej liczby liter. Na dzwoniącym spoczywa obowiązek prawidłowego korzystania z nich. To jest ważna decyzja projektowa. Można by napisać je tak, aby zwracały opcjonalne, które następnie musiałyby zostać rozpakowane przez wywołującego.


Swift 2.x

Niestety w Swift 2 , dropFirsti dropLast(poprzednia najlepsza roztworu) nie jest tak wygodna, jak były poprzednio. Dzięki rozszerzeniu do Stringmożesz ukryć brzydotę substringFromIndexi substringToIndex:

extension String {
    func chopPrefix(count: Int = 1) -> String {
         return self.substringFromIndex(advance(self.startIndex, count))
    }

    func chopSuffix(count: Int = 1) -> String {
        return self.substringToIndex(advance(self.endIndex, -count))
    }
}

"hello".chopPrefix()    // "ello"
"hello".chopPrefix(3)   // "lo"

"hello".chopSuffix()    // "hell"
"hello".chopSuffix(3)   // "he"

Podobnie jak dropFirsti dropLastwcześniej, te funkcje ulegną awarii, jeśli w ciągu znaków nie ma wystarczającej liczby liter. Na dzwoniącym spoczywa obowiązek prawidłowego korzystania z nich. To jest ważna decyzja projektowa. Można by napisać je tak, aby zwracały opcjonalne, które następnie musiałyby zostać rozpakowane przez wywołującego.


W Swift 1.2 musisz zadzwonić w chopPrefixten sposób:

"hello".chopPrefix(count: 3)  // "lo"

lub możesz dodać podkreślenie _do definicji funkcji, aby pominąć nazwę parametru:

extension String {
    func chopPrefix(_ count: Int = 1) -> String {
         return self.substringFromIndex(advance(self.startIndex, count))
    }

    func chopSuffix(_ count: Int = 1) -> String {
        return self.substringToIndex(advance(self.endIndex, -count))
    }
}
vacawama
źródło
3
Nadal jestem pod wrażeniem tego, jak skomplikowana jest wbudowana manipulacja ciągami w Swift. Mam na myśli to, że to podstawowe, podstawowe rzeczy; nie powinno wyglądać na to, że implementuję parser. Dzięki za rozszerzenie, aby to uprościć. To zaskakujące, że Apple nie dodałby tego po prostu do klasy String! Może wciąż się zmienia.
devios1
To jest to, co nazywają dziećmi „ewolucji”. Byłoby przezabawne, gdybyśmy nie musieli mieć z tym do czynienia każdego dnia. Wiem, że istnieją powody, dla których String API jest takie, jakie jest, ale naprawdę musi to odstraszyć tak wielu potencjalnych konwerterów na ten język. Poprzedni komentator ma absolutną rację - to powinno być podstawowe, podstawowe rzeczy.
Quintin Willison
17

Swift 2.2

„zaliczka” jest niedostępna: wywołaj metodę „advancedBy (n)” na indeksie

    func chopPrefix(count: Int = 1) -> String {
        return self.substringFromIndex(self.startIndex.advancedBy(count))
    }

    func chopSuffix(count: Int = 1) -> String {
        return self.substringFromIndex(self.endIndex.advancedBy(count))
    }

Swift 3.0

    func chopPrefix(_ count: Int = 1) -> String {
        return self.substring(from: self.characters.index(self.startIndex, offsetBy: count))
    }

    func chopSuffix(_ count: Int = 1) -> String {
       return self.substring(to: self.characters.index(self.endIndex, offsetBy: -count))
    }

Swift 3.2

Widok zawartości ciągu jako zbioru znaków.

@available(swift, deprecated: 3.2, message: "Please use String or Substring directly")
public var characters: String.CharacterView
func chopPrefix(_ count: Int = 1) -> String {
    if count >= 0 && count <= self.count {
        return self.substring(from: String.Index(encodedOffset: count))
    }
    return ""
}

func chopSuffix(_ count: Int = 1) -> String {
    if count >= 0 && count <= self.count {
        return self.substring(to: String.Index(encodedOffset: self.count - count))
    }
    return ""
}

Szybki 4

extension String {

    func chopPrefix(_ count: Int = 1) -> String {
        if count >= 0 && count <= self.count {
            let indexStartOfText = self.index(self.startIndex, offsetBy: count)
            return String(self[indexStartOfText...])
        }
        return ""
    }

    func chopSuffix(_ count: Int = 1) -> String {
        if count >= 0 && count <= self.count {
            let indexEndOfText = self.index(self.endIndex, offsetBy: -count)
            return String(self[..<indexEndOfText])
        }
        return ""
    }
}
Mohamed Jaleel Nazir
źródło
1
myślę, że zapomniałeś dodać negację -count do funkcji chopSuffix
Andy
10

Zależy od tego, jaki ma być efekt końcowy (mutacja vs brak mutacji).

Od wersji Swift 4.1:

Mutowanie:

var str = "hello"
str.removeFirst() // changes str 

Bez mutacji:

let str = "hello"
let strSlice = str.dropFirst() // makes a slice without the first letter
let str2 = String(strSlice)

Uwagi:

  • Postawiłem dodatkowy krok w nonmutatingprzykładzie, aby zwiększyć jasność. Subiektywnie połączenie dwóch ostatnich kroków byłoby bardziej zwięzłe.
  • Nazewnictwo dropFirstwydaje mi się nieco dziwne, ponieważ jeśli dobrze rozumiem Wytyczne dotyczące projektowania interfejsu API Swift , dropFirstpowinno być takie, dropingFirstponieważ nie powoduje mutacji. Tylko myśl :).
jason z
źródło
1
rzeczywiście, powinno być droppingFirst- zawiedli!
Fattie
6

A co z tym?

s.removeAtIndex(s.startIndex)

To oczywiście zakłada, że ​​twój ciąg jest zmienny. Zwraca znak, który został usunięty, ale zmienia oryginalny ciąg.

chris
źródło
6

Poprzednie odpowiedzi są całkiem dobre, ale na dzień dzisiejszy myślę, że może to być najbardziej zwięzły sposób na usunięcie pierwszego znaku z ciągu w Swift 4 :

var line: String = "This is a string..."
var char: Character? = nil

char = line.removeFirst()

print("char = \(char)")  // char = T
print("line = \(line)")  // line = his is a string ...
ByteSlinger
źródło
1

Nie znam nic bardziej zwięzłego po wyjęciu z pudełka, ale możesz łatwo zaimplementować prefiks ++, np.

public prefix func ++ <I: ForwardIndexType>(index: I) -> I {
    return advance(index, 1)
}

Po czym możesz bardzo zwięźle wykorzystać to do syta:

str.substringFromIndex(++str.startIndex)
Gregory Higley
źródło
1

W Swift 2 użyj tego rozszerzenia String:

extension String
{
    func substringFromIndex(index: Int) -> String
    {
        if (index < 0 || index > self.characters.count)
        {
            print("index \(index) out of bounds")
            return ""
        }
        return self.substringFromIndex(self.startIndex.advancedBy(index))
    }
}

display.text = display.text!.substringFromIndex(1)
ChikabuZ
źródło
1

"en_US, fr_CA, es_US" .chopSuffix (5) .chopPrefix (5) // ", fr_CA,"

extension String {
    func chopPrefix(count: Int = 1) -> String {
        return self.substringFromIndex(self.startIndex.advancedBy(count))
    }

    func chopSuffix(count: Int = 1) -> String {
        return self.substringToIndex(self.endIndex.advancedBy(-count))
    }
}
Andy
źródło
0

Aby usunąć pierwszy znak z ciągu

let choppedString = String(txtField.text!.characters.dropFirst())
Hardik Thakkar
źródło
@JasonFoglia Nie wiem, czy oddajesz głos, czy nie. jeśli oddasz głos, czy możesz szczegółowo wyjaśnić.
Hardik Thakkar
Ponownie sprawdziłem to i stwierdziłem, że jest poprawne. Zredagowałem odpowiedź, aby móc odpowiednio zagłosować.
Jason Foglia
0

Oto wersja rozszerzenia do zapisywania awarii Swift4chopPrefix , pozostawiająca chopSuffixspołeczności ...

extension String {
    func chopPrefix(_ count: Int = 1) -> String {
        return count>self.count ? self : String(self[index(self.startIndex, offsetBy: count)...])
    }
 }
iDoc
źródło
0

Swift3

extension String {
    func chopPrefix(_ count: UInt = 1) -> String {
        return substring(from: characters.index(startIndex, offsetBy: Int(count)))
    }

    func chopSuffix(_ count: UInt = 1) -> String {
        return substring(to: characters.index(endIndex, offsetBy: -Int(count)))
    }
}

class StringChopTests: XCTestCase {
    func testPrefix() {
        XCTAssertEqual("original".chopPrefix(0), "original")
        XCTAssertEqual("Xfile".chopPrefix(), "file")
        XCTAssertEqual("filename.jpg".chopPrefix(4), "name.jpg")
    }

    func testSuffix() {
        XCTAssertEqual("original".chopSuffix(0), "original")
        XCTAssertEqual("fileX".chopSuffix(), "file")
        XCTAssertEqual("filename.jpg".chopSuffix(4), "filename")
    }
}
neoneye
źródło
Myślę, że powinieneś dodać test, w którym wejście ma wartość ujemną
Moustafa Baalbaki