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.

🕑 12 daqiqa o‘qish 📄 674 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Ma'lumot turlari
  2. CSV o'qish va xatolar
  3. Buzuq qatorlarni yig'ish
  4. Guruhlash va hisobot
  5. Generik yordamchi
  6. Parallel qayta ishlash
  7. Faylga saqlash
  8. To'liq dastur
  9. Xulosa

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 #

Go
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:

  1. Pul tiyinda, int sifatida saqlanadi - 3-bo'limdagi float64 darsi.
  2. Kategoriya - iota bilan yasalgan o'z turi, String() metodi bilan (12-bo'lim).
  3. Jami() - qiymat resiverli metod, chunki u hech narsani o'zgartirmaydi.
Go
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")
}
Natija
Olma oziq-ovqat
12500.00 so'm
Jami: 37500.00 so'm

CSV o'qish va xatolar #

Go
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))
    }
}
Natija
{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.

%+v Kategoriya ni nega nom bilan chiqardi

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 #

Go
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)
}
Natija
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 #

Go
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))
}
Natija
KATEGORIYA         SUMMA
------------------------------
oziq-ovqat            87500.00
kiyim                250000.00
texnika            19000000.00
------------------------------
JAMI               19337500.00

Uchta narsa birlashdi:

  • map bilan guruhlash (10-bo'lim).
  • Kalitlarni saralab chiqarish - map tartibi tasodifiy bo'lgani uchun.
  • strings.Builder va fmt.Fprintf bilan matn yig'ish (4- va 13-bo'limlar).

slices.Sort(kategoriyalar) ishlaydi, chunki Kategoriya ning asosi int.

Generik yordamchi #

Go
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)
}
Natija
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 #

Go
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))
}
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.

Parallellik har doim ham tezlatmaydi

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 #

Go
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, "   "))
}
Natija
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 #

Go
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)
}
Natija
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:

QismBo'lim
int da pul saqlash3
strings va strconv4
switch va qo'riqchi shartlar5, 6
Ko'p qiymat qaytarish7
slice va map9, 10
Struktura va metodlar11, 12
fmt.Stringer13
%w, errors.Is, errors.Join14
Generik yordamchi17
Gorutinalar va kanallar18, 19
Keyingi qadamlar

Bu dasturni haqiqiy vositaga aylantirish uchun:

  1. Testlar yozing. savdo_test.go faylida func TestQatorniOqi(t *testing.T). Keyin go test -race ./....
  2. Buyruq qatori argumentlari. flag paketi bilan fayl nomini tashqaridan oling.
  3. Haqiqiy CSV. encoding/csv paketi qo'shtirnoq va vergulni to'g'ri hal qiladi.
  4. JSON chiqish. 11-bo'limdagi teglar bilan.
  5. Veb interfeys. net/http bilan - standart kutubxonaning o'zi yetadi.
Amaliy topshiriq
  1. Kategoriya ga yangi qiymat qo'shing va String() ni yangilang.
  2. Chegirma maydonini qo'shib, Jami() ni o'zgartiring.
  3. Buzuq qator uchun yangi xato turi yozing.
  4. errors.As bilan qaysi ustun buzilganini aniqlang.
  5. Hisobotga "eng qimmat mahsulot" qatorini qo'shing.
  6. Generik funksiyani eng kichigini topadigan qilib o'zgartiring.
  7. Hisobotni miqdor bo'yicha ham guruhlang.
  8. Faylni os.ReadFile bilan o'qib, tahlil qiling.
  9. Ishchilar sonini o'zgartirib, natija bir xilligini tekshiring.
  10. go vet va go test -race ni 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 %w bilan o'raladi, tur errors.Is bilan tekshiriladi.
  • Dastur birinchi xatoda to'xtamaydi - errors.Join bilan 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.

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.