Go: обработка текстовой информации

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 18 янв 2024, 03:51

В связи с подготовкой 2-го издания книги " Linux и Go. Эффективное низкоуровневое программирование"- это издательству так вздумалось назвать книгу (см. Книга: "Linux: многопроцессорная эффективность. Выбираем Go") ... подготовкой, которая должна закончиться к 1 февраля - начинаю эту тему.

По моему мнению ... вот добавил в новое издание:
... в языке Go есть 2 предмета, выделяющих его, в положительную сторону, на фоне многих других языков. А именно:
1. Конкурентное программирование и возможности параллельного многопроцессорного выполнения;
2. Расширенные возможности обработки мультиязычной текстовой информации, символьных строк.
Но по п.1 уже много написано...
А вот по 2 - мало, почти ничего, это нужно всё дописать в новое издание.
А всякие примеры кода буду отрабатывать здесь в теме.
По крайней мере, всякие "фишки", которые выявляются по дороге...

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 18 янв 2024, 16:28

Olej писал(а):
18 янв 2024, 03:51
По крайней мере, всякие "фишки", которые выявляются по дороге...
Только вещи которые не очевидные...
Строчные литерал:
... в Go, для обеспечения разнообразными форматами представления, этого оказывается мало.
• Традиционная запись строк в двойных кавычках допускает управляющие последовательности в строке, в потоке, отмечаемые обратным слэшем, мы их уже неоднократно видели, как например: "\n", "\t", "\r", "\x" (этот формат предоставляет возможность записать прямое 16-ричное значение в байт)…
• Сырое (raw) представление строки, в котором любые символы записываются «as is», без всякого управляющего смысла, записываются в одиночных кавычках, только не в тех одиночных кавычках, как в языке Python и некоторых других, а в обратных кавычках (на клавиатуре соответствующие букве Ё). Например: `это знак переноса строки: \n`.
• А обычные одиночные кавычки в Go зарезервированы для записей литералов типа rune, которые тоже имеют некоторый смысл в контексте строчных представлений, но являются целочисленными значениями int32. Например: 'Ю'.
Пример:

Код: Выделить всё

package main

import "fmt"

