Каков самый лаконичный способ удалить первый символ из строки в Swift?

122

Я хочу удалить первый символ из строки. Пока что самая лаконичная вещь, которую я придумал:

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

Я знаю, что мы не можем индексировать строку с помощью IntUnicode, но это решение кажется ужасно подробным. Есть ли другой способ, который я не замечаю?

SSteve
источник
3
На самом деле всего этого можно избежать advance, display.text!применив NSString. Я не говорю, что это хорошее решение - просто исправляю возможное заблуждение. С NSString вы можете индексировать его с помощью Int. - И причина, по которой вы не можете индексировать с помощью Int, не из-за Unicode; это потому, что символ может состоять из нескольких составных кодовых точек.
Мэтт
Если вы делаете это для того, чтобы использовать заглавные буквы, Stringтогда Swift 3 представил эту capitalizedфункцию для String.
Винс О'Салливан,

Ответы:

208

Если вы используете Swift 3 , вы можете проигнорировать второй раздел этого ответа. Хорошие новости в том, что теперь он снова стал лаконичным! Просто используя новый метод String remove (at :).

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

myString // "ello, World"

Мне нравится глобальная dropFirst()функция для этого.

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

Он короткий, понятный и подходит для всего, что соответствует протоколу Sliceable.

Если вы используете Swift 2 , этот ответ изменился. Вы по-прежнему можете использовать dropFirst, но не без удаления первого символа из charactersсвойства strings и последующего преобразования результата обратно в String. dropFirst также стал методом, а не функцией.

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

Другой альтернативой является использование функции суффикса для объединения строки UTF16View. Конечно, это также должно быть впоследствии преобразовано обратно в String.

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

Все это означает, что решение, которое я изначально предоставил, оказалось не самым лаконичным способом сделать это в новых версиях Swift. Я рекомендую использовать решение @chris, removeAtIndex()если вы ищете короткое и интуитивно понятное решение.

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

original // ello

И, как указывает @vacawama в комментариях ниже, другой вариант, который не изменяет исходную строку, - это использовать substringFromIndex.

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

Или, если вы хотите отбросить символ в начале и конце строки, вы можете использовать substringWithRange. Только обязательно остерегайтесь состояния, когда 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

Последняя строка также может быть записана с использованием нижних индексов.

let substring = original[newStartIndex..<newEndIndex]
Мик МакКаллум
источник
2
И это не требует, чтобы вы ступили в мир Основания.
Мэтт
2
И очень быстро / fp.
Дэвид Берри,
Спасибо, это намного элегантнее. Я занимаюсь программированием на Objective C в течение многих лет и беру курс программирования iTunes U Stanford iOS на Swift. Мне еще многое предстоит узнать о новой парадигме.
SSteve,
1
Помните, что при использовании dropLast или dropFirst разверните строку, иначе она не сработает.
Bhavuk Jain 04
3
Отличный ответ! Думаю, стоит отметить возвращаемое значение remove(at:)метода: это удаленный символ, а не новая строка. Например, let removedCharacter = myString.remove(at: myString.startIndex). Я совершал ошибку, возвращая результат вызова метода, забыв, что это имело место в данном подходе. Удачного всем кодирования!
kbpontius
119

Обновление для Swift 4

В Swift 4 снова Stringсоответствует Collection, поэтому можно использовать dropFirstи dropLastобрезать начало и конец строк. Результат имеет тип Substring, поэтому вам нужно передать его Stringконструктору, чтобы вернуть String:

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

dropFirst()а dropLast()также возьмите, Intчтобы указать количество отбрасываемых символов:

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

Если вы укажете для отбрасывания больше символов, чем указано в строке, результатом будет пустая строка ( "").

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

Обновление для Swift 3

Если вы просто хотите удалить первый символ и хотите заменить исходную строку, см. Ответ @MickMacCallum. Если вы хотите создать новую строку в процессе, используйте substring(from:). С помощью расширения Stringвы можете скрыть уродство substring(from:)и substring(to:)создать полезные дополнения для обрезки начала и конца 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"

