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,73 @@
// 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"
func main() {
nums := []int{2, 3, 7}
fmt.Printf("nums : %d\n", nums)
n := avgNoVariadic(nums)
fmt.Printf("avgNoVariadic : %d\n", n)
n = avg(2, 3, 7)
fmt.Printf("avg(2, 3, 7) : %d\n", n)
n = avg(2, 3, 7, 8)
fmt.Printf("avg(2, 3, 7, 8) : %d\n", n)
// use ... to pass a slice
n = avg(nums...)
fmt.Printf("avg(nums...) : %d\n", n)
// uses the existing slice
double(nums...)
fmt.Printf("double(nums...) : %d\n", nums)
// creates a new slice
double(4, 6, 14)
fmt.Printf("double(4, 6, 14): %d\n", nums)
// creates a nil slice
fmt.Printf("\nmain.nums : %p\n", nums)
investigate("passes main.nums", nums...)
investigate("passes main.nums", nums...)
investigate("passes args", 4, 6, 14)
investigate("passes args", 4, 6, 14)
investigate("no args")
}
func investigate(msg string, nums ...int) {
fmt.Printf("investigate.nums: %12p -> %s\n", nums, msg)
if len(nums) > 0 {
fmt.Printf("\tfirst element: %d\n", nums[0])
}
}
func double(nums ...int) {
for i := range nums {
nums[i] *= 2
}
}
func avg(nums ...int) int {
return sum(nums) / len(nums)
}
func avgNoVariadic(nums []int) int {
return sum(nums) / len(nums)
}
func sum(nums []int) (total int) {
for _, n := range nums {
total += n
}
return
}

View File

@@ -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 "fmt"
type filterFunc func(int) bool
func main() {
signatures()
funcValues()
}
func isEven(n int) bool {
return n%2 == 0
}
func isOdd(m int) bool {
return m%2 == 1
}
func signatures() {
fmt.Println("••• FUNC SIGNATURES (TYPES) •••")
fmt.Printf("isEven : %T\n", isEven)
fmt.Printf("isOdd : %T\n", isOdd)
}
func funcValues() {
fmt.Println("\n••• FUNC VALUES (VARS) •••")
// var value func(int) bool
var value filterFunc
fmt.Printf("value nil? : %t\n", value == nil)
value = isEven
fmt.Printf("value(2) : %t\n", value(2))
fmt.Printf("isEven(2) : %t\n", isEven(2))
value = isOdd
fmt.Printf("value(1) : %t\n", value(1))
fmt.Printf("isOdd(1) : %t\n", isOdd(1))
fmt.Printf("value nil? : %t\n", value == nil)
fmt.Printf("value : %p\n", value)
fmt.Printf("isEven : %p\n", isEven)
fmt.Printf("isOdd : %p\n", isOdd)
}

View File