func main() {
	s := "🔥요£µЩ"
	r := []rune(s)
	fmt.Printf("%x : байт %d символов %d\n", s, len(s), len(r))
	fmt.Printf("%#v\n", []byte(s)) 
	for p, c := range s {
		fmt.Printf("%d[%#U] ", p, c)
	}
	println()

	for i := 0; i < len(r); i++ {
		fmt.Printf("%x ", r[i])
	}
	println()

	//     f0  9f  94  a5  ec  9a  94  c2  a3  c2  b5  d0  a9
	s = "\xf0\x9f\x94\xa5\xec\x9a\x94\xc2\xa3\xc2\xb5\xd0\xa9"
	fmt.Printf("%s\n", s)
	s = `\xf0\x9f\x94\xa5\xec\x9a\x94\xc2\xa3\xc2\xb5\xd0\xa9`
	fmt.Printf("%s\n", s)
	s = string([]rune{0x1f525, 0xc694, 0xA3, 0xB5, 0x429})
	fmt.Printf("%s\n", s)
	s = string([]rune{'🔥', '요', '£', 'µ', 'Щ'})
	fmt.Printf("%s\n", s)
}
И выглядит это так (запускаю код как скрипт - это ещё одна "фишка" Go):

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples.work/strings/runes$ go run raw.go
f09f94a5ec9a94c2a3c2b5d0a9 : байт 13 символов 5
[]byte{0xf0, 0x9f, 0x94, 0xa5, 0xec, 0x9a, 0x94, 0xc2, 0xa3, 0xc2, 0xb5, 0xd0, 0xa9}
0[U+1F525 '🔥'] 4[U+C694 '요'] 7[U+00A3 '£'] 9[U+00B5 'µ'] 11[U+0429 'Щ'] 
1f525 c694 a3 b5 429 
🔥요£µЩ
\xf0\x9f\x94\xa5\xec\x9a\x94\xc2\xa3\xc2\xb5\xd0\xa9
🔥요£µЩ
🔥요£µЩ
Любопытно... :-o
Вложения
raw.go
(709 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 18 янв 2024, 16:41

Olej писал(а):
18 янв 2024, 16:28
Только вещи которые не очевидные...
В большинстве своём, все (осмысленные) строки Go встроенного типа string, с которыми мы зачастую будем иметь дело — это последовательность символов UNICODE в кодировке UTF-8 неограниченной длины. Но, в самом общем случае, строка Go может содержать любую последовательность байт, которая может не соответствовать вообще никаким символам UNICODE / UTF-8. Строго говоря, строки Go представляют собой срезы (слайсы) произвольных байт, и доступны эти срезы только для чтения (это ещё одна, и более точная формулировка неизменяемости строк Go).

Код: Выделить всё

package main
import "fmt"

func main() {
	buf := make([]byte, 10)
	fmt.Printf("%v\n", buf)
	str := string(buf)
	fmt.Printf("%x\n", []rune(str))
}

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples/strings/runes$ go run nulstr.go
[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
Здесь мы создали строку, состоящую исключительно из байт с нулевыми значениями! Это может быть большой неожиданностью для программистов привыкших к правилам C/C++!
Вложения
nulstr.go
(147 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 18 янв 2024, 16:58

Olej писал(а):
18 янв 2024, 03:51
всякие примеры кода
Не изменяемость строк Go, о которых много пишут - как это понимать правильно? (т.е. примерно так как и в Python).

Код: Выделить всё

package main

import (
	"bufio"
	"fmt"
	"os"
	"strings"
	"unicode"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		инкремент := func(r rune) rune {
			if unicode.IsNumber(r) {
				return '0' + (r-'0'+1)%10
			} else {
				return r
			}
		}
		fmt.Println(strings.Map(инкремент, scanner.Text()))
	}
}

Код: Выделить всё

$ ./map 
12 + 13 = 25
23 + 24 = 36
В 1913 году 1 пуд зерна стоил 34 руб.
В 2024 году 2 пуд зерна стоил 45 руб.
Пример бессмысленный, но убедительный :lol:
... содержимое переменных string можно сколь угодно изменять дозволенными функциями и методами, которые при этом берут такую переменную в одном месте памяти и, после её модификации, размещают новую модифицированную копию в совершенно другом месте памяти. В этом смысле строки Go совершенно подобны строкам Python, для которых точно таким же образом декларируется неизменяемость.
Вложения
map.go
(349 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 18 янв 2024, 17:01

Olej писал(а):
18 янв 2024, 03:51
всякие примеры кода
Палиндром ...
... ах палиндром, палиндром, палиндром ...
© - Это, похоже из какой-то песни. :lol: :oops:
Палиндромы — это слова или целые фразы, которые читаются одинаково в обоих направлениях: слева направо и справа налево. Самым длинным в мире палиндромом, состоящим из одного слова, принято считать финское слово saippuakivikauppias (торговец щелоком — мыльным камнем). В английском языке самый длинный палиндром считают — redivider (своего рода перегородка).

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 18 янв 2024, 17:08

Olej писал(а):
18 янв 2024, 17:01
Палиндром
Первой, более простой, задачей мы сделаем тестер слов-палидромов.

Код: Выделить всё

package main
import (
        "fmt"
        "bufio"
        "log"
        "os"
)

func test(scanner *bufio.Scanner) {
    for scanner.Scan() {
	ustr := scanner.Text() // введенная строка
	rstr := []rune(ustr)
	pali := true;
	for i := 0; i < len(rstr) / 2; i++ {
	    pali = (rstr[i] == rstr[len(rstr) - i - 1])
	    if !pali {
		break
	    }
	}
	fmt.Printf("%v : %s\n", pali, ustr)
    }
}

func main() {
    if len(os.Args) > 1 {
	file, err := os.Open(os.Args[1])
	if err != nil {
	    log.Fatal(err)
	}
	defer file.Close()
	test(bufio.NewScanner(file))
    } else {
	test(bufio.NewScanner(os.Stdin))
    }
}

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples.work/strings/palindrom$ ./palindrow
123 321
true : 123 321
12 33 21
true : 12 33 21
1 23 321
false : 1 23 321

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples/strings/palindrom$ go run palindrow.go words.txt
true : доход
true : шалаш
true : топот
true : радар
true : комок
true : saippuakivikauppias
true : redivider
P.S. В файле words.txt - тестовые строки-слова палиндромы
Вложения
palindrow.go
(626 байт) 9 скачиваний
words.txt
(85 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 18 янв 2024, 17:13

Olej писал(а):
18 янв 2024, 17:08
Первой, более простой, задачей мы сделаем тестер слов-палидромов.
По правилам (достаточно формализованным) составления палиндромов-слов:
- пробелы и знаки препинания в фразе не принимаются во внимание;
- большие и малые литеры не различаются;
- буквы вот в этих парах считаются идентичными: е-ё, и-й, ь-ъ;
Для начала мы исключим пробелы и различия в строчных и прописных буквах …

Код: Выделить всё

package main
import (
        "fmt"
        "bufio"
        "log"
        "os"
        "strings"
)

func test(scanner *bufio.Scanner) {
    for scanner.Scan() {
	ustr := scanner.Text() // введенная строка
	rstr := []rune(strings.ReplaceAll(strings.ToLower(ustr), " ", ""))
	pali := true;
	for i := 0; i < len(rstr) / 2; i++ {
	    if pali = rstr[i] == rstr[len(rstr) - i - 1]; !pali {
		break
	    }
	}
	fmt.Printf("%v : %s\n", pali, ustr)
    }
}

func main() {
    if len(os.Args) > 1 {
	file, err := os.Open(os.Args[1])
	if err != nil { log.Fatal(err) }
	defer file.Close()
	test(bufio.NewScanner(file))
    } else {
	test(bufio.NewScanner(os.Stdin))
    }
}

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples.work/strings/palindrom$ ./palindrob
saippuakivikauppias
true : saippuakivikauppias
Лев осовел
true : Лев осовел
А роза упала на лапу Азора
true : А роза упала на лапу Азора

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples.work/strings/palindrom$ ./palindrob palindrom.txt
true : А роза упала на лапу Азора
true : Я иду с мечем судия
true : На в лоб, болван
true : Лев осовел
false : Да, гневен гад
true : Мат и тут и там
false : Лев с ума ламу свёл
true : Кирилл лирик
true : Уж редко рукою окурок держу
true : Коту скоро сорок суток
false : А муза рада музе без ума да разума.
true : Веер веял для евреев
false : Madam, I’m Adam
false : Муха! О, муха! Велика аки лев! Ах, ум! О ах, ум!
true : Sum summus mus
false : Νίψον ανομήματα μη μόναν όψιν
true : Sator Arepo tenet opera rotas
false : Уверена я, а не реву
Почти хорошо...
Мешают ещё всякие знаки препинания.
P.S. В файле palindrom.txt - строки-фразы палиндромы
Вложения
palindrob.go
(678 байт) 9 скачиваний
palindrom.txt
(672 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 21 янв 2024, 12:41

Регулярные выражения — это отдельно стоящая, очень мощная техника работы с текстовой информацией. Регулярные выражения являются способом описания текстовых шаблонов (образцов) для сопоставления и выполнения последующих действий по итогам этих сопоставления.
Синтаксис записи образцов (шаблонов) сопоставления содержит много спецсимволов, поэтому для записи строк шаблона документация Go рекомендует использовать не обычную запись (в двойных кавычках), а сырой (raw) формат, заключаемый в обратные одиночные кавычки (где спецсимволы никак не интерпретируются).
Пример - выделять нам числовые значения:

Код: Выделить всё

package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
)

func main() {
	re, _ := regexp.Compile(`\d+`)
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		s := scanner.Text() //  A123  AA455 AAA2 A89
		r := re.FindAllString(s, -1)
		fmt.Println(r)      // [123 455 2 89]
		a := re.FindAllStringIndex(s, -1)
		fmt.Println(a)      // [[1 4] [8 11] [15 16] [18 20]]
		m := make(map[int]string)
		for _, e := range a {
			m[int(e[0])] = s[e[0]:e[1]]
		}
		fmt.Println(m)      // [1:123 8:455 15:2 18:89]
	}
}
Особо меня интересовала здесь возможность выделения числовых (ASCII) полей внутри мультибайтных UTF-8 строк UNICODE, отличных от латиницы:

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples.work/strings/regexp$ go run finddig.go
A123  AA455 AAA2 A89
[123 455 2 89]
[[1 4] [8 11] [15 16] [18 20]]
map[1:123 8:455 15:2 18:89]
13🔥 + 987요 + 5£ - 543µ / 4Щ
[13 987 5 543 4]
[[0 2] [9 12] [18 19] [24 27] [32 33]]
map[0:13 9:987 18:5 24:543 32:4]
При Петре I средний заработок неквалифицированного работника составлял 5-8 копеек в день, 1 пуд мяса тогда стоил 30 копеек, 1 пуд хлеба - 10 копеек.
[5 8 1 30 1 10]
[[133 134] [135 136] [163 164] [203 205] [220 221] [242 244]]
map[133:5 135:8 163:1 203:30 220:1 242:10]
Или так ... чтоб менее хлопотно тестировать:

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples.work/strings/regexp$ cat dig.txt 
A123  AA455 AAA2 A89
Б123 ВВ455 ГГГ2 Д89
в 1913 году 2 фунта муки стоили 1 рубль 15 копеек
13🔥 + 987요 + 5£ - 543µ / 4Щ
При Петре I средний заработок неквалифицированного работника составлял 5-8 копеек в день, 1 пуд мяса тогда стоил 30 копеек, 1 пуд хлеба - 10 копеек.

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples.work/strings/regexp$ go run finddig.go < dig.txt
[123 455 2 89]
[[1 4] [8 11] [15 16] [18 20]]
map[1:123 8:455 15:2 18:89]
[123 455 2 89]
[[2 5] [10 13] [20 21] [24 26]]
map[2:123 10:455 20:2 24:89]
[1913 2 1 15]
[[3 7] [17 18] [52 53] [65 67]]
map[3:1913 17:2 52:1 65:15]
[13 987 5 543 4]
[[0 2] [9 12] [18 19] [24 27] [32 33]]
map[0:13 9:987 18:5 24:543 32:4]
[5 8 1 30 1 10]
[[133 134] [135 136] [163 164] [203 205] [220 221] [242 244]]
map[133:5 135:8 163:1 203:30 220:1 242:10]
Вложения
finddig.go
(517 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 21 янв 2024, 13:17

Olej писал(а):
21 янв 2024, 12:41
Регулярные выражения
Но не всегда это срабатывает так как ... как ожидается по описаниям в литературе, или в примерах:

Код: Выделить всё

package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"strings"
)

func main() {
//	re, _ := regexp.Compile(`\w+`)
//	re, _ := regexp.Compile(`([Ё-Яa-ё]|[a-zA-Z])+`)
//	re, _ := regexp.Compile(`\S+`)
	re, _ := regexp.Compile(`\pL+`)
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		s := scanner.Text()
		r1 := re.FindAllString(s, -1)		
		fmt.Printf("[%d] => ", len(r1)) 		
		for _, c := range r1 {
			fmt.Printf("<%s> ", string(c))
		}
		fmt.Printf("\nfunc:  ")
		r2 := re.ReplaceAllStringFunc(s, rep2up)
		println()
		fmt.Printf("[%d] => %v\n", len(r2), string(r2)) 
		println("----------------------------")
	}
}

func rep2up (s string) string {
	fmt.Printf("<%s> ", s)
	return strings.ToLower(s)
}
Часто упоминаемый в публикациях шаблон `\w+` здесь не работает … то есть он работает, но только на ASCII (англоязычных строках). Остальные 3 показанных формы будут работать для разных языков.

Код: Выделить всё

olej@R420:~/2024/own.BOOKs/BHV.Go.2/examples.work/strings/regexp$ ./replw
Ёлки пойдут на палки
[4] => <Ёлки> <пойдут> <на> <палки>
func:  <Ёлки> <пойдут> <на> <палки>
[37] => ёлки пойдут на палки
----------------------------
世 界
[2] => <世> <界>
func:  <世> <界>
[7] => 世 界
----------------------------
В начале было Слово, и Слово было у Бога, и Слово было Бог
[13] => <В> <начале> <было> <Слово> <и> <Слово> <было> <у> <Бога> <и> <Слово> <было> <Бог>
func:  <В> <начале> <было> <Слово> <и> <Слово> <было> <у> <Бога> <и> <Слово> <было> <Бог>
[102] => в начале было слово, и слово было у бога, и слово было бог
----------------------------
It Is A Short English String
[6] => <It> <Is> <A> <Short> <English> <String>
func:  <It> <Is> <A> <Short> <English> <String>
[28] => it is a short english string
----------------------------
Если мы хотим определить диапазон символов конкретного языка, то прежде должны пересмотреть кодовые таблицы UNICOD этого языка, потому что символы не всегда следуют национальным алфавитам. Для русского языка, например, большая буква `Ё` (U+0401) предшествует `А` (U+0410), а малая `ё` (U+0451) следует за `я` (U+04FF), поэтому шаблон будет: `([Ё-Яa-ё]|[a-zA-Z])+`.
... шаблон из числа новых `\pL+` означает любой символ UNICODE. Поскольку это всё может заметно отличаться от традиционного синтаксиса регулярных выражений описываемого в литературе, настоятельно рекомендуется изучить краткое руководство по синтаксису для Go, датированное 2023 годом, и ссылка на которое даётся в конце текста.
Для каждого слова в тестируемой строке ... делаем замену re.ReplaceAllStringFunc(), но не просто фиксированную замену, а для каждого найденного слова вызываем нами же определённую собственную функцию вида func rep2up (s string) string, которая, вообще то говоря, может делать со строкой всё что угодно!
Вложения
replw.go
(720 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Go: обработка текстовой информации

Непрочитанное сообщение Olej » 21 янв 2024, 13:23

Olej писал(а):
21 янв 2024, 13:17
Для каждого слова в тестируемой строке ... делаем замену re.ReplaceAllStringFunc(), но не просто фиксированную замену, а для каждого найденного слова вызываем нами же определённую собственную функцию вида func rep2up (s string) string, которая, вообще то говоря, может делать со строкой всё что угодно!
Вариант не со строками string, но с байтовыми срезами []byte (с функцией замены func rep2up (b []byte) []byte ) очень похож:

Код: Выделить всё

package main

import (
	"bufio"
	"fmt"
	"os"
	"regexp"
	"strings"
)

func main() {
//	re, _ := regexp.Compile(`\w+`)
// re, _ := regexp.Compile(`([Ё-Яa-ё]|[a-zA-Z])+`)
//	re, _ := regexp.Compile(`\S+`)
	re, _ := regexp.Compile(`\pL+`)
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		s := scanner.Text()
		r1 := re.FindAll([]byte(s), -1)		
		fmt.Printf("[%d] => ", len(r1)) 		
		for _, c := range r1 {
			fmt.Printf("<%s> ", string(c))
		}
		fmt.Printf("\nfunc:  ")
		r2 := re.ReplaceAllFunc([]byte(s), rep2up)
		println()
		fmt.Printf("[%d] => %v\n", len(r2), string(r2)) 
		println("----------------------------")
	}
}

func rep2up (b []byte) []byte {
	fmt.Printf("<%s> ", b)
	s := string(b)
	return []byte(strings.ToLower(s))
}
Вложения
replwb.go
(752 байт) 9 скачиваний

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 14 гостей