Как dropFirstи dropLastдо них, эти функции будут аварийно завершены, если в строке будет недостаточно букв. Ответственность за правильное их использование лежит на вызывающем абоненте. Это верное дизайнерское решение. Можно написать их так, чтобы они возвращали необязательный параметр, который затем должен был быть развернут вызывающим.


Swift 2.x

Увы в Swift 2 , dropFirstи dropLast(предыдущий лучшее решение) не так удобно , как они были раньше. С помощью расширения на Stringможно скрыть уродство substringFromIndexи 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"

Как dropFirstи dropLastдо них, эти функции будут аварийно завершены, если в строке будет недостаточно букв. Ответственность за правильное их использование лежит на вызывающем абоненте. Это верное дизайнерское решение. Можно написать их так, чтобы они возвращали необязательный параметр, который затем должен был быть развернут вызывающим.


В Swift 1.2 вам нужно будет позвонить chopPrefixтак:

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

или вы можете добавить подчеркивание _к определениям функций, чтобы подавить имя параметра:

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
источник
3
Я до сих пор поражен тем, насколько сложны встроенные в Swift манипуляции со строками. Я имею в виду, что это базовые, базовые вещи; это не должно выглядеть так, как будто я реализую парсер. Спасибо за расширение, которое упростило это. Это сбивает с толку Apple не просто добавила бы это в класс String! Может быть, он все еще находится в постоянном движении.
devios1
Это то, что они называют «эволюционными» детьми. Было бы весело, если бы нам не приходилось сталкиваться с этим каждый день. Я знаю, что есть причины, по которым String API является таким, какой он есть, но на самом деле это должно отложить так много потенциальных преобразователей на этот язык. Предыдущий комментатор абсолютно прав - это должны быть базовые вещи.
Квинтин Уиллисон
17

Swift 2.2

'advance' недоступен: вызовите метод 'advancedBy (n)' для индекса

    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

Представление содержимого строки как набора символов.

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

Swift 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 ""
    }
}
Мохамед Джалил Назир
источник
1
я думаю, вы забыли добавить отрицание -count к функции chopSuffix
Энди
10

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

Начиная с Swift 4.1:

Минимальное видоизменение:

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

Nonmutating:

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

Ноты:

  • nonmutatingДля ясности я добавил дополнительный шаг в пример. Субъективно объединение двух последних шагов было бы более лаконичным.
  • Название dropFirstкажется мне немного странным, потому что, если я правильно понимаю Рекомендации по дизайну Swift API , dropFirstдействительно должно быть что-то вроде того, dropingFirstчто оно не изменяет. Просто мысль :).
Джейсон Зи
источник
1
действительно так и должно быть droppingFirst- они напортачили!
Fattie
6

Как насчет этого?

s.removeAtIndex(s.startIndex)

Это, конечно, предполагает, что ваша строка изменяема. Он возвращает символ, который был удален, но изменяет исходную строку.

Крис
источник
6

Предыдущие ответы довольно хороши, но на сегодняшний день я думаю, что это может быть наиболее лаконичный способ удалить первый символ из строки в 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
источник
1

Я не знаю ничего более лаконичного из коробки, но вы могли бы легко реализовать префикс ++, например,

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

После чего вы можете использовать его сколько душе угодно очень лаконично:

str.substringFromIndex(++str.startIndex)
Грегори Хигли
источник
1

В Swift 2 используйте это расширение 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
источник
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))
    }
}
Энди
источник
0

Чтобы удалить первый символ из строки

let choppedString = String(txtField.text!.characters.dropFirst())
Хардик Таккар
источник
@JasonFoglia, я не знаю, голосовали вы против или нет. если вы проголосуете "против", не могли бы вы объяснить подробно.
Hardik Thakkar
Я перепроверил это и обнаружил, что это правильно. Я отредактировал ответ, чтобы правильно проголосовать за него.
Джейсон Фолья
0

Вот версия расширения Swift4 с аварийным сохранением chopPrefix, оставляющая chopSuffixсообществу ...

extension String {
    func chopPrefix(_ count: Int = 1) -> String {
        return count>self.count ? self : String(self[index(self.startIndex, offsetBy: count)...])
    }
 }
IDoc
источник
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
источник
Я думаю, вам следует добавить тест, в котором входные данные имеют отрицательное значение
Мустафа Баальбаки