@@ -0,0 +1,82 @@
// 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"
"strings"
"unicode"
)
type filterFunc func(int) bool
func main() {
signatures()
fmt.Println("\n••• WITHOUT FUNC VALUES •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Printf("evens : %d\n", filterEvens(nums...))
fmt.Printf("odds : %d\n", filterOdds(nums...))
fmt.Println("\n••• FUNC VALUES •••")
fmt.Printf("evens : %d\n", filter(isEven, nums...))
fmt.Printf("odds : %d\n", filter(isOdd, nums...))
fmt.Println("\n••• MAPPING •••")
fmt.Println(strings.Map(unpunct, "hello!!! HOW ARE YOU???? :))"))
fmt.Println(strings.Map(unpunct, "TIME IS UP!"))
}
func unpunct(r rune) rune {
if unicode.IsPunct(r) {
return -1
}
return unicode.ToLower(r)
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func filterOdds(nums ...int) (filtered []int) {
for _, n := range nums {
if isOdd(n) {
filtered = append(filtered, n)
}
}
return
}
func filterEvens(nums ...int) (filtered []int) {
for _, n := range nums {
if isEven(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool {
return n%2 == 0
}
func isOdd(m int) bool {
return m%2 == 1
}
func signatures() {
fmt.Println("••• FUNC SIGNATURES (TYPES) •••")
fmt.Printf("isEven : %T\n", isEven)
fmt.Printf("isOdd : %T\n", isOdd)
}

View File

@@ -0,0 +1,76 @@
// 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"
type filterFunc func(int) bool
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println("••• FUNC VALUES •••")
fmt.Printf("evens : %d\n", filter(isEven, nums...))
fmt.Printf("odds : %d\n", filter(isOdd, nums...))
fmt.Printf("> 3 : %d\n", filter(greaterThan3, nums...))
fmt.Printf("> 6 : %d\n", filter(greaterThan6, nums...))
// ========================================================================
fmt.Println("\n••• CLOSURES •••")
var min int
greaterThan := func(n int) bool {
return n > min
}
min = 3
fmt.Printf("> 3 : %d\n", filter(greaterThan, nums...))
min = 6
fmt.Printf("> 6 : %d\n", filter(greaterThan, nums...))
// min = 1
// fmt.Printf("> 1 : %d\n", filter(greaterThan, nums...))
// min = 2
// fmt.Printf("> 2 : %d\n", filter(greaterThan, nums...))
// min = 3
// fmt.Printf("> 3 : %d\n", filter(greaterThan, nums...))
var filterers []filterFunc
for i := 1; i <= 3; i++ {
current := i
filterers = append(filterers, func(n int) bool {
min = current
return greaterThan(n)
})
}
printer(filterers, nums...)
}
func printer(filterers []filterFunc, nums ...int) {
for i, filterer := range filterers {
fmt.Printf("> %d : %d\n", i+1, filter(filterer, nums...))
}
}
func greaterThan6(n int) bool { return n > 6 }
func greaterThan3(n int) bool { return n > 3 }
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}

View File

@@ -0,0 +1,58 @@
// 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"
)
type filterFunc func(int) bool
func main() {
fmt.Println("••• HIGHER-ORDER FUNCS •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
odd := reverse(reverse(isEven))
fmt.Printf("reversed : %t\n", odd(8))
fmt.Printf("> 3 : %d\n", filter(greater(3), nums...))
fmt.Printf("> 6 : %d\n", filter(greater(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(lesseq(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(reverse(greater(6)), nums...))
}
func reverse(f filterFunc) filterFunc {
return func(n int) bool {
return !f(n)
}
}
func greater(min int) filterFunc {
return func(n int) bool {
return n > min
}
}
func lesseq(max int) filterFunc {
return func(n int) bool {
return n <= max
}
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }

View File

@@ -0,0 +1,97 @@
// 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"
)
type filterFunc func(int) bool
func main() {
fmt.Println("••• HIGHER-ORDER FUNCS •••")
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
odd := reverse(reverse(isEven))
fmt.Printf("reversed : %t\n", odd(8))
fmt.Printf("> 3 : %d\n", filter(greater(3), nums...))
fmt.Printf("> 6 : %d\n", filter(greater(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(lesseq(6), nums...))
fmt.Printf("<= 6 : %d\n", filter(reverse(greater(6)), nums...))
fmt.Println("\n••• CLOSURES •••")
fmt.Printf("uniques : %d\n", filter(uniques(), 1, 1, 2, 3, 3))
dups := reverse(uniques())
fmt.Printf("dups : %v\n", filter(dups, 1, 1, 2, 3, 3))
dups = reverse(uniques())
fmt.Printf("dups dups : %v\n", filter(dups, 1, 1, 2, 3, 3, 3, 3))
dups = reverse(uniques())
out := filter(dups, 1, 1, 2, 3, 3, 3, 3)
fmt.Printf("dups uniqs : %v\n", filter(uniques(), out...))
dups = reverse(uniques())
chained := chain(reverse(greater(1)), dups, uniques())
fmt.Printf("dups chain : %v\n", filter(chained, 1, 1, 2, 3, 3, 3, 3, 0, 0))
}
func chain(filters ...filterFunc) filterFunc {
return func(n int) bool {
for _, next := range filters {
if !next(n) {
return false
}
}
return true
}
}
func uniques() filterFunc {
seen := make(map[int]bool)
return func(n int) (ok bool) {
if !seen[n] {
seen[n], ok = true, true
}
return
}
}
func reverse(f filterFunc) filterFunc {
return func(n int) bool {
return !f(n)
}
}
func greater(min int) filterFunc {
return func(n int) bool {
return n > min
}
}
func lesseq(max int) filterFunc {
return func(n int) bool {
return n <= max
}
}
func filter(f filterFunc, nums ...int) (filtered []int) {
for _, n := range nums {
if f(n) {
filtered = append(filtered, n)
}
}
return
}
func isEven(n int) bool { return n%2 == 0 }
func isOdd(m int) bool { return m%2 == 1 }

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
}