update: directory structure

This commit is contained in:
Inanc Gumus
2019-08-25 20:58:36 +03:00
parent 0f07e4814e
commit 912d789933
74 changed files with 0 additions and 0 deletions

View File

@@ -1,73 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
func main() {
nums := []int{2, 3, 7}
fmt.Printf("nums : %d\n", nums)
n := avgNoVariadic(nums)
fmt.Printf("avgNoVariadic : %d\n", n)
n = avg(2, 3, 7)
fmt.Printf("avg(2, 3, 7) : %d\n", n)
n = avg(2, 3, 7, 8)
fmt.Printf("avg(2, 3, 7, 8) : %d\n", n)
// use ... to pass a slice
n = avg(nums...)
fmt.Printf("avg(nums...) : %d\n", n)
// uses the existing slice
double(nums...)
fmt.Printf("double(nums...) : %d\n", nums)
// creates a new slice
double(4, 6, 14)
fmt.Printf("double(4, 6, 14): %d\n", nums)
// creates a nil slice
fmt.Printf("\nmain.nums : %p\n", nums)
investigate("passes main.nums", nums...)
investigate("passes main.nums", nums...)
investigate("passes args", 4, 6, 14)
investigate("passes args", 4, 6, 14)
investigate("no args")
}
func investigate(msg string, nums ...int) {
fmt.Printf("investigate.nums: %12p -> %s\n", nums, msg)
if len(nums) > 0 {
fmt.Printf("\tfirst element: %d\n", nums[0])
}
}
func double(nums ...int) {
for i := range nums {
nums[i] *= 2
}
}
func avg(nums ...int) int {
return sum(nums) / len(nums)
}
func avgNoVariadic(nums []int) int {
return sum(nums) / len(nums)
}
func sum(nums []int) (total int) {
for _, n := range nums {
total += n
}
return
}

View File

@@ -1,52 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type filterFunc func(int) bool
func main() {
signatures()
funcValues()
}
func isEven(n int) bool {
return n%2 == 0
}
func isOdd(m int) bool {
return m%2 == 1
}
func signatures() {
fmt.Println("••• FUNC SIGNATURES (TYPES) •••")
fmt.Printf("isEven : %T\n", isEven)
fmt.Printf("isOdd : %T\n", isOdd)
}
func funcValues() {
fmt.Println("\n••• FUNC VALUES (VARS) •••")
// var value func(int) bool
var value filterFunc
fmt.Printf("value nil? : %t\n", value == nil)
value = isEven
fmt.Printf("value(2) : %t\n", value(2))
fmt.Printf("isEven(2) : %t\n", isEven(2))
value = isOdd
fmt.Printf("value(1) : %t\n", value(1))
fmt.Printf("isOdd(1) : %t\n", isOdd(1))
fmt.Printf("value nil? : %t\n", value == nil)
fmt.Printf("value : %p\n", value)
fmt.Printf("isEven : %p\n", isEven)
fmt.Printf("isOdd : %p\n", isOdd)
}

View File

