Подтвердить что ты не робот

Как разбить строку на новые строки в Swift

У меня есть строка, которую я получил из текстового файла.

Текстовый файл:

Line 1
Line 2
Line 3
...

Я хочу преобразовать его в массив, один элемент массива на строку.

[ "Line 1", "Line 2", "Line 3", ... ]

В зависимости от того, как файл был сохранен, строка может принимать одну из следующих форм:

  • string = "Line 1\nLine 2\nLine 3\n..." где \n - символ новой строки (строка строки)

  • string = "Line 1\r\nLine 2\r\nLine 3\r\n..." где \r - символ возврата каретки.

Как я понимаю, \n обычно используется в Apple/Linux сегодня, а \r\n используется в Windows.

Как мне разбить строку при любом разрыве строки, чтобы получить массив String без каких-либо пустых элементов?

Update

Существует несколько решений, которые работают ниже. На данный момент у меня нет веских оснований выбирать один из них более правильным, чем другие. Некоторые факторы, которые могут повлиять на выбор, могут быть (1) как "Swift", и (2) насколько быстро это происходит для очень длинных строк. Вы можете предоставить обратную связь, перенести один или несколько из них и/или оставить комментарий.

См. мой обобщенный ответ здесь

4b9b3361

Ответ 1

Вы можете использовать метод String enumerateLines:

Перечисляет все строки в строке.

Swift 3 или более поздняя версия

let sentence = "Line 1\nLine 2\nLine 3\n"
var lines: [String] = []
sentence.enumerateLines { line, _ in
    lines.append(line)
}
print(lines)   // "[Line 1, Line 2, Line 3]"

extension String {
    var lines: [String] {
        var result: [String] = []
        enumerateLines { line, _ in result.append(line) }
        return result
    }
}

Использование:

let sentence2 = "Line 4\nLine 5\nLine 6\n"
let sentence2Lines = sentence2.lines

print(sentence2Lines)    // ["Line 4", "Line 5", "Line 6"]


let sentence3 = "Line 7\r\nLine 8\r\nLine 9\r\n"
let sentence3Lines = sentence3.lines

print(sentence3Lines)  // "[Line 7, Line 8, Line 9]"

Ответ 2

В Swift 2 функция split верхнего уровня теперь представляет собой метод на CollectionType (который соответствует каждому виду символов String "). Существует две версии метода: вы хотите, чтобы тот, который берет замыкание как предикат, указывает, следует ли рассматривать данный элемент как разделитель.

Вы можете получить коллекцию символов из строки в виде набора символов UTF16 с помощью string.utf16, что сделает их совместимыми с API NSCharacterSet. Таким образом, мы можем легко проверить внутри замыкания, является ли данный символ в строке членом набора символов новой строки.

Стоит отметить, что split(_:) вернет символ SubSequence (в основном a Slice), поэтому ему нужно преобразовать обратно в массив строк, который обычно более полезен. Я сделал это ниже, используя flatMap(String.init) - инициализатор UTF16View на String является сбойным, поэтому использование flatMap будет игнорировать любые значения nil, которые могут быть возвращены, гарантируя, что вы получите массив необязательных строк назад.

Итак, для приятного Swift-подобного способа:

let str = "Line 1\nLine 2\r\nLine 3\n"
let newlineChars = NSCharacterSet.newlineCharacterSet()
let lines = str.utf16.split { newlineChars.characterIsMember($0) }.flatMap(String.init)
// lines = ["Line 1", "Line 2", "Line 3"]

Что приятно, так это то, что метод split имеет параметр allowEmptySubsequences, который гарантирует, что вы не получите никаких пустых последовательностей символов в результате. По умолчанию это false, поэтому вам вообще не нужно указывать его.

Изменить

Если вы хотите вообще избегать NSCharacterSet, вы можете так же легко разбить коллекцию совместимых с unicode Character s.

let lines = str.characters.split { $0 == "\n" || $0 == "\r\n" }.map(String.init)

Swift может обрабатывать "\r\n" как один расширенный кластер графем, используя его как один Character для сравнения вместо создания String. Также обратите внимание, что инициализатор для создания строки из Character не может быть невозможен, поэтому мы можем просто использовать map.

Ответ 3

в Xcode 8.2, Swift 3.0.1:

Использовать метод NSString (отделитьBy:)

let text = "line1\nline2"
let array = text.components(separatedBy: CharacterSet.newlines)

Или используйте метод String enumerateLines, например Leo Dabus answer

Ответ 4

let test1 = "Line1\n\rLine2\nLine3\rLine4"
let t1 = test1.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
let t2 = t1.filter{ $0 != "" }
let t3 = t1.filter{ !$0.isEmpty }

Ответ 5

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

Новые строки обычно создаются с символом \n, но также могут быть сделаны с помощью \r\n (из файлов, сохраненных в Windows).

Решение

1. componentsSeparatedByCharactersInSet

let multiLineString = "Line 1\nLine 2\r\nLine 3\n"
let newlineChars = NSCharacterSet.newlineCharacterSet()
let lineArray = multiLineString.componentsSeparatedByCharactersInSet(newlineChars).filter{!$0.isEmpty}
// "[Line 1, Line 2, Line 3]"

Если filter не были использованы, то \r\n создавал бы пустой элемент массива, потому что он подсчитывается как два символа и поэтому дважды разделяет строку в том же месте.

2. split

let multiLineString = "Line 1\nLine 2\r\nLine 3\n"
let newlineChars = NSCharacterSet.newlineCharacterSet()
let lineArray = multiLineString.utf16.split { newlineChars.characterIsMember($0) }.flatMap(String.init)
// "[Line 1, Line 2, Line 3]"

или

let multiLineString = "Line 1\nLine 2\r\nLine 3\n"
let lineArray = multiLineString.characters.split { $0 == "\n" || $0 == "\r\n" }.map(String.init)
// "[Line 1, Line 2, Line 3]"

Здесь \r\n подсчитывается как один символ Swift (расширенный кластер графем)

3. enumerateLines

let multiLineString = "Line 1\nLine 2\r\nLine 3\n"
var lineArray = [String]()
multiLineString.enumerateLines { (line, stop) -> () in
    lineArray.append(line)
}
// "[Line 1, Line 2, Line 3]"

Подробнее о синтаксисе enumerateLine см. этот ответ.

Примечания:

  • многострочная строка обычно не смешивает как \r\n, так и \n, но я делаю это здесь, чтобы показать, что эти методы могут обрабатывать оба формата.
  • NSCharacterSet.newlineCharacterSet() - это символы новой строки, определенные как (U + 000A-U + 000D, U + 0085), которые включают \r и \n.
  • Этот ответ представляет собой резюме ответов на мой предыдущий вопрос. Прочтите эти ответы для более подробной информации.

Ответ 6

Как мне разбить строку при любом разрыве строки, чтобы получить массив String без каких-либо пустых элементов?

Вы были почти там - это просто заднее закрытие, которое здесь различно:

let array = stringFromFile.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()).filter{!$0.isEmpty}

Это то же самое, что:

let newLineChars = NSCharacterSet.newlineCharacterSet() // newline characters defined as (U+000A–U+000D, U+0085)
let array = stringFromFile.componentsSeparatedByCharactersInSet(newLineChars).filter{!$0.isEmpty}

ETA: удалены ненужные дополнительные скобки при закрытии крышки

Ответ 7

Для записи Swift Foundation CharacterSet можно использовать как-то:

extension String {
    var lines: [String] {
        return characters.split { String($0).rangeOfCharacter(from: .newlines) != nil }.map(String.init)
    }
}