Разделить строку по длине в Golang

Кто-нибудь знает, как разбить строку в Golang по длине?

Например, чтобы разделить «helloworld» после каждых 3 символов, поэтому в идеале он должен возвращать массив «hel», «low», «orl», «d»?

В качестве альтернативы возможным решением было бы также добавить новую строку после каждых 3 символов.

Все идеи приветствуются!


person Fernando Parra    schedule 05.09.2014    source источник
comment
Что ж, здесь может помочь программирование? Нравится s[n:n+3]+"\n"?   -  person Volker    schedule 05.09.2014


Ответы (6)


Не забудьте преобразовать свой string в фрагмент руны: см. "Slice разбить на буквы".

for автоматически преобразует string в rune, поэтому в этом случае нет необходимости в дополнительном коде, чтобы сначала преобразовать string в rune.

for i, r := range s {
    fmt.Printf("i%d r %c\n", i, r)
    // every 3 i, do something
}

r[n:n+3] будет лучше всего работать с кусочком руны.

Индекс будет увеличиваться на единицу для каждой руны, в то время как он может увеличиваться более чем на единицу для каждого байта в кусок строки: "世界": i будет равно 0 и 3: символ (руна) может состоять из нескольких байтов.


Например, рассмотрим s := "世a界世bcd界efg世": 12 рун. (см. play.golang.org)

Если вы попытаетесь разобрать его байт за байтом, вы пропустите (в наивном разделении каждые 3 символа реализации) некоторые из «индексов по модулю 3» (равны 2, 5, 8 и 11), потому что индекс будет увеличиваться за эти ценности:

for i, r := range s {
    res = res + string(r)
    fmt.Printf("i %d r %c\n", i, r)
    if i > 0 && (i+1)%3 == 0 {
        fmt.Printf("=>(%d) '%v'\n", i, res)
        res = ""
    }
}

Выход:

i  0 r 世
i  3 r a   <== miss i==2
i  4 r 界
i  7 r 世  <== miss i==5
i 10 r b  <== miss i==8
i 11 r c  ===============> would print '世a界世bc', not exactly '3 chars'!
i 12 r d
i 13 r 界
i 16 r e  <== miss i==14
i 17 r f  ===============> would print 'd界ef'
i 18 r g
i 19 r 世 <== miss the rest of the string

Но если бы вы повторяли руны (a := []rune(s)), вы бы получили то, что ожидали, поскольку индекс будет увеличиваться на одну руну за раз, что упрощает агрегирование ровно 3 символов:

for i, r := range a {
    res = res + string(r)
    fmt.Printf("i%d r %c\n", i, r)
    if i > 0 && (i+1)%3 == 0 {
        fmt.Printf("=>(%d) '%v'\n", i, res)
        res = ""
    }
}

Выход:

i 0 r 世
i 1 r a
i 2 r 界 ===============> would print '世a界'
i 3 r 世
i 4 r b
i 5 r c ===============> would print '世bc'
i 6 r d
i 7 r 界
i 8 r e ===============> would print 'd界e'
i 9 r f
i10 r g
i11 r 世 ===============> would print 'fg世'
person VonC    schedule 05.09.2014
comment
Это очень неэффективно из-за конкатенации строк. Есть ответы, которые их не используют - person Igor Mikushkin; 28.04.2020

Вот еще один вариант игровая площадка. Это намного эффективнее с точки зрения скорости и памяти, чем другие ответы. Если вы хотите запустить тесты здесь, это бенчмарки. В целом это в 5 раз быстрее, чем предыдущая версия, которая в любом случае была самым быстрым ответом.

func Chunks(s string, chunkSize int) []string {
    if len(s) == 0 {
        return nil
    }
    if chunkSize >= len(s) {
        return []string{s}
    }
    var chunks []string = make([]string, 0, (len(s)-1)/chunkSize+1)
    currentLen := 0
    currentStart := 0
    for i := range s {
        if currentLen == chunkSize {
            chunks = append(chunks, s[currentStart:i])
            currentLen = 0
            currentStart = i
        }
        currentLen++
    }
    chunks = append(chunks, s[currentStart:])
    return chunks
}

Обратите внимание, что индекс указывает на первый байт руны при переборе строки. Руна занимает от 1 до 4 байт. Нарезка также обрабатывает строку как массив байтов.

ПРЕДЫДУЩИЙ МЕДЛЕННЫЙ АЛГОРИТМ

Код находится здесь игровая площадка. Преобразование байтов в руны, а затем снова в байты, на самом деле занимает много времени. Так что лучше используйте быстрый алгоритм в верхней части ответа.

func ChunksSlower(s string, chunkSize int) []string {
    if chunkSize >= len(s) {
        return []string{s}
    }
    var chunks []string
    chunk := make([]rune, chunkSize)
    len := 0
    for _, r := range s {
        chunk[len] = r
        len++
        if len == chunkSize {
            chunks = append(chunks, string(chunk))
            len = 0
        }
    }
    if len > 0 {
        chunks = append(chunks, string(chunk[:len]))
    }
    return chunks
}

Обратите внимание, что эти два алгоритма по-разному обрабатывают недопустимые символы UTF-8. Первый обрабатывает их как есть, а второй заменяет их символом utf8.RuneError ('\uFFFD'), который имеет следующее шестнадцатеричное представление в UTF-8: efbfbd.