@@ -1,82 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strings"
"unicode"
)
type filterFunc func(int) bool
func main() {
signatures()
fmt.Println("\n••• WITHOUT FUNC VALUES •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("evens : %d\n", filterEvens(nums...))
fmt.Printf("odds : %d\n", filterOdds(nums...))
fmt.Println("\n••• FUNC VALUES •••")
fmt.Printf("evens : %d\n", filter(isEven, nums...))
fmt.Printf("odds : %d\n", filter(isOdd, nums...))
fmt.Println("\n••• MAPPING •••")
fmt.Println(strings.Map(unpunct, "hello!!! HOW ARE YOU???? :))"))
fmt.Println(strings.Map(unpunct, "TIME IS UP!"))
}
func unpunct(r rune) rune {
if unicode.IsPunct(r) {
return -1
}
return unicode.ToLower(r)
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func filterOdds(nums ...int) (filtered []int) {
for _, n := range nums {
if isOdd(n) {
filtered = append(filtered, n)
}
}
return
}
func filterEvens(nums ...int) (filtered []int) {
for _, n := range nums {
if isEven(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool {
return n%2 == 0
}
func isOdd(m int) bool {
return m%2 == 1
}
func signatures() {
fmt.Println("••• FUNC SIGNATURES (TYPES) •••")
fmt.Printf("isEven : %T\n", isEven)
fmt.Printf("isOdd : %T\n", isOdd)
}

View File

@@ -1,76 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type filterFunc func(int) bool
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println("••• FUNC VALUES •••")
fmt.Printf("evens : %d\n", filter(isEven, nums...))
fmt.Printf("odds : %d\n", filter(isOdd, nums...))
fmt.Printf("> 3 : %d\n", filter(greaterThan3, nums...))
fmt.Printf("> 6 : %d\n", filter(greaterThan6, nums...))
// ========================================================================
fmt.Println("\n••• CLOSURES •••")
var min int
greaterThan := func(n int) bool {
return n > min
}
min = 3
fmt.Printf("> 3 : %d\n", filter(greaterThan, nums...))
min = 6
fmt.Printf("> 6 : %d\n", filter(greaterThan, nums...))
// min = 1
// fmt.Printf("> 1 : %d\n", filter(greaterThan, nums...))
// min = 2
// fmt.Printf("> 2 : %d\n", filter(greaterThan, nums...))
// min = 3
// fmt.Printf("> 3 : %d\n", filter(greaterThan, nums...))
var filterers []filterFunc
for i := 1; i <= 3; i++ {
current := i
filterers = append(filterers, func(n int) bool {
min = current
return greaterThan(n)
})
}
printer(filterers, nums...)
}
func printer(filterers []filterFunc, nums ...int) {
for i, filterer := range filterers {
fmt.Printf("> %d : %d\n", i+1, filter(filterer, nums...))
}
}
func greaterThan6(n int) bool { return n > 6 }
func greaterThan3(n int) bool { return n > 3 }
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}

View File

@@ -1,58 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
type filterFunc func(int) bool
func main() {
fmt.Println("••• HIGHER-ORDER FUNCS •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
odd := reverse(reverse(isEven))
fmt.Printf("reversed : %t\n", odd(8))
fmt.Printf("> 3 : %d\n", filter(greater(3), nums...))
fmt.Printf("> 6 : %d\n", filter(greater(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(lesseq(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(reverse(greater(6)), nums...))
}
func reverse(f filterFunc) filterFunc {
return func(n int) bool {
return !f(n)
}
}
func greater(min int) filterFunc {
return func(n int) bool {
return n > min
}
}
func lesseq(max int) filterFunc {
return func(n int) bool {
return n <= max
}
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }

View File

@@ -1,97 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
type filterFunc func(int) bool
func main() {
fmt.Println("••• HIGHER-ORDER FUNCS •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
odd := reverse(reverse(isEven))
fmt.Printf("reversed : %t\n", odd(8))
fmt.Printf("> 3 : %d\n", filter(greater(3), nums...))
fmt.Printf("> 6 : %d\n", filter(greater(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(lesseq(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(reverse(greater(6)), nums...))
fmt.Println("\n••• CLOSURES •••")
fmt.Printf("uniques : %d\n", filter(uniques(), 1, 1, 2, 3, 3))
dups := reverse(uniques())
fmt.Printf("dups : %v\n", filter(dups, 1, 1, 2, 3, 3))
dups = reverse(uniques())
fmt.Printf("dups dups : %v\n", filter(dups, 1, 1, 2, 3, 3, 3, 3))
dups = reverse(uniques())
out := filter(dups, 1, 1, 2, 3, 3, 3, 3)
fmt.Printf("dups uniqs : %v\n", filter(uniques(), out...))
dups = reverse(uniques())
chained := chain(reverse(greater(1)), dups, uniques())
fmt.Printf("dups chain : %v\n", filter(chained, 1, 1, 2, 3, 3, 3, 3, 0, 0))
}
func chain(filters ...filterFunc) filterFunc {
return func(n int) bool {
for _, next := range filters {
if !next(n) {
return false
}
}
return true
}
}
func uniques() filterFunc {
seen := make(map[int]bool)
return func(n int) (ok bool) {
if !seen[n] {
seen[n], ok = true, true
}
return
}
}
func reverse(f filterFunc) filterFunc {
return func(n int) bool {
return !f(n)
}
}
func greater(min int) filterFunc {
return func(n int) bool {
return n > min
}
}
func lesseq(max int) filterFunc {
return func(n int) bool {
return n <= max
}
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }

View File

@@ -1,7 +0,0 @@
SHELL := /bin/bash
r:
go run . < ../logs/log.txt
t:
time go run . < ../logs/log.txt

View File

@@ -1,46 +0,0 @@
package main
import (
"io"
"sort"
"strconv"
c "github.com/wcharczuk/go-chart"
)
func chartWriter(w io.Writer) outputFn {
return func(res []result) error {
return chartWrite(w, res)
}
}
func chartWrite(w io.Writer, res []result) error {
sort.Slice(res, func(i, j int) bool {
return res[i].domain > res[j].domain
})
donut := c.DonutChart{
Title: "Total Visits Per Domain",
TitleStyle: c.Style{
FontSize: 35,
Show: true,
FontColor: c.ColorAlternateGreen,
},
Width: 1920,
Height: 800,
}
for _, r := range res {
v := c.Value{
Label: r.domain + r.page + ": " + strconv.Itoa(r.visits),
Value: float64(r.visits),
Style: c.Style{
FontSize: 14,
},
}
donut.Values = append(donut.Values, v)
}
return donut.Render(c.SVG, w)
}

View File

@@ -1,33 +0,0 @@
package main
import (
"errors"
"fmt"
"strconv"
)
// field helps for field parsing
type field struct{ err error }
// uatoi parses an unsigned integer string and saves the error.
// it assumes that the val is unsigned.
// for ease of usability: it returns an int instead of uint.
func (f *field) uatoi(name, val string) int {
n, err := strconv.Atoi(val)
if err != nil || n < 0 {
f.err = fmt.Errorf("incorrect field -> %q = %q", name, val)
}
return n
}
func atoi(input []byte) (int, error) {
val := 0
for i := 0; i < len(input); i++ {
char := input[i]
if char < '0' || char > '9' {
return 0, errors.New("invalid number")
}
val = val*10 + int(char) - '0'
}
return val, nil
}

View File

@@ -1,34 +0,0 @@
package main
import "strings"
func noopFilter(r result) bool {
return true
}
func notUsing(filter filterFn) filterFn {
return func(r result) bool {
return !filter(r)
}
}
func domainExtFilter(domains ...string) filterFn {
return func(r result) bool {
for _, domain := range domains {
if strings.HasSuffix(r.domain, "."+domain) {
return true
}
}
return false
}
}
func domainFilter(domain string) filterFn {
return func(r result) bool {
return strings.Contains(r.domain, domain)
}
}
func orgDomainsFilter(r result) bool {
return strings.HasSuffix(r.domain, ".org")
}

View File

@@ -1,20 +0,0 @@
package main
// domainGrouper groups by domain.
// but it keeps the other fields.
// for example: it returns pages as well, but you shouldn't use them.
// exercise: write a function that erases superfluous data.
func domainGrouper(r result) string {
return r.domain
}
func pageGrouper(r result) string {
return r.domain + r.page
}
// groupBy allocates map unnecessarily
func noopGrouper(r result) string {
// with something like:
// return randomStrings()
return ""
}

View File

@@ -1,44 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"os"
)
func main() {
p := pipeline{
read: textReader(os.Stdin),
write: textWriter(os.Stdout),
filter: notUsing(domainExtFilter("io", "com")),
group: domainGrouper,
}
// var p pipeline
// p.
// filterBy(notUsing(domainExtFilter("io", "com"))).
// groupBy(domainGrouper)
if err := p.start(); err != nil {
fmt.Println("> Err:", err)
}
}
// []outputter{textFile("results.txt"), chartFile("graph.png")}
// func outputs(w io.Writer) outputFn {
// tw := textWriter(w)
// cw := chartWriter(w)
// return func(rs []result) error {
// err := tw(rs)
// err = cw(rs)
// return err
// }
// }

View File

@@ -1,78 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "os"
type (
processFn func(r result)
inputFn func(processFn) error
outputFn func([]result) error
filterFn func(result) (include bool)
groupFn func(result) (key string)
)
type pipeline struct {
read inputFn
write outputFn
filter filterFn
group groupFn
}
func (p *pipeline) filterBy(f filterFn) *pipeline { p.filter = f; return p }
func (p *pipeline) groupBy(f groupFn) *pipeline { p.group = f; return p }
func (p *pipeline) from(f inputFn) *pipeline { p.read = f; return p }
func (p *pipeline) to(f outputFn) *pipeline { p.write = f; return p }
func (p *pipeline) defaults() {
if p.filter == nil {
p.filter = noopFilter
}
if p.group == nil {
p.group = domainGrouper
}
if p.read == nil {
p.read = textReader(os.Stdin)
}
if p.write == nil {
p.write = textWriter(os.Stdout)
}
}
func (p *pipeline) start() error {
p.defaults()
// retrieve and process the lines
sum := make(map[string]result)
process := func(r result) {
if !p.filter(r) {
return
}
k := p.group(r)
sum[k] = r.add(sum[k])
}
// return err from input reader
if err := p.read(process); err != nil {
return err
}
// prepare the results for outputting
var out []result
for _, res := range sum {
out = append(out, res)
}
// return err from output reader
return p.write(out)
}

View File

@@ -1,83 +0,0 @@
package main
import (
"fmt"
"strings"
)
const fieldsLength = 4
// result stores the parsed result for a domain
type result struct {
domain string
page string
visits int
uniques int
}
// add adds the metrics of another result
func (r result) add(other result) result {
r.visits += other.visits
r.uniques += other.uniques
return r
}
// parseFields parses and returns the parsing result
func parseFields(line string) (r result, err error) {
fields := strings.Fields(line)
if len(fields) != fieldsLength {
return r, fmt.Errorf("wrong number of fields %q", fields)
}
r.domain = fields[0]
r.page = fields[1]
f := new(field)
r.visits = f.uatoi("visits", fields[2])
r.uniques = f.uatoi("uniques", fields[3])
return r, f.err
}
func fastParseFields(data []byte) (res result, err error) {
const separator = ' '
var findex int
for i, j := 0, 0; i < len(data); i++ {
c := data[i]
last := len(data) == i+1
if c != separator && !last {
continue
}
if last {
i = len(data)
}
switch fval := data[j:i]; findex {
case 0:
res.domain = string(fval)
case 1:
res.page = string(fval)
case 2:
res.visits, err = atoi(fval)
case 3:
res.uniques, err = atoi(fval)
}
if err != nil {
return res, err
}
j = i + 1
findex++
}
if findex != fieldsLength {
err = fmt.Errorf("wrong number of fields %q", data)
}
return res, err
}

View File

@@ -1,39 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"fmt"
"io"
)
func textReader(r io.Reader) inputFn {
return func(process processFn) error {
var (
l = 1
in = bufio.NewScanner(r)
)
for in.Scan() {
r, err := fastParseFields(in.Bytes())
// r, err := parseFields(in.Text())
if err != nil {
return fmt.Errorf("line %d: %v", l, err)
}
process(r)
l++
}
if c, ok := r.(io.Closer); ok {
c.Close()
}
return in.Err()
}
}

View File

@@ -1,50 +0,0 @@
package main
import (
"fmt"
"io"
"sort"
"strings"
)
// TODO: sort by result key interfaces section
const (
// DOMAINS PAGES VISITS UNIQUES
// ^ ^ ^ ^
// | | | |
header = "%-25s %-10s %10s %10s\n"
line = "%-25s %-10s %10d %10d\n"
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
dash = "-"
dashLength = 58
)
func textWriter(w io.Writer) outputFn {
return func(res []result) error {
sort.Slice(res, func(i, j int) bool {
return res[i].domain > res[j].domain
})
var total result
fmt.Fprintf(w, header, "DOMAINS", "PAGES", "VISITS", "UNIQUES")
fmt.Fprintf(w, strings.Repeat(dash, dashLength)+"\n")
for _, r := range res {
total = total.add(r)
fmt.Fprintf(w, line, r.domain, r.page, r.visits, r.uniques)
}
fmt.Fprintf(w, footer, "", total.visits, total.uniques)
return nil
}
}
func noWhere() outputFn {
return func(res []result) error {
return nil
}
}

View File

@@ -1,34 +0,0 @@
## CHANGES
### PROBLEM
+ adding new fields makes the code complex
+ needs to update: `result`, `parser`, `summarizer`
+ needs to add new fields to `parser`: `totalVisits` + `totalUniques`
+ in `parse()`: repeating line errors
+ if we parsing out of it we'd need to have *parser — superfluous
### SOLUTION
+ move all the result related logic to result.go
+ move `parser.go/result` -> `result.go`
+ move `parser.go/parsing` logic -> `result.go`
+ add `addResult` -> `result.go`
+ remove `parser struct`'s: `totalVisits`, `totalUniques`
+ change `update()`'s last line: `p.sum[r.domain] = addResult`
+ remove `(line #d)` errors from `result.go`
+ add: `return r, err` named params are error prone
+ always check for the error first
+ `if r.visits < 0 || err != nil` -> `if err != nil || r.visits < 0`
+ `parser.go`: check the `parseFields()`:
```golang
r, err := parseFields(line)
if err != nil {
p.lerr = fmt.Errorf("line %d: %v", p.lines, err)
}```
+ - `parser.go` and `summarize.go`
- remove `total int`
- let `summarize()` calculate the totals

View File

@@ -1,36 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
p := newParser()
in := bufio.NewScanner(os.Stdin)
for in.Scan() {
parsed := parse(p, in.Text())
update(p, parsed)
}
summarize(p)
dumpErrs([]error{in.Err(), err(p)})
}
// dumpErrs simplifies handling multiple errors
func dumpErrs(errs []error) {
for _, err := range errs {
if err != nil {
fmt.Println("> Err:", err)
}
}
}

View File

@@ -1,74 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
// parser keeps track of the parsing
type parser struct {
sum map[string]result // metrics per domain
domains []string // unique domain names
lines int // number of parsed lines (for the error messages)
lerr error // the last error occurred
// totalVisits int // total visits for all domains
// totalUniques int // total uniques for all domains
}
// newParser constructs, initializes and returns a new parser
func newParser() *parser {
return &parser{sum: make(map[string]result)}
}
// parse a log line and return the result
func parse(p *parser, line string) (r result) {
if p.lerr != nil {
return
}
p.lines++
r, err := parseResult(line)
if err != nil {
p.lerr = fmt.Errorf("line %d: %v", p.lines, err)
}
return r
}
// update the parsing results
func update(p *parser, r result) {
if p.lerr != nil {
return
}
// Collect the unique domains
cur, ok := p.sum[r.domain]
if !ok {
p.domains = append(p.domains, r.domain)
}
// Keep track of total and per domain visits
// p.totalVisits += r.visits
// p.totalUniques += r.uniques
// create and assign a new copy of `visit`
// p.sum[r.domain] = result{
// domain: r.domain,
// visits: r.visits + cur.visits,
// uniques: r.uniques + cur.uniques,
// }
p.sum[r.domain] = addResult(r, cur)
}
// err returns the last error encountered
func err(p *parser) error {
return p.lerr
}

View File

@@ -1,53 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strconv"
"strings"
)
const fieldsLength = 4
// result stores the parsed result for a domain
type result struct {
domain, page string
visits, uniques int
// add more metrics if needed
}
// parseResult from a log line
func parseResult(line string) (r result, err error) {
fields := strings.Fields(line)
if len(fields) != fieldsLength {
return r, fmt.Errorf("wrong input: %v", fields)
}
r.domain = fields[0]
r.page = fields[1]
r.visits, err = strconv.Atoi(fields[2])
if err != nil || r.visits < 0 {
return r, fmt.Errorf("wrong input: %q", fields[2])
}
r.uniques, err = strconv.Atoi(fields[3])
if err != nil || r.uniques < 0 {
return r, fmt.Errorf("wrong input: %q", fields[3])
}
return r, nil
}
// addResult to another one
func addResult(r, other result) result {
r.visits += other.visits
r.uniques += other.uniques
return r
}

View File

@@ -1,43 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"sort"
"strings"
)
const (
// DOMAINS PAGES VISITS UNIQUES
// ^ ^ ^ ^
// | | | |
header = "%-25s %-10s %10s %10s\n"
line = "%-25s %-10s %10d %10d\n"
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
dash = "-"
dashLength = 58
)
// summarize summarizes and prints the parsing result
func summarize(p *parser) {
sort.Strings(p.domains)
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
fmt.Println(strings.Repeat("-", dashLength))
var total result
for _, domain := range p.domains {
r := p.sum[domain]
total = addResult(total, r)
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
}
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
}

View File

@@ -1,61 +0,0 @@
### PROBLEM
+ `main.go` (api client) does a lot of things:
+ read the log input
+ parse line by line
+ updates the results
+ display the results
+ inflexible:
+ filter by extension (can change)
+ group by domain (can change) — group by page?
## SOLUTION
+ hide the parsing api from the client
+ move `main.go/scanner` -> `parser.go/parse()`
+ add `main.go`: err handling from `parse()`
+ `parser.go/parse()` -> return err directly
+ remove: `if p.lerr != nil { return }` from parse() and update()
+ remove: `dumpErrs`
+ remove: `parser.go/err()`
+ remove `parser.go/lerr`
+ return `in.Err()` from `parse()`
+ remove: `p.lines++`
+ `return r, fmt.Errorf("line %d: %v", p.lines, err)`
+ remove: `lines int`
+ `parse()` and `parse()` becomes:
```golang
func parse(p *parser, line string) (result, error) {
return parseFields(line)
}
func parse(p *parser) {
// ...
r, err := parse(p, in.Text())
if err != nil {
return fmt.Errorf("line %d: %v", p.lines, err)
}
// ...
}
```
+ remove `parse()`
+ call `parseFields` directly in `parse()`:
```go
var (
l = 1
in = bufio.NewScanner(os.Stdin)
)
for in.Scan() {
r, err := parseFields(in.Text())
if err != nil {
return fmt.Errorf("line %d: %v", l, err)
}
update(p, r)
l++
}
```

View File

@@ -1,23 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
func main() {
p := newParser()
if err := parse(p); err != nil {
fmt.Println("> Err:", err)
return
}
summarize(p)
}

View File

@@ -1,57 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"fmt"
"os"
)
// parser keeps track of the parsing
type parser struct {
sum map[string]result // metrics per domain
domains []string // unique domain names
}
// newParser constructs, initializes and returns a new parser
func newParser() *parser {
return &parser{sum: make(map[string]result)}
}
// parse the log lines and return results
func parse(p *parser) error {
var (
l = 1
in = bufio.NewScanner(os.Stdin)
)
for in.Scan() {
r, err := parseResult(in.Text())
if err != nil {
return fmt.Errorf("line %d: %v", l, err)
}
l++
update(p, r)
}
return in.Err()
}
// update the parsing results
func update(p *parser, r result) {
// Collect the unique domains
if _, ok := p.sum[r.domain]; !ok {
p.domains = append(p.domains, r.domain)
}
// create and assign a new copy of `visit`
p.sum[r.domain] = addResult(r, p.sum[r.domain])
}

View File

@@ -1,53 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strconv"
"strings"
)
const fieldsLength = 4
// result stores the parsed result for a domain
type result struct {
domain, page string
visits, uniques int
// add more metrics if needed
}
// parseResult from a log line
func parseResult(line string) (r result, err error) {
fields := strings.Fields(line)
if len(fields) != fieldsLength {
return r, fmt.Errorf("wrong input: %v", fields)
}
r.domain = fields[0]
r.page = fields[1]
r.visits, err = strconv.Atoi(fields[2])
if err != nil || r.visits < 0 {
return r, fmt.Errorf("wrong input: %q", fields[2])
}
r.uniques, err = strconv.Atoi(fields[3])
if err != nil || r.uniques < 0 {
return r, fmt.Errorf("wrong input: %q", fields[3])
}
return r, nil
}
// addResult to another one
func addResult(r, other result) result {
r.visits += other.visits
r.uniques += other.uniques
return r
}

View File

@@ -1,48 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"sort"
"strings"
)
// summarize summarizes and prints the parsing result
// + violation: accesses the parsing internals: p.domains + p.sum + p.total
// + give it the []result only.
// + let it calculate the total.
const (
// DOMAINS PAGES VISITS UNIQUES
// ^ ^ ^ ^
// | | | |
header = "%-25s %-10s %10s %10s\n"
line = "%-25s %-10s %10d %10d\n"
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
dash = "-"
dashLength = 58
)
// summarize summarizes and prints the parsing result
func summarize(p *parser) {
sort.Strings(p.domains)
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
fmt.Println(strings.Repeat("-", dashLength))
var total result
for _, domain := range p.domains {
r := p.sum[domain]
total = addResult(total, r)
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
}
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
}

View File

@@ -1,20 +0,0 @@
### PROBLEM
+ `summarize()` knows a lot about the internals of the `parser`.
+ coupled to the `parser`.
## SOLUTION
+ remove: `parser.go` `sum` and `domains` fields
+ remove: `parser.go/newParser()`
+ change: `parser.go/parse(p *parser) error` -> `parse() ([]result, error)`
+ initialize: `sum` inside `parse()`
+ remove: `update()`
+ call: `sum` update in the `parse()`
+ collect the grouped results and return them from `parser()`
+ `summarize(p *parser)` -> `summarize([]result)`
+ in `summarize()`
+ `sort.Slice`
+ range over `[]result`
+ `main.go`
+ just: `res, err := parse()`

View File

@@ -1,22 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
func main() {
res, err := parse()
if err != nil {
fmt.Println("> Err:", err)
return
}
summarize(res)
}

View File

@@ -1,48 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"fmt"
"os"
)
// parser keeps track of the parsing
type parser struct {
}
// parse the log lines and return results
func parse() ([]result, error) {
var (
l = 1
in = bufio.NewScanner(os.Stdin)
sum = make(map[string]result)
)
// parse the log lines
for in.Scan() {
r, err := parseResult(in.Text())
if err != nil {
return nil, fmt.Errorf("line %d: %v", l, err)
}
l++
// group the log lines by domain
sum[r.domain] = addResult(r, sum[r.domain])
}
// collect the grouped results
res := make([]result, 0, len(sum))
for _, r := range sum {
res = append(res, r)
}
return res, in.Err()
}

View File

@@ -1,53 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strconv"
"strings"
)
const fieldsLength = 4
// result stores the parsed result for a domain
type result struct {
domain, page string
visits, uniques int
// add more metrics if needed
}
// parseResult from a log line
func parseResult(line string) (r result, err error) {
fields := strings.Fields(line)
if len(fields) != fieldsLength {
return r, fmt.Errorf("wrong input: %v", fields)
}
r.domain = fields[0]
r.page = fields[1]
r.visits, err = strconv.Atoi(fields[2])
if err != nil || r.visits < 0 {
return r, fmt.Errorf("wrong input: %q", fields[2])
}
r.uniques, err = strconv.Atoi(fields[3])
if err != nil || r.uniques < 0 {
return r, fmt.Errorf("wrong input: %q", fields[3])
}
return r, nil
}
// addResult to another one
func addResult(r, other result) result {
r.visits += other.visits
r.uniques += other.uniques
return r
}

View File

@@ -1,49 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"sort"
"strings"
)
// summarize summarizes and prints the parsing result
// + violation: accesses the parsing internals: p.domains + p.sum + p.total
// + give it the []result only.
// + let it calculate the total.
const (
// DOMAINS PAGES VISITS UNIQUES
// ^ ^ ^ ^
// | | | |
header = "%-25s %-10s %10s %10s\n"
line = "%-25s %-10s %10d %10d\n"
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
dash = "-"
dashLength = 58
)
// summarize summarizes and prints the parsing result
func summarize(res []result) {
// sort.Strings(p.domains)
sort.Slice(res, func(i, j int) bool {
return res[i].domain <= res[j].domain
})
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
fmt.Println(strings.Repeat("-", dashLength))
var total result
for _, r := range res {
total = addResult(total, r)
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
}
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
}

View File

@@ -1,14 +0,0 @@
### PROBLEM
+ `parser.go/parse()` also does updating. back to square one.
+ we need to extract the reusable behavior: scanning.
+ inflexible:
+ adding a filter is hard. needs to change the `scan()` code.
+ adding a grouper is also hard. domain grouping is hardcoded.
## SOLUTION
+
## IDEAS:
+ make domain filter accept variadic args

View File

@@ -1,36 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
/*
p := pipeline{
read: textReader(os.Stdin),
write: textWriter(os.Stdout),
filterBy: notUsing(domainExtFilter("io")),
groupBy: domainGrouper,
}
if err := start(p); err != nil {
fmt.Println("> Err:", err)
}
*/
func main() {
p := newParser()
if err := parse(p); err != nil {
fmt.Println("> Err:", err)
return
}
summarize(p)
}

View File

@@ -1,40 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
// parser keeps track of the parsing
type parser struct {
sum map[string]result // metrics per domain
domains []string // unique domain names
}
// newParser constructs, initializes and returns a new parser
func newParser() *parser {
return &parser{sum: make(map[string]result)}
}
// parse all the log lines and update the results
func parse(p *parser) error {
process := func(r result) {
update(p, r)
}
err := scan(process)
return err
}
func update(p *parser, r result) {
// Collect the unique domains
if _, ok := p.sum[r.domain]; !ok {
p.domains = append(p.domains, r.domain)
}
// create and assign a new copy of `visit`
p.sum[r.domain] = addResult(r, p.sum[r.domain])
}

View File

@@ -1,53 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strconv"
"strings"
)
const fieldsLength = 4
// result stores the parsed result for a domain
type result struct {
domain, page string
visits, uniques int
// add more metrics if needed
}
// parseResult from a log line
func parseResult(line string) (r result, err error) {
fields := strings.Fields(line)
if len(fields) != fieldsLength {
return r, fmt.Errorf("wrong input: %v", fields)
}
r.domain = fields[0]
r.page = fields[1]
r.visits, err = strconv.Atoi(fields[2])
if err != nil || r.visits < 0 {
return r, fmt.Errorf("wrong input: %q", fields[2])
}
r.uniques, err = strconv.Atoi(fields[3])
if err != nil || r.uniques < 0 {
return r, fmt.Errorf("wrong input: %q", fields[3])
}
return r, nil
}
// addResult to another one
func addResult(r, other result) result {
r.visits += other.visits
r.uniques += other.uniques
return r
}

View File

@@ -1,35 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"fmt"
"os"
)
type processFn func(r result)
func scan(process processFn) error {
var (
l = 1
in = bufio.NewScanner(os.Stdin)
)
for in.Scan() {
r, err := parseResult(in.Text())
if err != nil {
return fmt.Errorf("line %d: %v", l, err)
}
l++
process(r)
}
return in.Err()
}

