move: advanced funcs to functional programming

This commit is contained in:
Inanc Gumus
2019-08-06 01:34:36 +03:00
parent c97becdf82
commit 4e8b80c519
27 changed files with 417 additions and 181 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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 ""
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
*/

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org 4
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org -100
golang.org 6
blog.golang.org 20
blog.golang.org 10

View File

@ -1,6 +0,0 @@
learngoprogramming.com 10
learngoprogramming.com 10
golang.org FOUR
golang.org 6
blog.golang.org 20
blog.golang.org 10

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"
"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)
}
}
}

View File

@ -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
}

View File

@ -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)