Avatar ·

Как разбить строку по пробелам в Go? (Split string on whitespace)

Всем привет! Я только начал изучать Go, и у меня возникла проблема. Нужно разбить строку на слова, игнорируя все пробельные символы (пробелы, табуляции, переносы строк). Пробовал использовать `strings.Split()`, но она работает только с одним разделителем. Например, если я пишу: ```go package main import ( "fmt" "strings" ) func main() { s := "hello world\tfoo\nbar" parts := strings.Split(s, " ") fmt.Println(parts) // ["hello", "", "", "world\tfoo\nbar"] } ``` Получается каша — пустые строки, табуляция не учитывается. Видел, что в других языках (Python, Java) есть встроенные методы `split()` без аргументов, которые сами обрабатывают любые пробелы. В Go есть что-то подобное? Или нужно писать свою функцию? Слышал, что можно через `regexp`, но боюсь, что это замедлит программу. Подскажите оптимальный способ. Важно: мне нужно именно разбить на слова, а не просто удалить лишние пробелы. Пример: "a b\tc" → ["a", "b", "c"]. <<>>

Лучшее решение: strings.Fields()

В Go есть встроенная функция strings.Fields(), которая идеально подходит для вашей задачи. Она разбивает строку по любому количеству пробельных символов (пробелы, табуляции, переносы строк, возврат каретки и т.д.) и автоматически удаляет пустые элементы.

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "hello   world\tfoo\nbar"
    parts := strings.Fields(s)
    fmt.Printf("%q\n", parts) // ["hello" "world" "foo" "bar"]
    fmt.Println(len(parts))   // 4
}

Как это работает?

Функция strings.Fields() использует определение пробельных символов из Юникода (Unicode whitespace). Она:

  • Пропускает начальные и конечные пробелы
  • Разбивает строку по одному или нескольким пробельным символам
  • Не включает пустые строки в результат

Когда это не подходит?

Если вам нужно разделить строку только по одному конкретному символу (например, только по пробелу, но не по табуляции), используйте strings.Split() с фильтрацией пустых строк:

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "hello   world\tfoo"
    parts := strings.FieldsFunc(s, func(r rune) bool {
        return r == ' ' // только пробел
    })
    fmt.Printf("%q\n", parts) // ["hello" "" "" "world\tfoo"]
}

Производительность

strings.Fields() работает очень быстро — это нативная функция, написанная на Go. Она в разы быстрее, чем regexp. Если вам нужно разбить миллионы строк, используйте именно её.

Дополнительные возможности

Если вам нужен более гибкий контроль над разделителями, используйте strings.FieldsFunc(). Она принимает функцию, которая определяет, является ли символ разделителем:

package main

import (
    "fmt"
    "strings"
    "unicode"
)

func main() {
    s := "hello,world;foo:bar"
    parts := strings.FieldsFunc(s, func(r rune) bool {
        return r == ',' || r == ';' || r == ':'
    })
    fmt.Printf("%q\n", parts) // ["hello" "world" "foo" "bar"]
}

Также можно использовать unicode.IsSpace() для точного совпадения с поведением strings.Fields():

parts := strings.FieldsFunc(s, unicode.IsSpace)

Это даст тот же результат, что и strings.Fields().

<<>>

Альтернатива: bufio.Scanner для построчной обработки

Если вы работаете с большими текстами или потоками данных, рассмотрите bufio.Scanner с кастомной функцией разделения. Это позволяет обрабатывать строку по словам без загрузки всего текста в память:

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    s := "hello   world\tfoo\nbar"
    scanner := bufio.NewScanner(strings.NewReader(s))
    scanner.Split(bufio.ScanWords) // предопределённый splitter для слов

    var words []string
    for scanner.Scan() {
        words = append(words, scanner.Text())
    }

    fmt.Printf("%q\n", words) // ["hello" "world" "foo" "bar"]
}

Этот подход особенно полезен, когда:

  • Строка очень длинная (сотни мегабайт)
  • Вы читаете данные из файла или сети
  • Нужно обрабатывать слова по мере поступления, а не все сразу

bufio.ScanWords использует то же определение пробельных символов, что и strings.Fields(), но работает лениво — не создаёт весь слайс заранее.

Важно: Для простых случаев strings.Fields() всё же предпочтительнее из-за простоты и скорости. Используйте bufio.Scanner только когда память или потоковая обработка критичны.