View File

@@ -1,48 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"sort"
"strings"
)
// summarize summarizes and prints the parsing result
// + violation: accesses the parsing internals: p.domains + p.sum + p.total
// + give it the []result only.
// + let it calculate the total.
const (
// DOMAINS PAGES VISITS UNIQUES
// ^ ^ ^ ^
// | | | |
header = "%-25s %-10s %10s %10s\n"
line = "%-25s %-10s %10d %10d\n"
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
dash = "-"
dashLength = 58
)
// summarize summarizes and prints the parsing result
func summarize(p *parser) {
sort.Strings(p.domains)
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
fmt.Println(strings.Repeat("-", dashLength))
var total result
for _, domain := range p.domains {
r := p.sum[domain]
total = addResult(total, r)
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
}
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
}

View File

@@ -1,7 +0,0 @@
### PROBLEM
+ ...
## SOLUTION
+ `parser struct` -> `pipeline struct`
+ `parse()` -> `pipe(pipeline)`

View File

@@ -1,29 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
)
func main() {
pipes := pipeline{
// read: textParser(),
// write: textSummary(),
// filterBy: notUsing(domainExtFilter("io", "com")),
// groupBy: domainGrouper,
}
res, err := pipe(pipes)
if err != nil {
fmt.Println("> Err:", err)
return
}
summarize(res)
}

