diff --git a/25-project-file-parser-structs/log.txt b/25-project-file-parser-structs/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/25-project-file-parser-structs/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/25-project-file-parser-structs/main.go b/25-project-file-parser-structs/main.go new file mode 100644 index 0000000..09314d8 --- /dev/null +++ b/25-project-file-parser-structs/main.go @@ -0,0 +1,80 @@ +// 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" + "strconv" + "strings" +) + +// domain represents a domain log record +type domain struct { + name string + visits int +} + +// parser parses a log file and provides an iterator to iterate upon the domains +// +// the parser struct is carefully crafted to be usable using its zero values except the map field +type parser struct { + sum map[string]int // visits per unique domain + domains []domain // unique domain names + total int // total visits to all domains + lines int // number of parsed lines (for the error messages) +} + +func main() { + in := bufio.NewScanner(os.Stdin) + + p := parser{ + sum: make(map[string]int), + } + + // Scan the standard-in line by line + for in.Scan() { + p.lines++ + + // Parse the fields + fields := strings.Fields(in.Text()) + if len(fields) != 2 { + fmt.Printf("wrong input: %v (line #%d)\n", fields, p.lines) + return + } + name, visits := fields[0], fields[1] + + // Sum the total visits per domain + n, _ := strconv.Atoi(visits) + if n < 0 { + fmt.Printf("wrong input: %q (line #%d)\n", visits, p.lines) + return + } + + d := domain{name: name, visits: n} + + // Collect the unique domains + if _, ok := p.sum[name]; !ok { + p.domains = append(p.domains, d) + } + + p.sum[d.name] += n + p.total += d.visits + } + + // Print the visits per domain + for _, d := range p.domains { + vis := p.sum[d.name] + + fmt.Printf("%-25s -> %d\n", d.name, vis) + } + + // Print the total visits for all domains + fmt.Printf("\n%-25s -> %d\n", "TOTAL", p.total) +} diff --git a/x-tba/6-methods/xxx-project-file-parser/01-funcs/log.txt b/x-tba/6-methods/xxx-project-file-parser/01-funcs/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/01-funcs/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/x-tba/6-methods/xxx-project-file-parser/01-funcs/main.go b/x-tba/6-methods/xxx-project-file-parser/01-funcs/main.go new file mode 100644 index 0000000..d79c157 --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/01-funcs/main.go @@ -0,0 +1,70 @@ +// 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() { + in := bufio.NewScanner(os.Stdin) + + p := newParser() + for in.Scan() { + // dom, err := parse(p, in.Text()) + // if err != nil { + // fmt.Println(err) + // return + // } + + // p = push(p, dom) + + p = add(p, in.Text()) + } + + summarize(p) + dumpErrs(in.Err(), err(p)) +} + +// funcs not always need to be reused. +// here, it tells about what it does: it summarizes the parsing result. +func summarize(p parser) { + // multiple iterators can be created. each one remembers the last + // read domain record. + next, cur := iterator(p) + for next() { + dom := cur() + fmt.Printf("%-25s -> %d\n", dom.name, dom.visits) + } + fmt.Printf("\n%-25s -> %d\n", "TOTAL", p.total) +} + +// // funcs not always need to be reused. +// // here, it tells about what it does: it summarizes the parsing result. +// func summarize(p parser) { +// // Print the visits per domain +// for _, d := range p.domains { +// vis := p.sum[d.name] + +// fmt.Printf("%-25s -> %d\n", d.name, vis) +// } + +// // Print the total visits for all domains +// fmt.Printf("\n%-25s -> %d\n", "TOTAL", p.total) +// } + +// this variadic func simplifies the multiple error handling +func dumpErrs(errs ...error) { + for _, err := range errs { + if err != nil { + fmt.Printf("> Err: %s\n", err) + } + } +} diff --git a/x-tba/6-methods/xxx-project-file-parser/01-funcs/parser.go b/x-tba/6-methods/xxx-project-file-parser/01-funcs/parser.go new file mode 100644 index 0000000..70daef6 --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/01-funcs/parser.go @@ -0,0 +1,109 @@ +package main + +import ( + "fmt" + "strconv" + "strings" +) + +// domain represents a domain log record +type domain struct { + name string + visits int +} + +// parser parses a log file and provides an iterator to iterate upon the domains +// +// the parser struct is carefully crafted to be usable using its zero values except the map field. +type parser struct { + sum map[string]int // visits per unique domain + domains []domain // unique domain names + total int // total visits to all domains + lines int // number of parsed lines (for the error messages) + err error // saves the last error occurred +} + +// newParser creates and returns a new parser. +func newParser() parser { + return parser{sum: make(map[string]int)} +} + +func add(p parser, line string) parser { + // if there was a previous error do not add + if p.err != nil { + return p + } + + p, dom, err := parse(p, line) + + // store only the last error + if err != nil { + p.err = err + return p + } + + return push(p, dom) +} + +// iterator returns two functions for iterating over domains. +// next = returns true when there are more domains to iterate on. +// cur = returns the current domain +// +// READ METHOD +func iterator(p parser) (next func() bool, cur func() domain) { + // remember the last received line + var last int + + next = func() bool { + defer func() { last++ }() + return len(p.domains) > last + } + + cur = func() domain { + d := p.domains[last-1] + vis := p.sum[d.name] + + // return a copy so the caller cannot change it + return domain{name: d.name, visits: vis} + } + + return +} + +// error returns the last error occurred +// +// READ METHOD +func err(p parser) error { + return p.err +} + +func parse(p parser, line string) (pr parser, dom domain, err error) { + p.lines++ // increase the parsed line counter (only write is here) + + fields := strings.Fields(line) + if len(fields) != 2 { + err = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines) + return p, dom, err + } + + name, visits := fields[0], fields[1] + + n, err := strconv.Atoi(visits) + if n < 0 || err != nil { + err = fmt.Errorf("wrong input: %q (line #%d)", visits, p.lines) + return p, dom, err + } + + return p, domain{name: name, visits: n}, nil +} + +func push(p parser, d domain) parser { + // collect the unique domains + if _, ok := p.sum[d.name]; !ok { + p.domains = append(p.domains, d) + } + + p.sum[d.name] += d.visits + p.total += d.visits + return p +} diff --git a/x-tba/6-methods/xxx-project-file-parser/02-pointers/log.txt b/x-tba/6-methods/xxx-project-file-parser/02-pointers/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/02-pointers/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/x-tba/6-methods/xxx-project-file-parser/02-pointers/main.go b/x-tba/6-methods/xxx-project-file-parser/02-pointers/main.go new file mode 100644 index 0000000..819e044 --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/02-pointers/main.go @@ -0,0 +1,56 @@ +// 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() { + in := bufio.NewScanner(os.Stdin) + + p := newParser() + for in.Scan() { + // dom, err := parse(p, in.Text()) + // if err != nil { + // fmt.Println(err) + // return + // } + + // p = push(p, dom) + + add(p, in.Text()) + } + + summarize(p) + dumpErrs(in.Err(), err(p)) +} + +// funcs not always need to be reused. +// here, it tells about what it does: it summarizes the parsing result. +func summarize(p *parser) { + // multiple iterators can be created. each one remembers the last + // read domain record. + next, cur := iterator(p) + for next() { + dom := cur() + fmt.Printf("%-25s -> %d\n", dom.name, dom.visits) + } + fmt.Printf("\n%-25s -> %d\n", "TOTAL", p.total) +} + +// this variadic func simplifies the multiple error handling +func dumpErrs(errs ...error) { + for _, err := range errs { + if err != nil { + fmt.Printf("> Err: %s\n", err) + } + } +} diff --git a/x-tba/6-methods/xxx-project-file-parser/02-pointers/parser.go b/x-tba/6-methods/xxx-project-file-parser/02-pointers/parser.go new file mode 100644 index 0000000..5cfb6ab --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/02-pointers/parser.go @@ -0,0 +1,108 @@ +package main + +import ( + "fmt" + "strconv" + "strings" +) + +// domain represents a domain log record +type domain struct { + name string + visits int +} + +// parser parses a log file and provides an iterator to iterate upon the domains +// +// the parser struct is carefully crafted to be usable using its zero values except the map field +type parser struct { + sum map[string]int // visits per unique domain + domains []domain // unique domain names + total int // total visits to all domains + lines int // number of parsed lines (for the error messages) + err error // saves the last error occurred +} + +// newParser creates and returns a new parser. +func newParser() *parser { + return &parser{sum: make(map[string]int)} +} + +func add(p *parser, line string) { + // if there was a previous error do not add + if p.err != nil { + return + } + + dom, err := parse(p, line) + + // store only the last error + if err != nil { + p.err = err + return + } + + push(p, dom) +} + +// iterator returns two functions for iterating over domains. +// next = returns true when there are more domains to iterate on. +// cur = returns the current domain +// +// READ METHOD +func iterator(p *parser) (next func() bool, cur func() domain) { + // remember the last received line + var last int + + next = func() bool { + defer func() { last++ }() + return len(p.domains) > last + } + + cur = func() domain { + d := p.domains[last-1] + vis := p.sum[d.name] + + // return a copy so the caller cannot change it + return domain{name: d.name, visits: vis} + } + + return +} + +// error returns the last error occurred +// +// READ METHOD +func err(p *parser) error { + return p.err +} + +func parse(p *parser, line string) (dom domain, err error) { + p.lines++ // increase the parsed line counter (only write is here) + + fields := strings.Fields(line) + if len(fields) != 2 { + err = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines) + return dom, err + } + + name, visits := fields[0], fields[1] + + n, err := strconv.Atoi(visits) + if n < 0 || err != nil { + err = fmt.Errorf("wrong input: %q (line #%d)", visits, p.lines) + return dom, err + } + + return domain{name: name, visits: n}, nil +} + +func push(p *parser, d domain) { + // collect the unique domains + if _, ok := p.sum[d.name]; !ok { + p.domains = append(p.domains, d) + } + + p.sum[d.name] += d.visits + p.total += d.visits +} diff --git a/x-tba/6-methods/xxx-project-file-parser/03-methods/log.txt b/x-tba/6-methods/xxx-project-file-parser/03-methods/log.txt new file mode 100644 index 0000000..fb2432b --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/03-methods/log.txt @@ -0,0 +1,6 @@ +learngoprogramming.com 10 +learngoprogramming.com 10 +golang.org 4 +golang.org 6 +blog.golang.org 20 +blog.golang.org 10 \ No newline at end of file diff --git a/x-tba/6-methods/xxx-project-file-parser/03-methods/main.go b/x-tba/6-methods/xxx-project-file-parser/03-methods/main.go new file mode 100644 index 0000000..d8b8a11 --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/03-methods/main.go @@ -0,0 +1,52 @@ +// 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() { + in := bufio.NewScanner(os.Stdin) + + // create a file + // defer closing it + // in := bufio.NewScanner(os.Stdin) + + p := newParser() + for in.Scan() && p.error() == nil { + p.add(in.Text()) + } + + summarize(p) + dumpErrs(in.Err(), p.error()) +} + +// funcs not always need to be reused. +// here, it tells about what it does: it summarizes the parsing result. +func summarize(p *parser) { + // multiple iterators can be created. each one remembers the last + // read domain record. + next, cur := p.iterator() + for next() { + dom := cur() + fmt.Printf("%-25s -> %d\n", dom.name, dom.visits) + } + fmt.Printf("\n%-25s -> %d\n", "TOTAL", p.total) +} + +// this variadic func simplifies the multiple error handling +func dumpErrs(errs ...error) { + for _, err := range errs { + if err != nil { + fmt.Printf("> Err: %s\n", err) + } + } +} diff --git a/x-tba/6-methods/xxx-project-file-parser/03-methods/parser.go b/x-tba/6-methods/xxx-project-file-parser/03-methods/parser.go new file mode 100644 index 0000000..7c48e57 --- /dev/null +++ b/x-tba/6-methods/xxx-project-file-parser/03-methods/parser.go @@ -0,0 +1,125 @@ +package main + +import ( + "fmt" + "strconv" + "strings" +) + +// domain represents a domain log record +type domain struct { + name string + visits int +} + +// parser parses a log file and provides an iterator to iterate upon the domains +// +// the parser struct is carefully crafted to be usable using its zero values except the map field +type parser struct { + sum map[string]int // visits per unique domain + domains []domain // unique domain names + total int // total visits to all domains + lines int // number of parsed lines (for the error messages) + err error // saves the last error occurred +} + +// newParser creates and returns a new parser. +// +// bare func, it doesn't need to operate on a parser value. +func newParser() *parser { + return &parser{sum: make(map[string]int)} +} + +// PUBLIC METHODS (API) + +// add parses the given line and saves the result to the internal list of +// domains. it doesn't add the record when the parsing fails. +// +// WRITE METHOD +func (p *parser) add(line string) { + // if there was a previous error do not add + if p.err != nil { + return + } + + dom, err := p.parse(line) + + // store only the last error + if err != nil { + p.err = err + return + } + + p.push(dom) +} + +// iterator returns two functions for iterating over domains. +// next = returns true when there are more domains to iterate on. +// cur = returns the current domain +// +// READ METHOD +func (p *parser) iterator() (next func() bool, cur func() domain) { + // remember the last received line + var last int + + next = func() bool { + defer func() { last++ }() + return len(p.domains) > last + } + + cur = func() domain { + d := p.domains[last-1] + vis := p.sum[d.name] + + // return a copy so the caller cannot change it + return domain{name: d.name, visits: vis} + } + + return +} + +// error returns the last error occurred +// +// READ METHOD +func (p *parser) error() error { + return p.err +} + +// PRIVATE METHODS + +// parse parses the given text and returns a domain struct +// +// WRITE METHOD +func (p *parser) parse(line string) (dom domain, err error) { + p.lines++ // increase the parsed line counter (only write is here) + + fields := strings.Fields(line) + if len(fields) != 2 { + err = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines) + return dom, err + } + + name, visits := fields[0], fields[1] + + n, err := strconv.Atoi(visits) + if n < 0 || err != nil { + err = fmt.Errorf("wrong input: %q (line #%d)", visits, p.lines) + return dom, err + } + + return domain{name: name, visits: n}, nil +} + +// push pushes the given domain to the internal list of domains. +// it also increases the total visits for all the domains. +// +// WRITE METHOD +func (p *parser) push(d domain) { + // collect the unique domains + if _, ok := p.sum[d.name]; !ok { + p.domains = append(p.domains, d) + } + + p.sum[d.name] += d.visits + p.total += d.visits +}