person Igor Mikushkin    schedule 27.04.2020
comment
Интересно. Проголосовал. Определенно эффективнее, чем мой 6-летний ответ! - person VonC; 28.04.2020
comment
Это должно быть добавлено в стандартную библиотеку. - person TomOnTime; 30.07.2020
comment
Вы можете повысить производительность, используя strings.Builder, см. эту игровую площадку. Контрольные показатели здесь - person ardnew; 31.05.2021
comment
@ardnew Большое спасибо, что указали на это! Это показывает, что копирование является ударом по производительности. Однако этот билдер делает некоторое копирование сам по себе, и здесь он не нужен. Вместо этого я придумал еще более быструю версию, которую скоро опубликую здесь. - person Igor Mikushkin; 02.06.2021

Также недавно понадобилась функция для этого, см. пример использования здесь

func SplitSubN(s string, n int) []string {
    sub := ""
    subs := []string{}

    runes := bytes.Runes([]byte(s))
    l := len(runes)
    for i, r := range runes {
        sub = sub + string(r)
        if (i + 1) % n == 0 {
            subs = append(subs, sub)
            sub = ""
        } else if (i + 1) == l {
            subs = append(subs, sub)
        }
    }

    return subs
}
person mozey    schedule 06.09.2016
comment
Это очень неэффективно из-за конкатенации строк. Есть ответы, которые их не используют - person Igor Mikushkin; 28.04.2020

Вот еще один пример (вы можете попробовать его здесь):

package main

import (
    "fmt"
    "strings"
)

func ChunkString(s string, chunkSize int) []string {
    var chunks []string
    runes := []rune(s)

    if len(runes) == 0 {
        return []string{s}
    }

    for i := 0; i < len(runes); i += chunkSize {
        nn := i + chunkSize
        if nn > len(runes) {
            nn = len(runes)
        }
        chunks = append(chunks, string(runes[i:nn]))
    }
    return chunks
}

func main() {
    fmt.Println(ChunkString("helloworld", 3))
    fmt.Println(strings.Join(ChunkString("helloworld", 3), "\n"))
}
person rinat.io    schedule 27.01.2018
comment
Это, безусловно, лучший ответ здесь. Однако len(runes) проверка выглядит излишней. Вы можете проверить len(s) и вернуть nil или пустой массив. Таким образом, вы можете определить runes и chunks после этой проверки - person Igor Mikushkin; 28.04.2020
comment
На самом деле я написал свой собственный ответ, который более эффективен. - person Igor Mikushkin; 28.04.2020

Простое решение с использованием регулярных выражений

re := regexp.MustCompile((\S{3})) x := re.FindAllStringSubmatch("HelloWorld", -1) fmt.Println(x)

https://play.golang.org/p/mfmaQlSRkHe

person rahul    schedule 10.02.2018
comment
Ты запускал его в The Go Playground? Не могли бы вы, пожалуйста, дайте мне знать, что вы не поняли? - person rahul; 11.02.2018
comment
должно быть FindAllString вместо FindAllStringSubmatch, нет? - person PLG; 09.08.2018
comment
это, безусловно, лучший пример. Судя по количеству голосов, кажется, что программисты любят писать много кода! - person Mark Robson; 04.06.2021

Я попробовал 3 версии для реализации функции, функция с именем «splitByWidthMake» является самой быстрой.

Эти функции игнорируют юникод, а только код ascii.

package main

import (
    "fmt"
    "strings"
    "time"
    "math"
)

func splitByWidthMake(str string, size int) []string {
    strLength := len(str)
    splitedLength := int(math.Ceil(float64(strLength) / float64(size)))
    splited := make([]string, splitedLength)
    var start, stop int
    for i := 0; i < splitedLength; i += 1 {
        start = i * size
        stop = start + size
        if stop > strLength {
            stop = strLength
        }
        splited[i] = str[start : stop]
    }
    return splited
}



func splitByWidth(str string, size int) []string {
    strLength := len(str)
    var splited []string
    var stop int
    for i := 0; i < strLength; i += size {
        stop = i + size
        if stop > strLength {
            stop = strLength
        }
        splited = append(splited, str[i:stop])
    }
    return splited
}

func splitRecursive(str string, size int) []string {
    if len(str) <= size {
        return []string{str}
    }
    return append([]string{string(str[0:size])}, splitRecursive(str[size:], size)...)
}

func main() {
    /*
    testStrings := []string{
        "hello world",
        "",
        "1",
    }
    */

    testStrings := make([]string, 10)
    for i := range testStrings {
        testStrings[i] = strings.Repeat("#", int(math.Pow(2, float64(i))))
    }

    //fmt.Println(testStrings)

    t1 := time.Now()
    for i := range testStrings {
        _ = splitByWidthMake(testStrings[i], 2)
        //fmt.Println(t)
    }
    elapsed := time.Since(t1)
    fmt.Println("for loop version elapsed: ", elapsed)


    t1 = time.Now()
    for i := range testStrings {
        _ = splitByWidth(testStrings[i], 2)
    }
    elapsed = time.Since(t1)
    fmt.Println("for loop without make version elapsed: ", elapsed)




    t1 = time.Now()
    for i := range testStrings {
        _ = splitRecursive(testStrings[i], 2)
    }
    elapsed = time.Since(t1)
    fmt.Println("recursive version elapsed: ", elapsed)

}
person ryan    schedule 24.04.2019