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
}