View File

@@ -1,48 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"fmt"
"os"
)
// pipeline determines the behavior of log processing
type pipeline struct {
}
// pipe the log lines through funcs and produce a result
func pipe(opts pipeline) ([]result, error) {
var (
l = 1
in = bufio.NewScanner(os.Stdin)
sum = make(map[string]result)
)
// parse the log lines
for in.Scan() {
r, err := parseResult(in.Text())
if err != nil {
return nil, fmt.Errorf("line %d: %v", l, err)
}
l++
// group the log lines by domain
sum[r.domain] = addResult(r, sum[r.domain])
}
// collect the grouped results
res := make([]result, 0, len(sum))
for _, r := range sum {
res = append(res, r)
}
return res, in.Err()
}

View File

@@ -1,53 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"strconv"
"strings"
)
const fieldsLength = 4
// result stores the parsed result for a domain
type result struct {
domain, page string
visits, uniques int
// add more metrics if needed
}
// parseResult from a log line
func parseResult(line string) (r result, err error) {
fields := strings.Fields(line)
if len(fields) != fieldsLength {
return r, fmt.Errorf("wrong input: %v", fields)
}
r.domain = fields[0]
r.page = fields[1]
r.visits, err = strconv.Atoi(fields[2])
if err != nil || r.visits < 0 {
return r, fmt.Errorf("wrong input: %q", fields[2])
}
r.uniques, err = strconv.Atoi(fields[3])
if err != nil || r.uniques < 0 {
return r, fmt.Errorf("wrong input: %q", fields[3])
}
return r, nil
}
// addResult to another one
func addResult(r, other result) result {
r.visits += other.visits
r.uniques += other.uniques
return r
}

View File

@@ -1,48 +0,0 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"sort"
"strings"
)
// summarize summarizes and prints the parsing result
// + violation: accesses the parsing internals: p.domains + p.sum + p.total
// + give it the []result only.
// + let it calculate the total.
const (
// DOMAINS PAGES VISITS UNIQUES
// ^ ^ ^ ^
// | | | |
header = "%-25s %-10s %10s %10s\n"
line = "%-25s %-10s %10d %10d\n"
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
dash = "-"
dashLength = 58
)
// summarize summarizes and prints the parsing result
func summarize(res []result) {
sort.Slice(res, func(i, j int) bool {
return res[i].domain <= res[j].domain
})
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
fmt.Println(strings.Repeat("-", dashLength))
var total result
for _, r := range res {
total = addResult(total, r)
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
}
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
}