DEV Community

Discussion on: Daily Challenge #4 - Checkbook Balancing

Collapse
 
kunde21 profile image
Chad Kunde

Doing a little catch up on challenges. Here's my solution in Go:

Enjoy the naming scheme :evil-smile:

type Checkbook struct {
    bal      int64
    cheques  []Cheque
    totalExp int64
    avgExp   int64
}

type Cheque struct {
    no       int
    category string
    amount   int64
    balance  int64
}

const scaling = 1000

var filter = regexp.MustCompile("[^a-zA-Z0-9 .\n]*")

// New parses a text listing of cheques
func New(chequeList string) (Checkbook, error) {
    // remove unwanted runes
    chequeList = filter.ReplaceAllString(strings.TrimSpace(chequeList), "")
    lines := strings.Split(chequeList, "\n")
    bal, ok := (&big.Float{}).SetString(strings.TrimSpace(lines[0]))
    if !ok {
        return Checkbook{}, errors.Errorf("balance invalid %q", lines[0])
    }
    b, _ := bal.Mul(bal, (&big.Float{}).SetInt64(scaling)).Int64()
    cb := Checkbook{
        bal:     b,
        cheques: make([]Cheque, len(lines)-1),
    }
    tot := int64(0)
    for i, v := range lines[1:] {
        cq, err := parseCheque(v)
        if err != nil {
            return Checkbook{}, err
        }
        cq.balance, b = b-cq.amount, b-cq.amount
        cb.cheques[i] = cq
        tot += cq.amount
    }
    cb.totalExp = tot
    cb.avgExp = tot / int64(len(cb.cheques))
    return cb, nil
}

// parseCheque from an entry line in the checkbook.
func parseCheque(entry string) (Cheque, error) {
    entry = strings.TrimSpace(entry)
    if strings.Count(entry, " ") < 2 {
        return Cheque{}, errors.Errorf("cheque line invalid %q", entry)
    }
    fi, li := strings.Index(entry, " "), strings.LastIndex(entry, " ")
    chNo, cat, amt := entry[:fi], strings.TrimSpace(entry[fi:li]), entry[li+1:]
    no, err := strconv.Atoi(chNo)
    if err != nil {
        return Cheque{}, err
    }
    cq := Cheque{no: no, category: cat}
    bal, ok := (&big.Float{}).SetString(amt)
    if !ok {
        return Cheque{}, errors.Errorf("amount invalid %q", amt)
    }
    cq.amount, _ = bal.Mul(bal, (&big.Float{}).SetInt64(scaling)).Int64()
    return cq, nil
}

// String display of entire checkbook.
func (cb Checkbook) String() string {
    b := &strings.Builder{}
    fmt.Fprintf(b, "Original Balance: %0.2f\n", float64(cb.bal)/scaling)
    for _, v := range cb.cheques {
        v.Write(b)
        fmt.Fprintln(b)
    }
    fmt.Fprintf(b, "Total Expenses: %0.2f\n", float64(cb.totalExp)/scaling)
    fmt.Fprintf(b, "Average Expenses: %0.2f\n", float64(cb.avgExp)/scaling)
    return b.String()
}

// String display of Cheque data.
func (cq Cheque) String() string {
    return fmt.Sprintf("No: %d, Category: %s, Amount: %0.2f, Balance: %0.2f",
        cq.no, cq.category, float64(cq.amount)/scaling, float64(cq.balance)/scaling)
}

// Write string display to writer.
func (cq Cheque) Write(w io.Writer) (int, error) {
    return fmt.Fprintf(w, "No: %d, Category: %s, Amount: %0.2f, Balance: %0.2f",
        cq.no, cq.category, float64(cq.amount)/scaling, float64(cq.balance)/scaling)
}