20-bo‘lim
Amaliy loyiha - savdo tahlilchisi
O'rganilgan hamma narsani birlashtirib, CSV o'qiydigan, xatolarni to'g'ri qayta ishlaydigan, parallel hisoblaydigan va hisobot yozadigan dastur yozamiz.
Ushbu bo‘lim mundarijasi
Endi yigirma bo'limda o'rgangan narsalarni bitta dasturga yig'amiz: savdo hisobotlarini tahlil qiluvchi vosita.
U CSV o'qiydi, buzuq qatorlarni aniq xato bilan ajratadi, ma'lumotni guruhlaydi, parallel hisoblaydi va natijani faylga yozadi.
Ma'lumot turlari #
package main
import "fmt"
type Kategoriya int
const (
Oziq Kategoriya = iota
Kiyim
Texnika
)
func (k Kategoriya) String() string {
switch k {
case Oziq:
return "oziq-ovqat"
case Kiyim:
return "kiyim"
case Texnika:
return "texnika"
default:
return "noma'lum"
}
}
type Savdo struct {
Mahsulot string
Kategoriya Kategoriya
Miqdor int
NarxTiyin int
}
func (s Savdo) Jami() int {
return s.Miqdor * s.NarxTiyin
}
func somda(tiyin int) string {
return fmt.Sprintf("%d.%02d", tiyin/100, tiyin%100)
}
Uchta qaror darhol ko'rinadi:
- Pul tiyinda,
intsifatida saqlanadi - 3-bo'limdagifloat64darsi. Kategoriya-iotabilan yasalgan o'z turi,String()metodi bilan (12-bo'lim).Jami()- qiymat resiverli metod, chunki u hech narsani o'zgartirmaydi.
func main() {
s := Savdo{
Mahsulot: "Olma",
Kategoriya: Oziq,
Miqdor: 3,
NarxTiyin: 1250000,
}
fmt.Println(s.Mahsulot, s.Kategoriya)
fmt.Println(somda(s.NarxTiyin), "so'm")
fmt.Println("Jami:", somda(s.Jami()), "so'm")
}
Olma oziq-ovqat
12500.00 so'm
Jami: 37500.00 so'm
CSV o'qish va xatolar #
import (
"errors"
"strconv"
"strings"
)
var XatoBuzuqQator = errors.New("qator buzuq")
func kategoriyaOqi(matn string) (Kategoriya, error) {
switch strings.ToLower(strings.TrimSpace(matn)) {
case "oziq":
return Oziq, nil
case "kiyim":
return Kiyim, nil
case "texnika":
return Texnika, nil
default:
return 0, fmt.Errorf("kategoriya %q: %w", matn, XatoBuzuqQator)
}
}
func qatorniOqi(qator string) (Savdo, error) {
qismlar := strings.Split(qator, ",")
if len(qismlar) != 4 {
return Savdo{}, fmt.Errorf("4 ta ustun kerak, %d ta bor: %w",
len(qismlar), XatoBuzuqQator)
}
kat, xato := kategoriyaOqi(qismlar[1])
if xato != nil {
return Savdo{}, xato
}
miqdor, xato := strconv.Atoi(strings.TrimSpace(qismlar[2]))
if xato != nil {
return Savdo{}, fmt.Errorf("miqdor: %w", XatoBuzuqQator)
}
narx, xato := strconv.Atoi(strings.TrimSpace(qismlar[3]))
if xato != nil {
return Savdo{}, fmt.Errorf("narx: %w", XatoBuzuqQator)
}
return Savdo{
Mahsulot: strings.TrimSpace(qismlar[0]),
Kategoriya: kat,
Miqdor: miqdor,
NarxTiyin: narx,
}, nil
}
func main() {
yaxshi, xato := qatorniOqi("Olma, oziq, 3, 1250000")
fmt.Printf("%+v %v\n", yaxshi, xato)
for _, buzuq := range []string{
"Olma, oziq, 3",
"Olma, noma'lum, 3, 100",
"Olma, oziq, ko'p, 100",
} {
_, xato := qatorniOqi(buzuq)
fmt.Println(xato, "|", errors.Is(xato, XatoBuzuqQator))
}
}
{Mahsulot:Olma Kategoriya:oziq-ovqat Miqdor:3 NarxTiyin:1250000} <nil>
4 ta ustun kerak, 3 ta bor: qator buzuq | true
kategoriya " noma'lum": qator buzuq | true
miqdor: qator buzuq | true
Har bir xato nishon xato XatoBuzuqQator ni o'raydi
(14-bo'lim). Shuning uchun chaqiruvchi tafsilotni ham ko'radi,
turini ham errors.Is bilan bila oladi.
Birinchi qatorda Kategoriya:oziq-ovqat chiqdi, Kategoriya:0
emas.
Sababi - Kategoriya turida String() metodi bor va fmt uni
struktura ichida ham avtomatik ishlatadi (12-bo'lim).
Bu nosozlikni izlashda juda qulay: jurnal fayllarida raqam emas, tushunarli nom ko'rinadi.
Buzuq qatorlarni yig'ish #
import (
"errors"
"strconv"
"strings"
)
var XatoBuzuqQator = errors.New("qator buzuq")
func kategoriyaOqi(matn string) (Kategoriya, error) {
switch strings.ToLower(strings.TrimSpace(matn)) {
case "oziq":
return Oziq, nil
case "kiyim":
return Kiyim, nil
case "texnika":
return Texnika, nil
default:
return 0, fmt.Errorf("kategoriya %q: %w", matn, XatoBuzuqQator)
}
}
func qatorniOqi(qator string) (Savdo, error) {
qismlar := strings.Split(qator, ",")
if len(qismlar) != 4 {
return Savdo{}, fmt.Errorf("ustunlar soni: %w", XatoBuzuqQator)
}
kat, xato := kategoriyaOqi(qismlar[1])
if xato != nil {
return Savdo{}, xato
}
miqdor, xato := strconv.Atoi(strings.TrimSpace(qismlar[2]))
if xato != nil {
return Savdo{}, fmt.Errorf("miqdor: %w", XatoBuzuqQator)
}
narx, xato := strconv.Atoi(strings.TrimSpace(qismlar[3]))
if xato != nil {
return Savdo{}, fmt.Errorf("narx: %w", XatoBuzuqQator)
}
return Savdo{strings.TrimSpace(qismlar[0]), kat, miqdor, narx}, nil
}
func hammasiniOqi(matn string) ([]Savdo, error) {
var savdolar []Savdo
var xatolar []error
for i, qator := range strings.Split(strings.TrimSpace(matn), "\n") {
if i == 0 || strings.TrimSpace(qator) == "" {
continue
}
s, xato := qatorniOqi(qator)
if xato != nil {
xatolar = append(xatolar, fmt.Errorf("%d-qator: %w", i+1, xato))
continue
}
savdolar = append(savdolar, s)
}
return savdolar, errors.Join(xatolar...)
}
func main() {
manba := `mahsulot,kategoriya,miqdor,narx
Olma, oziq, 3, 1250000
Shim, kiyim, 1, 25000000
Buzuq qator
Noutbuk, texnika, 2, 950000000
Non, oziq, ko'p, 500000`
savdolar, xato := hammasiniOqi(manba)
fmt.Println("o'qildi:", len(savdolar))
fmt.Println("xatolar:")
fmt.Println(xato)
}
o'qildi: 3
xatolar:
4-qator: ustunlar soni: qator buzuq
6-qator: miqdor: qator buzuq
Dastur birinchi xatoda to'xtamaydi. U yaxshi qatorlarni
yig'adi, buzuqlarini esa errors.Join bilan birlashtiradi
(14-bo'lim).
Amalda aynan shu kerak: foydalanuvchi hamma muammoni birdaniga ko'rsin, birma-bir emas.
Guruhlash va hisobot #
import (
"slices"
"strings"
)
func guruhla(savdolar []Savdo) map[Kategoriya]int {
jami := make(map[Kategoriya]int)
for _, s := range savdolar {
jami[s.Kategoriya] += s.Jami()
}
return jami
}
func hisobot(savdolar []Savdo) string {
jami := guruhla(savdolar)
kategoriyalar := make([]Kategoriya, 0, len(jami))
for k := range jami {
kategoriyalar = append(kategoriyalar, k)
}
slices.Sort(kategoriyalar)
var q strings.Builder
q.WriteString("KATEGORIYA SUMMA\n")
q.WriteString(strings.Repeat("-", 30) + "\n")
umumiy := 0
for _, k := range kategoriyalar {
fmt.Fprintf(&q, "%-15s %14s\n", k, somda(jami[k]))
umumiy += jami[k]
}
q.WriteString(strings.Repeat("-", 30) + "\n")
fmt.Fprintf(&q, "%-15s %14s\n", "JAMI", somda(umumiy))
return q.String()
}
func main() {
savdolar := []Savdo{
{"Olma", Oziq, 3, 1250000},
{"Non", Oziq, 10, 500000},
{"Shim", Kiyim, 1, 25000000},
{"Noutbuk", Texnika, 2, 950000000},
}
fmt.Print(hisobot(savdolar))
}
KATEGORIYA SUMMA
------------------------------
oziq-ovqat 87500.00
kiyim 250000.00
texnika 19000000.00
------------------------------
JAMI 19337500.00
Uchta narsa birlashdi:
mapbilan guruhlash (10-bo'lim).- Kalitlarni saralab chiqarish - map tartibi tasodifiy bo'lgani uchun.
strings.Buildervafmt.Fprintfbilan matn yig'ish (4- va 13-bo'limlar).
slices.Sort(kategoriyalar) ishlaydi, chunki Kategoriya ning
asosi int.
Generik yordamchi #
import "cmp"
func EngKattasi[T any, K cmp.Ordered](royxat []T, kalit func(T) K) (T, bool) {
var bosh T
if len(royxat) == 0 {
return bosh, false
}
eng := royxat[0]
for _, q := range royxat[1:] {
if kalit(q) > kalit(eng) {
eng = q
}
}
return eng, true
}
func main() {
savdolar := []Savdo{
{"Olma", Oziq, 3, 1250000},
{"Noutbuk", Texnika, 2, 950000000},
{"Shim", Kiyim, 1, 25000000},
}
qimmat, bor := EngKattasi(savdolar, func(s Savdo) int { return s.Jami() })
fmt.Println(qimmat.Mahsulot, somda(qimmat.Jami()), bor)
kop, _ := EngKattasi(savdolar, func(s Savdo) int { return s.Miqdor })
fmt.Println(kop.Mahsulot, kop.Miqdor)
_, bor2 := EngKattasi([]Savdo{}, func(s Savdo) int { return s.Miqdor })
fmt.Println("bo'sh ro'yxat:", bor2)
}
Noutbuk 19000000.00 true
Olma 3
bo'sh ro'yxat: false
Bitta generik funksiya har qanday mezon bo'yicha ishlaydi -
17-bo'limdagi var bosh T naqshi bilan bo'sh ro'yxatni ham
to'g'ri qayta ishlaydi.
Parallel qayta ishlash #
import (
"slices"
"strings"
"sync"
)
func qatorlarniParallelOqi(qatorlar []string, ishchilar int) []string {
vazifalar := make(chan string, len(qatorlar))
natijalar := make(chan string, len(qatorlar))
var kutuvchi sync.WaitGroup
for range ishchilar {
kutuvchi.Add(1)
go func() {
defer kutuvchi.Done()
for q := range vazifalar {
natijalar <- strings.ToUpper(strings.TrimSpace(q))
}
}()
}
for _, q := range qatorlar {
vazifalar <- q
}
close(vazifalar)
kutuvchi.Wait()
close(natijalar)
yigilgan := make([]string, 0, len(qatorlar))
for n := range natijalar {
yigilgan = append(yigilgan, n)
}
slices.Sort(yigilgan)
return yigilgan
}
func main() {
qatorlar := []string{"olma", "non", "shim", "noutbuk", "choy"}
natija := qatorlarniParallelOqi(qatorlar, 3)
fmt.Println(natija)
fmt.Println(len(natija))
}
[CHOY NON NOUTBUK OLMA SHIM]
5
Bu 19-bo'limdagi ishchilar hovuzi, faqat haqiqiy vazifa bilan.
Tartib kafolatlanmagani uchun oxirida slices.Sort - shuning
uchun natija har safar bir xil.
Bu misolda uchta ishchi ishlatildi, lekin vazifa juda yengil - satrni katta harfga o'tkazish.
Bunday holatda gorutinalar va kanallar qo'shimcha xarajat keltiradi va dastur ketma-ket ishlaganidan sekinroq bo'lishi mumkin.
Parallellik foyda beradigan joy: har bir vazifa uzoq davom etganda - fayl o'qish, tarmoq so'rovi, og'ir hisob.
Qoida: avval oddiy ketma-ket kod yozing. Sekin ekanini o'lchagandan keyin parallellashtiring.
Faylga saqlash #
import (
"os"
"strings"
)
func saqla(yol, mazmun string) error {
if strings.TrimSpace(mazmun) == "" {
return fmt.Errorf("hisobot bo'sh")
}
return os.WriteFile(yol, []byte(mazmun), 0o644)
}
func oqi(yol string) (string, error) {
xom, xato := os.ReadFile(yol)
if xato != nil {
return "", fmt.Errorf("%s o'qilmadi: %w", yol, xato)
}
return string(xom), nil
}
func main() {
yol := "hisobot.txt"
if xato := saqla(yol, "KATEGORIYA\noziq-ovqat 100\n"); xato != nil {
fmt.Println("saqlanmadi:", xato)
return
}
mazmun, xato := oqi(yol)
if xato != nil {
fmt.Println(xato)
return
}
fmt.Print(mazmun)
fmt.Println("qatorlar:", strings.Count(mazmun, "\n"))
os.Remove(yol)
_, xato = oqi("yoq.txt")
fmt.Println(xato != nil)
fmt.Println(saqla(yol, " "))
}
KATEGORIYA
oziq-ovqat 100
qatorlar: 2
true
hisobot bo'sh
os.WriteFile va os.ReadFile - eng sodda shakl. 0o644 -
fayl ruxsatlari: egasi yozadi, qolganlar o'qiydi.
Har bir fayl amali xato qaytaradi va har biri tekshirilmoqda.
To'liq dastur #
import (
"errors"
"os"
"slices"
"strconv"
"strings"
)
var XatoBuzuqQator = errors.New("qator buzuq")
func kategoriyaOqi(matn string) (Kategoriya, error) {
switch strings.ToLower(strings.TrimSpace(matn)) {
case "oziq":
return Oziq, nil
case "kiyim":
return Kiyim, nil
case "texnika":
return Texnika, nil
default:
return 0, fmt.Errorf("kategoriya %q: %w", matn, XatoBuzuqQator)
}
}
func qatorniOqi(qator string) (Savdo, error) {
q := strings.Split(qator, ",")
if len(q) != 4 {
return Savdo{}, fmt.Errorf("ustunlar soni: %w", XatoBuzuqQator)
}
kat, xato := kategoriyaOqi(q[1])
if xato != nil {
return Savdo{}, xato
}
miqdor, xato := strconv.Atoi(strings.TrimSpace(q[2]))
if xato != nil {
return Savdo{}, fmt.Errorf("miqdor: %w", XatoBuzuqQator)
}
narx, xato := strconv.Atoi(strings.TrimSpace(q[3]))
if xato != nil {
return Savdo{}, fmt.Errorf("narx: %w", XatoBuzuqQator)
}
return Savdo{strings.TrimSpace(q[0]), kat, miqdor, narx}, nil
}
func hammasiniOqi(matn string) ([]Savdo, error) {
var savdolar []Savdo
var xatolar []error
for i, qator := range strings.Split(strings.TrimSpace(matn), "\n") {
if i == 0 || strings.TrimSpace(qator) == "" {
continue
}
s, xato := qatorniOqi(qator)
if xato != nil {
xatolar = append(xatolar, fmt.Errorf("%d-qator: %w", i+1, xato))
continue
}
savdolar = append(savdolar, s)
}
return savdolar, errors.Join(xatolar...)
}
func hisobot(savdolar []Savdo) string {
jami := make(map[Kategoriya]int)
for _, s := range savdolar {
jami[s.Kategoriya] += s.Jami()
}
kategoriyalar := make([]Kategoriya, 0, len(jami))
for k := range jami {
kategoriyalar = append(kategoriyalar, k)
}
slices.Sort(kategoriyalar)
var q strings.Builder
fmt.Fprintf(&q, "%-15s %14s\n", "KATEGORIYA", "SUMMA")
q.WriteString(strings.Repeat("-", 30) + "\n")
umumiy := 0
for _, k := range kategoriyalar {
fmt.Fprintf(&q, "%-15s %14s\n", k, somda(jami[k]))
umumiy += jami[k]
}
q.WriteString(strings.Repeat("-", 30) + "\n")
fmt.Fprintf(&q, "%-15s %14s\n", "JAMI", somda(umumiy))
return q.String()
}
func ishga(manba, chiqishYoli string) error {
savdolar, xatolar := hammasiniOqi(manba)
if len(savdolar) == 0 {
return fmt.Errorf("bironta ham yaroqli qator yo'q")
}
matn := hisobot(savdolar)
if xato := os.WriteFile(chiqishYoli, []byte(matn), 0o644); xato != nil {
return fmt.Errorf("hisobot yozilmadi: %w", xato)
}
fmt.Print(matn)
fmt.Println("yaroqli qatorlar:", len(savdolar))
if xatolar != nil {
fmt.Println("o'tkazib yuborildi:")
fmt.Println(xatolar)
}
return nil
}
func main() {
manba := `mahsulot,kategoriya,miqdor,narx
Olma, oziq, 3, 1250000
Non, oziq, 10, 500000
Shim, kiyim, 1, 25000000
buzuq
Noutbuk, texnika, 2, 950000000
Choy, oziq, x, 300000`
yol := "savdo-hisobot.txt"
if xato := ishga(manba, yol); xato != nil {
fmt.Println("xato:", xato)
os.Exit(1)
}
saqlangan, _ := os.ReadFile(yol)
fmt.Println("faylga yozildi:", len(saqlangan) > 0)
os.Remove(yol)
}
KATEGORIYA SUMMA
------------------------------
oziq-ovqat 87500.00
kiyim 250000.00
texnika 19000000.00
------------------------------
JAMI 19337500.00
yaroqli qatorlar: 4
o'tkazib yuborildi:
5-qator: ustunlar soni: qator buzuq
7-qator: miqdor: qator buzuq
faylga yozildi: true
Dastur yigirma bo'limning deyarli hammasidan foydalanadi:
| Qism | Bo'lim |
|---|---|
int da pul saqlash | 3 |
strings va strconv | 4 |
switch va qo'riqchi shartlar | 5, 6 |
| Ko'p qiymat qaytarish | 7 |
slice va map | 9, 10 |
| Struktura va metodlar | 11, 12 |
fmt.Stringer | 13 |
%w, errors.Is, errors.Join | 14 |
| Generik yordamchi | 17 |
| Gorutinalar va kanallar | 18, 19 |
Bu dasturni haqiqiy vositaga aylantirish uchun:
- Testlar yozing.
savdo_test.gofaylidafunc TestQatorniOqi(t *testing.T). Keyingo test -race ./.... - Buyruq qatori argumentlari.
flagpaketi bilan fayl nomini tashqaridan oling. - Haqiqiy CSV.
encoding/csvpaketi qo'shtirnoq va vergulni to'g'ri hal qiladi. - JSON chiqish. 11-bo'limdagi teglar bilan.
- Veb interfeys.
net/httpbilan - standart kutubxonaning o'zi yetadi.
Kategoriyaga yangi qiymat qo'shing vaString()ni yangilang.- Chegirma maydonini qo'shib,
Jami()ni o'zgartiring. - Buzuq qator uchun yangi xato turi yozing.
errors.Asbilan qaysi ustun buzilganini aniqlang.- Hisobotga "eng qimmat mahsulot" qatorini qo'shing.
- Generik funksiyani eng kichigini topadigan qilib o'zgartiring.
- Hisobotni miqdor bo'yicha ham guruhlang.
- Faylni
os.ReadFilebilan o'qib, tahlil qiling. - Ishchilar sonini o'zgartirib, natija bir xilligini tekshiring.
go vetvago test -raceni ishga tushiring.
Xulosa #
- Pul eng kichik birlikda, butun son sifatida saqlanadi.
iota+String()- o'qishli va xavfsiz kategoriyalar beradi.- Xatolar nishon xato atrofida qurilgan: tafsilot
%wbilan o'raladi, turerrors.Isbilan tekshiriladi. - Dastur birinchi xatoda to'xtamaydi -
errors.Joinbilan hammasini bir vaqtda ko'rsatadi. - Map bo'yicha hisobot chiqarishda kalitlar albatta saralanadi.
- Generik yordamchi bitta mantiqni har qanday mezonga qo'llaydi.
- Ishchilar hovuzi tartibni saqlamaydi - natija saralanadi.
- Parallellikni o'lchagandan keyin qo'shing.
Tabriklaymiz - siz Go ning asosiy qismini o'rgandingiz. Endi eng yaxshi davom etish yo'li - o'z loyihangizni yozish.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.