move: advanced funcs to functional programming
This commit is contained in:
		
							
								
								
									
										20
									
								
								27-functional-programming/log-parser-exp/field.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								27-functional-programming/log-parser-exp/field.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										15
									
								
								27-functional-programming/log-parser-exp/filterby.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								27-functional-programming/log-parser-exp/filterby.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										34
									
								
								27-functional-programming/log-parser-exp/filters.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								27-functional-programming/log-parser-exp/filters.go
									
									
									
									
									
										Normal 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") | ||||
| } | ||||
							
								
								
									
										17
									
								
								27-functional-programming/log-parser-exp/groupby.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								27-functional-programming/log-parser-exp/groupby.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										21
									
								
								27-functional-programming/log-parser-exp/groupers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								27-functional-programming/log-parser-exp/groupers.go
									
									
									
									
									
										Normal 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 "" | ||||
| } | ||||
							
								
								
									
										16
									
								
								27-functional-programming/log-parser-exp/log.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								27-functional-programming/log-parser-exp/log.txt
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										16
									
								
								27-functional-programming/log-parser-exp/log_err_missing.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								27-functional-programming/log-parser-exp/log_err_missing.txt
									
									
									
									
									
										Normal 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 | ||||
| @@ -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 | ||||
							
								
								
									
										16
									
								
								27-functional-programming/log-parser-exp/log_err_str.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								27-functional-programming/log-parser-exp/log_err_str.txt
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										49
									
								
								27-functional-programming/log-parser-exp/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								27-functional-programming/log-parser-exp/main.go
									
									
									
									
									
										Normal 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 | ||||
| */ | ||||
							
								
								
									
										85
									
								
								27-functional-programming/log-parser-exp/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								27-functional-programming/log-parser-exp/report.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										38
									
								
								27-functional-programming/log-parser-exp/result.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								27-functional-programming/log-parser-exp/result.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										44
									
								
								27-functional-programming/log-parser-exp/textreader.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								27-functional-programming/log-parser-exp/textreader.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										30
									
								
								27-functional-programming/log-parser-exp/textwriter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								27-functional-programming/log-parser-exp/textwriter.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
		Reference in New Issue
	
	Block a user