From 4e8b80c5199e10e6ecb0abebc1a670359e0c580b Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Tue, 6 Aug 2019 01:34:36 +0300 Subject: [PATCH] move: advanced funcs to functional programming --- .../_legacy}/01-variadic-funcs/main.go | 0 .../_legacy}/02-func-values/main.go | 0 .../_legacy}/03-func-to-func/main.go | 0 .../_legacy}/04-closures/main.go | 0 .../_legacy}/05-higher-order-funcs/main.go | 0 .../06-functional-programming/main.go | 0 .../log-parser-exp/field.go | 20 ++++ .../log-parser-exp/filterby.go | 15 +++ .../log-parser-exp/filters.go | 34 ++++++ .../log-parser-exp/groupby.go | 17 +++ .../log-parser-exp/groupers.go | 21 ++++ .../log-parser-exp/log.txt | 16 +++ .../log-parser-exp/log_err_missing.txt | 16 +++ .../log-parser-exp/log_err_negative.txt | 16 +++ .../log-parser-exp/log_err_str.txt | 16 +++ .../log-parser-exp/main.go | 49 +++++++++ .../log-parser-exp/report.go | 85 +++++++++++++++ .../log-parser-exp/result.go | 38 +++++++ .../log-parser-exp/textreader.go | 44 ++++++++ .../log-parser-exp/textwriter.go | 30 ++++++ 27-functions-advanced/07-log-parser/log.txt | 6 -- .../07-log-parser/log_err_missing.txt | 6 -- .../07-log-parser/log_err_negative.txt | 6 -- .../07-log-parser/log_err_str.txt | 6 -- 27-functions-advanced/07-log-parser/main.go | 48 --------- 27-functions-advanced/07-log-parser/parser.go | 101 ------------------ 27-functions-advanced/exercises/README.md | 8 -- 27 files changed, 417 insertions(+), 181 deletions(-) rename {27-functions-advanced => 27-functional-programming/_legacy}/01-variadic-funcs/main.go (100%) rename {27-functions-advanced => 27-functional-programming/_legacy}/02-func-values/main.go (100%) rename {27-functions-advanced => 27-functional-programming/_legacy}/03-func-to-func/main.go (100%) rename {27-functions-advanced => 27-functional-programming/_legacy}/04-closures/main.go (100%) rename {27-functions-advanced => 27-functional-programming/_legacy}/05-higher-order-funcs/main.go (100%) rename {27-functions-advanced => 27-functional-programming/_legacy}/06-functional-programming/main.go (100%) create mode 100644 27-functional-programming/log-parser-exp/field.go create mode 100644 27-functional-programming/log-parser-exp/filterby.go create mode 100644 27-functional-programming/log-parser-exp/filters.go create mode 100644 27-functional-programming/log-parser-exp/groupby.go create mode 100644 27-functional-programming/log-parser-exp/groupers.go create mode 100644 27-functional-programming/log-parser-exp/log.txt create mode 100644 27-functional-programming/log-parser-exp/log_err_missing.txt create mode 100644 27-functional-programming/log-parser-exp/log_err_negative.txt create mode 100644 27-functional-programming/log-parser-exp/log_err_str.txt create mode 100644 27-functional-programming/log-parser-exp/main.go create mode 100644 27-functional-programming/log-parser-exp/report.go create mode 100644 27-functional-programming/log-parser-exp/result.go create mode 100644 27-functional-programming/log-parser-exp/textreader.go create mode 100644 27-functional-programming/log-parser-exp/textwriter.go delete mode 100644 27-functions-advanced/07-log-parser/log.txt delete mode 100644 27-functions-advanced/07-log-parser/log_err_missing.txt delete mode 100644 27-functions-advanced/07-log-parser/log_err_negative.txt delete mode 100644 27-functions-advanced/07-log-parser/log_err_str.txt delete mode 100644 27-functions-advanced/07-log-parser/main.go delete mode 100644 27-functions-advanced/07-log-parser/parser.go delete mode 100644 27-functions-advanced/exercises/README.md diff --git a/27-functions-advanced/01-variadic-funcs/main.go b/27-functional-programming/_legacy/01-variadic-funcs/main.go similarity index 100% rename from 27-functions-advanced/01-variadic-funcs/main.go rename to 27-functional-programming/_legacy/01-variadic-funcs/main.go diff --git a/27-functions-advanced/02-func-values/main.go b/27-functional-programming/_legacy/02-func-values/main.go similarity index 100% rename from 27-functions-advanced/02-func-values/main.go rename to 27-functional-programming/_legacy/02-func-values/main.go diff --git a/27-functions-advanced/03-func-to-func/main.go b/27-functional-programming/_legacy/03-func-to-func/main.go similarity index 100% rename from 27-functions-advanced/03-func-to-func/main.go rename to 27-functional-programming/_legacy/03-func-to-func/main.go diff --git a/27-functions-advanced/04-closures/main.go b/27-functional-programming/_legacy/04-closures/main.go similarity index 100% rename from 27-functions-advanced/04-closures/main.go rename to 27-functional-programming/_legacy/04-closures/main.go diff --git a/27-functions-advanced/05-higher-order-funcs/main.go b/27-functional-programming/_legacy/05-higher-order-funcs/main.go similarity index 100% rename from 27-functions-advanced/05-higher-order-funcs/main.go rename to 27-functional-programming/_legacy/05-higher-order-funcs/main.go diff --git a/27-functions-advanced/06-functional-programming/main.go b/27-functional-programming/_legacy/06-functional-programming/main.go similarity index 100% rename from 27-functions-advanced/06-functional-programming/main.go rename to 27-functional-programming/_legacy/06-functional-programming/main.go diff --git a/27-functional-programming/log-parser-exp/field.go b/27-functional-programming/log-parser-exp/field.go new file mode 100644 index 0000000..7881c1e --- /dev/null +++ b/27-functional-programming/log-parser-exp/field.go @@ -0,0 +1,20 @@ +package main + +import ( + "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 n < 0 || err != nil { + f.err = fmt.Errorf("incorrect field -> %q = %q", name, val) + } + return n +} diff --git a/27-functional-programming/log-parser-exp/filterby.go b/27-functional-programming/log-parser-exp/filterby.go new file mode 100644 index 0000000..7201aa7 --- /dev/null +++ b/27-functional-programming/log-parser-exp/filterby.go @@ -0,0 +1,15 @@ +package main + +func filterBy(results []result, filterer filterFunc) []result { + out := results[:0] + + for _, r := range results { + if !filterer(r) { + continue + } + + out = append(out, r) + } + + return out +} diff --git a/27-functional-programming/log-parser-exp/filters.go b/27-functional-programming/log-parser-exp/filters.go new file mode 100644 index 0000000..68de7cb --- /dev/null +++ b/27-functional-programming/log-parser-exp/filters.go @@ -0,0 +1,34 @@ +package main + +import "strings" + +func noopFilter(r result) bool { + return true +} + +func not(filter filterFunc) filterFunc { + return func(r result) bool { + return !filter(r) + } +} + +func domainExtFilter(domains ...string) filterFunc { + return func(r result) bool { + for _, domain := range domains { + if strings.HasSuffix(r.domain, "."+domain) { + return true + } + } + return false + } +} + +func domainFilter(domain string) filterFunc { + return func(r result) bool { + return strings.Contains(r.domain, domain) + } +} + +func orgDomainsFilter(r result) bool { + return strings.HasSuffix(r.domain, ".org") +} diff --git a/27-functional-programming/log-parser-exp/groupby.go b/27-functional-programming/log-parser-exp/groupby.go new file mode 100644 index 0000000..7d9f71a --- /dev/null +++ b/27-functional-programming/log-parser-exp/groupby.go @@ -0,0 +1,17 @@ +package main + +func groupBy(results []result, keyer groupFunc) []result { + grouped := make(map[string]result, len(results)) + + for _, cur := range results { + key := keyer(cur) + grouped[key] = cur.add(grouped[key]) + } + + out := results[:0] + for _, r := range grouped { + out = append(out, r) + } + + return out +} diff --git a/27-functional-programming/log-parser-exp/groupers.go b/27-functional-programming/log-parser-exp/groupers.go new file mode 100644 index 0000000..40ee1cb --- /dev/null +++ b/27-functional-programming/log-parser-exp/groupers.go @@ -0,0 +1,21 @@ +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 +} + +// you could have created a noopGrouper as well +// but it's not necessary i think (map allocation) +func noopGrouper(r result) string { + // with something like: + // return randomStrings() + return "" +} diff --git a/27-functional-programming/log-parser-exp/log.txt b/27-functional-programming/log-parser-exp/log.txt new file mode 100644 index 0000000..e907371 --- /dev/null +++ b/27-functional-programming/log-parser-exp/log.txt @@ -0,0 +1,16 @@ +learngoprogramming.com.tr / 10 5 +learngoprogramming.com /courses 15 10 +learngoprogramming.com /courses 10 5 +learngoprogramming.com /articles 20 15 +learngoprogramming.com /articles 5 2 +golang.org / 40 20 +golang.org / 20 10 +golang.org /blog 45 25 +golang.org /blog 15 5 +blog.golang.org /courses 60 30 +blog.golang.org /courses 30 20 +blog.golang.org /updates 20 10 +blog.golang.org /reference 65 35 +blog.golang.org /reference 15 5 +inanc.io /about 30 15 +inanc.io /about 70 35 \ No newline at end of file diff --git a/27-functional-programming/log-parser-exp/log_err_missing.txt b/27-functional-programming/log-parser-exp/log_err_missing.txt new file mode 100644 index 0000000..01f606f --- /dev/null +++ b/27-functional-programming/log-parser-exp/log_err_missing.txt @@ -0,0 +1,16 @@ +learngoprogramming.com / 10 5 +learngoprogramming.com /courses 15 10 +learngoprogramming.com /courses 10 5 +learngoprogramming.com /articles 20 15 +learngoprogramming.com /articles 5 2 +golang.org / 40 20 +golang.org / 20 10 +golang.org /blog 45 25 +golang.org /blog 15 5 +blog.golang.org /updates +blog.golang.org /updates 30 20 +blog.golang.org /updates 20 10 +blog.golang.org /reference 65 35 +blog.golang.org /reference 15 5 +inanc.io /about 30 15 +inanc.io /about 70 35 diff --git a/27-functional-programming/log-parser-exp/log_err_negative.txt b/27-functional-programming/log-parser-exp/log_err_negative.txt new file mode 100644 index 0000000..d84023e --- /dev/null +++ b/27-functional-programming/log-parser-exp/log_err_negative.txt @@ -0,0 +1,16 @@ +learngoprogramming.com / 10 5 +learngoprogramming.com /courses 15 10 +learngoprogramming.com /courses 10 5 +learngoprogramming.com /articles 20 15 +learngoprogramming.com /articles 5 2 +golang.org / 40 20 +golang.org / 20 10 +golang.org /blog 45 -250 +golang.org /blog 15 5 +blog.golang.org /updates 60 30 +blog.golang.org /updates 30 20 +blog.golang.org /updates 20 10 +blog.golang.org /reference 65 35 +blog.golang.org /reference 15 5 +inanc.io /about 30 15 +inanc.io /about 70 35 diff --git a/27-functional-programming/log-parser-exp/log_err_str.txt b/27-functional-programming/log-parser-exp/log_err_str.txt new file mode 100644 index 0000000..20272ab --- /dev/null +++ b/27-functional-programming/log-parser-exp/log_err_str.txt @@ -0,0 +1,16 @@ +learngoprogramming.com / 10 5 +learngoprogramming.com /courses 15 10 +learngoprogramming.com /courses 10 5 +learngoprogramming.com /articles 20 15 +learngoprogramming.com /articles 5 2 +golang.org / 40 TWENTY +golang.org / 20 10 +golang.org /blog 45 25 +golang.org /blog 15 5 +blog.golang.org /updates 60 30 +blog.golang.org /updates 30 20 +blog.golang.org /updates 20 10 +blog.golang.org /reference 65 35 +blog.golang.org /reference 15 5 +inanc.io /about 30 15 +inanc.io /about 70 35 \ No newline at end of file diff --git a/27-functional-programming/log-parser-exp/main.go b/27-functional-programming/log-parser-exp/main.go new file mode 100644 index 0000000..76e5642 --- /dev/null +++ b/27-functional-programming/log-parser-exp/main.go @@ -0,0 +1,49 @@ +// 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() { + defer recoverErr() + + _, err := newReport(). + from(os.Stdin). + to(os.Stdout). + retrieveFrom(textReader). + filterBy(orgDomainsFilter). + // filterBy(not(domainExtFilter("org", "io"))). + // groupBy(pageGrouper). + groupBy(domainGrouper). + writeTo(textWriter). + run() + + if err != nil { + fmt.Println("> Err:", err) + } +} + +func recoverErr() { + val := recover() + + if val == nil { + return + } + + if err, ok := val.(string); ok { + fmt.Println("> Error occurred:", err) + } +} + +/* +newReport -> stats.NewReport(). +Result -> stats.Record +*/ diff --git a/27-functional-programming/log-parser-exp/report.go b/27-functional-programming/log-parser-exp/report.go new file mode 100644 index 0000000..11f4b1c --- /dev/null +++ b/27-functional-programming/log-parser-exp/report.go @@ -0,0 +1,85 @@ +package main + +import "io" + +type ( + parserFunc func(io.Reader) ([]result, error) + filterFunc func(result) bool + groupFunc func(result) string + outputFunc func(io.Writer, []result) error +) + +type report struct { + input io.Reader + output io.Writer + parser parserFunc + filterer filterFunc + grouper groupFunc + outputter outputFunc +} + +func newReport() *report { + return &report{ + // parser: textParser, + filterer: noopFilter, + } +} + +func (r *report) from(reader io.Reader) *report { + r.input = reader + return r +} + +func (r *report) to(writer io.Writer) *report { + r.output = writer + return r +} + +func (r *report) retrieveFrom(fn parserFunc) *report { + r.parser = fn + return r +} + +func (r *report) filterBy(fn filterFunc) *report { + r.filterer = fn + return r +} + +func (r *report) groupBy(fn groupFunc) *report { + r.grouper = fn + return r +} + +func (r *report) writeTo(fn outputFunc) *report { + r.outputter = fn + return r +} + +func (r *report) run() ([]result, error) { + if r.parser == nil { + panic("report retriever cannot be nil") + } + + results, err := r.parser(r.input) + if err != nil { + return nil, err + } + + // noop if filterer is nil + results = filterBy(results, r.filterer) + + // grouper is more tricky + // you don't want to create an unnecessary map + if r.grouper != nil { + results = groupBy(results, r.grouper) + } + + // prefer: noop output + if r.output != nil { + if err := r.outputter(r.output, results); err != nil { + return nil, err + } + } + + return results, nil +} diff --git a/27-functional-programming/log-parser-exp/result.go b/27-functional-programming/log-parser-exp/result.go new file mode 100644 index 0000000..0df77b8 --- /dev/null +++ b/27-functional-programming/log-parser-exp/result.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "strings" +) + +// result stores the parsed result for a domain +type result struct { + domain string + page string + visits int + uniques int +} + +// parseLine parses a log line and returns the parsed result with an error +func parseLine(line string) (r result, err error) { + fields := strings.Fields(line) + if len(fields) != 4 { + return r, fmt.Errorf("wrong number of fields -> %v", 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 +} + +// add adds the metrics of another result to the result +func (r result) add(other result) result { + r.visits += other.visits + r.uniques += other.uniques + return r +} diff --git a/27-functional-programming/log-parser-exp/textreader.go b/27-functional-programming/log-parser-exp/textreader.go new file mode 100644 index 0000000..17cd2b0 --- /dev/null +++ b/27-functional-programming/log-parser-exp/textreader.go @@ -0,0 +1,44 @@ +// 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) ([]result, error) { + in := bufio.NewScanner(r) + return parseText(in) +} + +func parseText(in *bufio.Scanner) ([]result, error) { + var ( + results []result + lines int + ) + + for in.Scan() { + lines++ + + result, err := parseLine(in.Text()) + if err != nil { + // TODO: custom error type for line information + return nil, fmt.Errorf("line %d: %v", lines, err) + } + + results = append(results, result) + } + + if err := in.Err(); err != nil { + return nil, err + } + + return results, nil +} diff --git a/27-functional-programming/log-parser-exp/textwriter.go b/27-functional-programming/log-parser-exp/textwriter.go new file mode 100644 index 0000000..d9be406 --- /dev/null +++ b/27-functional-programming/log-parser-exp/textwriter.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "io" + "strings" +) + +// TODO: sort by result key interfaces section + +func textWriter(w io.Writer, results []result) error { + fmt.Fprintf(w, "%-25s %-10s %10s %10s\n", + "DOMAINS", "PAGES", "VISITS", "UNIQUES") + + fmt.Fprintln(w, strings.Repeat("-", 58)) + + var total result + + for _, r := range results { + total = total.add(r) + + fmt.Fprintf(w, "%-25s %-10s %10d %10d\n", + r.domain, r.page, r.visits, r.uniques) + } + + fmt.Fprintf(w, "\n%-36s %10d %10d\n", + "", total.visits, total.uniques) + + return nil +} diff --git a/27-functions-advanced/07-log-parser/log.txt b/27-functions-advanced/07-log-parser/log.txt deleted file mode 100644 index fb2432b..0000000 --- a/27-functions-advanced/07-log-parser/log.txt +++ /dev/null @@ -1,6 +0,0 @@ -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/27-functions-advanced/07-log-parser/log_err_missing.txt b/27-functions-advanced/07-log-parser/log_err_missing.txt deleted file mode 100644 index fd8eff4..0000000 --- a/27-functions-advanced/07-log-parser/log_err_missing.txt +++ /dev/null @@ -1,6 +0,0 @@ -learngoprogramming.com 10 -learngoprogramming.com 10 -golang.org -golang.org 6 -blog.golang.org 20 -blog.golang.org 10 \ No newline at end of file diff --git a/27-functions-advanced/07-log-parser/log_err_negative.txt b/27-functions-advanced/07-log-parser/log_err_negative.txt deleted file mode 100644 index 60485c0..0000000 --- a/27-functions-advanced/07-log-parser/log_err_negative.txt +++ /dev/null @@ -1,6 +0,0 @@ -learngoprogramming.com 10 -learngoprogramming.com 10 -golang.org -100 -golang.org 6 -blog.golang.org 20 -blog.golang.org 10 \ No newline at end of file diff --git a/27-functions-advanced/07-log-parser/log_err_str.txt b/27-functions-advanced/07-log-parser/log_err_str.txt deleted file mode 100644 index 3a55bd7..0000000 --- a/27-functions-advanced/07-log-parser/log_err_str.txt +++ /dev/null @@ -1,6 +0,0 @@ -learngoprogramming.com 10 -learngoprogramming.com 10 -golang.org FOUR -golang.org 6 -blog.golang.org 20 -blog.golang.org 10 \ No newline at end of file diff --git a/27-functions-advanced/07-log-parser/main.go b/27-functions-advanced/07-log-parser/main.go deleted file mode 100644 index 70e06e3..0000000 --- a/27-functions-advanced/07-log-parser/main.go +++ /dev/null @@ -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" - "strings" -) - -func main() { - p := newParser() - - in := bufio.NewScanner(os.Stdin) - for in.Scan() { - parsed := parse(p, in.Text()) - update(p, parsed) - } - - summarize(p) - dumpErrs(in.Err(), err(p)) -} - -// summarize summarizes and prints the parsing result -func summarize(p *parser) { - fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS") - fmt.Println(strings.Repeat("-", 45)) - - loop(p, func(r result) { - fmt.Printf("%-30s %10d\n", r.domain, r.visits) - }) - fmt.Printf("\n%-30s %10d\n", "TOTAL", totalVisits(p)) -} - -// dumpErrs simplifies handling multiple errors -func dumpErrs(errs ...error) { - for _, err := range errs { - if err != nil { - fmt.Println("> Err:", err) - } - } -} diff --git a/27-functions-advanced/07-log-parser/parser.go b/27-functions-advanced/07-log-parser/parser.go deleted file mode 100644 index a84e99c..0000000 --- a/27-functions-advanced/07-log-parser/parser.go +++ /dev/null @@ -1,101 +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" - "strconv" - "strings" -) - -// result stores the parsed result for a domain -type result struct { - domain string - visits int - // add more metrics if needed -} - -// parser keep tracks of the parsing -type parser struct { - sum map[string]result // metrics per domain - domains []string // unique domain names - total int // total visits for all domains - lines int // number of parsed lines (for the error messages) - lerr error // the last error occurred -} - -// newParser constructs, initializes and returns a new parser -func newParser() *parser { - return &parser{sum: make(map[string]result)} -} - -// parse parses a log line and returns the parsed result with an error -func parse(p *parser, line string) (r result) { - if p.lerr != nil { - return - } - - p.lines++ - - fields := strings.Fields(line) - if len(fields) != 2 { - p.lerr = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines) - return - } - - var err error - - r.domain = fields[0] - r.visits, err = strconv.Atoi(fields[1]) - - if r.visits < 0 || err != nil { - p.lerr = fmt.Errorf("wrong input: %q (line #%d)", fields[1], p.lines) - } - return -} - -// update updates all the parsing results using the given parsing result -func update(p *parser, r result) { - if p.lerr != nil { - return - } - - // Collect the unique domains - if _, ok := p.sum[r.domain]; !ok { - p.domains = append(p.domains, r.domain) - } - - // Keep track of total and per domain visits - p.total += r.visits - - // create and assign a new copy of `visit` - p.sum[r.domain] = result{ - domain: r.domain, - visits: r.visits + p.sum[r.domain].visits, - } -} - -// err returns the last error encountered -func err(p *parser) error { - return p.lerr -} - -// loop allows a func to access all the copies of parsing results -func loop(p *parser, feed func(result)) { - sort.Strings(p.domains) - - for _, domain := range p.domains { - feed(p.sum[domain]) - } -} - -// total returns the total visits -func totalVisits(p *parser) int { - return p.total -} diff --git a/27-functions-advanced/exercises/README.md b/27-functions-advanced/exercises/README.md deleted file mode 100644 index 8b4725b..0000000 --- a/27-functions-advanced/exercises/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# IDEAS - -* Create a chainable funcs for strings -* Create a mapper instead of a filter -* Create map/reduce -* Create a time it takes higher-order func -* Use stdlib funcs with func values -* Create a functional program with structs (filter etc) \ No newline at end of file