update: directory structure
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 }
|
||||
@@ -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 }
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// single()
|
||||
// stacked()
|
||||
findTheMeaning()
|
||||
}
|
||||
|
||||
func findTheMeaningNoDefer() {
|
||||
start := time.Now()
|
||||
fmt.Printf("%s starts...\n", "findTheMeaning")
|
||||
|
||||
// do some heavy calculation...
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
fmt.Printf("%s took %v\n", "findTheMeaning", time.Since(start))
|
||||
}
|
||||
|
||||
func findTheMeaning() {
|
||||
defer measure("findTheMeaning")()
|
||||
|
||||
// do some heavy calculation
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
|
||||
func measure(name string) func() {
|
||||
start := time.Now()
|
||||
fmt.Printf("%s starts...\n", name)
|
||||
|
||||
return func() {
|
||||
fmt.Printf("%s took %v\n", name, time.Since(start))
|
||||
}
|
||||
}
|
||||
|
||||
func stacked() {
|
||||
for count := 1; count <= 5; count++ {
|
||||
defer fmt.Println(count)
|
||||
}
|
||||
|
||||
fmt.Println("the stacked func returns")
|
||||
}
|
||||
|
||||
func single() {
|
||||
var count int
|
||||
|
||||
// defer func() {
|
||||
// fmt.Println(count)
|
||||
// }()
|
||||
defer fmt.Println(count)
|
||||
|
||||
count++
|
||||
// fmt.Println(count)
|
||||
|
||||
// the defer runs here
|
||||
// fmt.Println(count)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
/*
|
||||
// ~~~ THE CLASSIC WAY ~~~
|
||||
try {
|
||||
// open a file
|
||||
// throws an exception
|
||||
} catch (ExceptionType name) {
|
||||
// handle the error
|
||||
} finally {
|
||||
// close the file
|
||||
}
|
||||
|
||||
// ~~~ GO WAY ~~~
|
||||
file, err := // open the file
|
||||
if err != nil {
|
||||
// handle the error
|
||||
}
|
||||
// close the file
|
||||
|
||||
// really really exceptional problem occurs
|
||||
// mostly due to a programmer
|
||||
|
||||
// panic("PANIC PANIC")
|
||||
*/
|
||||
|
||||
func main() {
|
||||
files := []string{
|
||||
"pngs/cups-jpg.png",
|
||||
"pngs/forest-jpg.png",
|
||||
"pngs/golden.png",
|
||||
"pngs/work.png",
|
||||
"pngs/shakespeare-text.png",
|
||||
"pngs/empty.png",
|
||||
}
|
||||
|
||||
list("png", files)
|
||||
|
||||
// fmt.Println("catch me if you can!")
|
||||
}
|
||||
|
||||
func list(format string, files []string) {
|
||||
valids := detect(format, files)
|
||||
|
||||
fmt.Printf("Correct Files:\n")
|
||||
for _, valid := range valids {
|
||||
fmt.Printf(" + %s\n", valid)
|
||||
}
|
||||
}
|
||||
|
||||
func detect(format string, filenames []string) (valids []string) {
|
||||
header := headerOf(format)
|
||||
|
||||
buf := make([]byte, len(header))
|
||||
|
||||
for _, filename := range filenames {
|
||||
if read(filename, buf) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal([]byte(header), buf) {
|
||||
valids = append(valids, filename)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func headerOf(format string) string {
|
||||
switch format {
|
||||
case "png":
|
||||
return "\x89PNG\r\n\x1a\n"
|
||||
case "jpg":
|
||||
return "\xff\xd8\xff"
|
||||
}
|
||||
panic("unknown format: " + format)
|
||||
}
|
||||
|
||||
// read reads len(buf) bytes to buf from a file
|
||||
func read(filename string, buf []byte) error {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.Size() <= int64(len(buf)) {
|
||||
return fmt.Errorf("file size < len(buf)")
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(file, buf)
|
||||
return err
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,12 @@
|
||||
come night come romeo come thou day in night
|
||||
for thou wilt lie upon the wings of night
|
||||
whiter than new snow on a raven's back
|
||||
come gentle night come loving black-browed night
|
||||
give me my romeo and when he shall die
|
||||
take him and cut him out in little stars
|
||||
and he will make the face of heaven so fine
|
||||
that all the world will be in love with night
|
||||
and pay no worship to the garish sun
|
||||
oh i have bought the mansion of love
|
||||
but not possessed it and though i am sold
|
||||
not yet enjoyed
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,146 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
files := []string{
|
||||
"pngs/cups-jpg.png",
|
||||
"pngs/forest-jpg.png",
|
||||
"pngs/golden.png",
|
||||
"pngs/work.png",
|
||||
"pngs/shakespeare-text.png",
|
||||
"pngs/empty.png",
|
||||
}
|
||||
|
||||
valids := detect(files)
|
||||
|
||||
fmt.Printf("Correct Files:\n")
|
||||
for _, valid := range valids {
|
||||
fmt.Printf(" + %s\n", valid)
|
||||
}
|
||||
}
|
||||
|
||||
func detect(filenames []string) (pngs []string) {
|
||||
const pngHeader = "\x89PNG\r\n\x1a\n"
|
||||
|
||||
buf := make([]byte, len(pngHeader))
|
||||
|
||||
for _, filename := range filenames {
|
||||
if read(filename, buf) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal([]byte(pngHeader), buf) {
|
||||
pngs = append(pngs, filename)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// read reads len(buf) bytes to buf from a file
|
||||
func read(filename string, buf []byte) error {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.Size() <= int64(len(buf)) {
|
||||
return fmt.Errorf("file size < len(buf)")
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(file, buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func detectPNGUnsafeAndVerbose(filenames []string) (valids []string) {
|
||||
const pngHeader = "\x89PNG\r\n\x1a\n"
|
||||
|
||||
buf := make([]byte, len(pngHeader))
|
||||
|
||||
for _, filename := range filenames {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
file.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
if fi.Size() <= int64(len(pngHeader)) {
|
||||
file.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(file, buf)
|
||||
file.Close()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal([]byte(pngHeader), buf) {
|
||||
valids = append(valids, filename)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// defers don't run when the loop block ends
|
||||
func detectPNGWrong(filenames []string) (pngs []string) {
|
||||
const pngHeader = "\x89PNG\r\n\x1a\n"
|
||||
|
||||
buf := make([]byte, len(pngHeader))
|
||||
|
||||
for _, filename := range filenames {
|
||||
fmt.Printf("processing: %s\n", filename)
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
defer func() {
|
||||
fmt.Printf("closes : %s\n", filename)
|
||||
file.Close()
|
||||
}()
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if fi.Size() <= int64(len(pngHeader)) {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(file, buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal([]byte(pngHeader), buf) {
|
||||
pngs = append(pngs, filename)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,12 @@
|
||||
come night come romeo come thou day in night
|
||||
for thou wilt lie upon the wings of night
|
||||
whiter than new snow on a raven's back
|
||||
come gentle night come loving black-browed night
|
||||
give me my romeo and when he shall die
|
||||
take him and cut him out in little stars
|
||||
and he will make the face of heaven so fine
|
||||
that all the world will be in love with night
|
||||
and pay no worship to the garish sun
|
||||
oh i have bought the mansion of love
|
||||
but not possessed it and though i am sold
|
||||
not yet enjoyed
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,99 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
fmt.Println("panicked:", rerr)
|
||||
}
|
||||
}()
|
||||
|
||||
files := []string{
|
||||
"pngs/cups-jpg.png",
|
||||
"pngs/forest-jpg.png",
|
||||
"pngs/golden.png",
|
||||
"pngs/work.png",
|
||||
"pngs/shakespeare-text.png",
|
||||
"pngs/empty.png",
|
||||
}
|
||||
|
||||
args := os.Args[1:]
|
||||
if len(args) != 1 {
|
||||
fmt.Println("png or jpg?")
|
||||
return
|
||||
}
|
||||
|
||||
list(args[0], files)
|
||||
|
||||
// fmt.Println("catch me if you can!")
|
||||
}
|
||||
|
||||
func list(format string, files []string) {
|
||||
valids := detect(format, files)
|
||||
|
||||
fmt.Printf("Correct Files:\n")
|
||||
for _, valid := range valids {
|
||||
fmt.Printf(" + %s\n", valid)
|
||||
}
|
||||
}
|
||||
|
||||
func detect(format string, filenames []string) (pngs []string) {
|
||||
header := headerOf(format)
|
||||
buf := make([]byte, len(header))
|
||||
|
||||
for _, filename := range filenames {
|
||||
if read(filename, buf) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if bytes.Equal([]byte(header), buf) {
|
||||
pngs = append(pngs, filename)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func headerOf(format string) string {
|
||||
switch format {
|
||||
case "png":
|
||||
return "\x89PNG\r\n\x1a\n"
|
||||
case "jpg":
|
||||
return "\xff\xd8\xff"
|
||||
}
|
||||
// this should never occur
|
||||
panic("unknown format: " + format)
|
||||
}
|
||||
|
||||
// read reads len(buf) bytes to buf from a file
|
||||
func read(filename string, buf []byte) error {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fi, err := file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.Size() <= int64(len(buf)) {
|
||||
return fmt.Errorf("file size < len(buf)")
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(file, buf)
|
||||
return err
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,12 @@
|
||||
come night come romeo come thou day in night
|
||||
for thou wilt lie upon the wings of night
|
||||
whiter than new snow on a raven's back
|
||||
come gentle night come loving black-browed night
|
||||
give me my romeo and when he shall die
|
||||
take him and cut him out in little stars
|
||||
and he will make the face of heaven so fine
|
||||
that all the world will be in love with night
|
||||
and pay no worship to the garish sun
|
||||
oh i have bought the mansion of love
|
||||
but not possessed it and though i am sold
|
||||
not yet enjoyed
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
|
||||
"github.com/inancgumus/learngo/magic"
|
||||
//
|
||||
// Use the following one for the panicking library:
|
||||
// magic "github.com/inancgumus/learngo/magicpanic"
|
||||
//
|
||||
)
|
||||
|
||||
func main() {
|
||||
files := []string{
|
||||
"pngs/cups-jpg.png",
|
||||
"pngs/forest-jpg.png",
|
||||
"pngs/golden.png",
|
||||
"pngs/work.png",
|
||||
"pngs/shakespeare-text.png",
|
||||
"pngs/empty.png",
|
||||
}
|
||||
|
||||
args := os.Args[1:]
|
||||
if len(args) != 1 {
|
||||
fmt.Println("png or jpg?")
|
||||
return
|
||||
}
|
||||
|
||||
list(args[0], files)
|
||||
|
||||
// fmt.Println("catch me if you can!")
|
||||
}
|
||||
|
||||
func list(format string, files []string) {
|
||||
valids, err := magic.Detect(format, files)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Correct Files:\n")
|
||||
for _, valid := range valids {
|
||||
fmt.Printf(" + %s\n", valid)
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,12 @@
|
||||
come night come romeo come thou day in night
|
||||
for thou wilt lie upon the wings of night
|
||||
whiter than new snow on a raven's back
|
||||
come gentle night come loving black-browed night
|
||||
give me my romeo and when he shall die
|
||||
take him and cut him out in little stars
|
||||
and he will make the face of heaven so fine
|
||||
that all the world will be in love with night
|
||||
and pay no worship to the garish sun
|
||||
oh i have bought the mansion of love
|
||||
but not possessed it and though i am sold
|
||||
not yet enjoyed
|
||||
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// EXERCISE: Refactor
|
||||
//
|
||||
// In the headerOf function, instead of using a switch,
|
||||
// use a map. Declare it at the package-level.
|
||||
//
|
||||
// RESTRICTION
|
||||
//
|
||||
// For panicking, use a deferred function and a named param.
|
||||
//
|
||||
// EXPECTED OUTPUT
|
||||
//
|
||||
// Please run the solution.
|
||||
//
|
||||
// ---------------------------------------------------------
|
||||
|
||||
func main() {
|
||||
fmt.Println(headerOf("gif"))
|
||||
}
|
||||
|
||||
func headerOf(format string) string {
|
||||
switch format {
|
||||
case "png":
|
||||
return "\x89PNG\r\n\x1a\n"
|
||||
case "jpg":
|
||||
return "\xff\xd8\xff"
|
||||
}
|
||||
panic("unknown format: " + format)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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() {
|
||||
fmt.Println(headerOf("gif"))
|
||||
}
|
||||
|
||||
var headers = map[string]string{
|
||||
"png": "\x89PNG\r\n\x1a\n",
|
||||
"jpg": "\xff\xd8\xff",
|
||||
}
|
||||
|
||||
func headerOf(format string) (header string) {
|
||||
defer func() {
|
||||
if header == "" {
|
||||
panic("unknown format: " + format)
|
||||
}
|
||||
}()
|
||||
return headers[format]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Header
|
||||
|
||||
What you will learn?
|
||||
|
||||
1. **[Refactor](https://github.com/inancgumus/learngo/tree/master/28-error-handling/exercises/01-refactor)**
|
||||
|
||||
Refactor the headerOf function with a map + defer + and a named param.
|
||||
|
||||
2. **[text](https://github.com/inancgumus/learngo/tree/master/)**
|
||||
|
||||
text
|
||||
@@ -0,0 +1,7 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
r:
|
||||
go run . < ../logs/log.txt
|
||||
|
||||
t:
|
||||
time go run . < ../logs/log.txt
|
||||
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
c "github.com/wcharczuk/go-chart"
|
||||
)
|
||||
|
||||
func chartWriter(w io.Writer) outputFn {
|
||||
return func(res []result) error {
|
||||
return chartWrite(w, res)
|
||||
}
|
||||
}
|
||||
|
||||
func chartWrite(w io.Writer, res []result) error {
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
return res[i].domain > res[j].domain
|
||||
})
|
||||
|
||||
donut := c.DonutChart{
|
||||
Title: "Total Visits Per Domain",
|
||||
TitleStyle: c.Style{
|
||||
FontSize: 35,
|
||||
Show: true,
|
||||
FontColor: c.ColorAlternateGreen,
|
||||
},
|
||||
Width: 1920,
|
||||
Height: 800,
|
||||
}
|
||||
|
||||
for _, r := range res {
|
||||
v := c.Value{
|
||||
Label: r.domain + r.page + ": " + strconv.Itoa(r.visits),
|
||||
Value: float64(r.visits),
|
||||
Style: c.Style{
|
||||
FontSize: 14,
|
||||
},
|
||||
}
|
||||
|
||||
donut.Values = append(donut.Values, v)
|
||||
}
|
||||
|
||||
return donut.Render(c.SVG, w)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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 err != nil || n < 0 {
|
||||
f.err = fmt.Errorf("incorrect field -> %q = %q", name, val)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func atoi(input []byte) (int, error) {
|
||||
val := 0
|
||||
for i := 0; i < len(input); i++ {
|
||||
char := input[i]
|
||||
if char < '0' || char > '9' {
|
||||
return 0, errors.New("invalid number")
|
||||
}
|
||||
val = val*10 + int(char) - '0'
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
func noopFilter(r result) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func notUsing(filter filterFn) filterFn {
|
||||
return func(r result) bool {
|
||||
return !filter(r)
|
||||
}
|
||||
}
|
||||
|
||||
func domainExtFilter(domains ...string) filterFn {
|
||||
return func(r result) bool {
|
||||
for _, domain := range domains {
|
||||
if strings.HasSuffix(r.domain, "."+domain) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func domainFilter(domain string) filterFn {
|
||||
return func(r result) bool {
|
||||
return strings.Contains(r.domain, domain)
|
||||
}
|
||||
}
|
||||
|
||||
func orgDomainsFilter(r result) bool {
|
||||
return strings.HasSuffix(r.domain, ".org")
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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
|
||||
}
|
||||
|
||||
// groupBy allocates map unnecessarily
|
||||
func noopGrouper(r result) string {
|
||||
// with something like:
|
||||
// return randomStrings()
|
||||
return ""
|
||||
}
|
||||
@@ -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 (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
p := pipeline{
|
||||
read: textReader(os.Stdin),
|
||||
write: textWriter(os.Stdout),
|
||||
filter: notUsing(domainExtFilter("io", "com")),
|
||||
group: domainGrouper,
|
||||
}
|
||||
|
||||
// var p pipeline
|
||||
// p.
|
||||
// filterBy(notUsing(domainExtFilter("io", "com"))).
|
||||
// groupBy(domainGrouper)
|
||||
|
||||
if err := p.start(); err != nil {
|
||||
fmt.Println("> Err:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// []outputter{textFile("results.txt"), chartFile("graph.png")}
|
||||
|
||||
// func outputs(w io.Writer) outputFn {
|
||||
// tw := textWriter(w)
|
||||
// cw := chartWriter(w)
|
||||
|
||||
// return func(rs []result) error {
|
||||
// err := tw(rs)
|
||||
// err = cw(rs)
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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 "os"
|
||||
|
||||
type (
|
||||
processFn func(r result)
|
||||
inputFn func(processFn) error
|
||||
outputFn func([]result) error
|
||||
filterFn func(result) (include bool)
|
||||
groupFn func(result) (key string)
|
||||
)
|
||||
|
||||
type pipeline struct {
|
||||
read inputFn
|
||||
write outputFn
|
||||
filter filterFn
|
||||
group groupFn
|
||||
}
|
||||
|
||||
func (p *pipeline) filterBy(f filterFn) *pipeline { p.filter = f; return p }
|
||||
func (p *pipeline) groupBy(f groupFn) *pipeline { p.group = f; return p }
|
||||
func (p *pipeline) from(f inputFn) *pipeline { p.read = f; return p }
|
||||
func (p *pipeline) to(f outputFn) *pipeline { p.write = f; return p }
|
||||
|
||||
func (p *pipeline) defaults() {
|
||||
if p.filter == nil {
|
||||
p.filter = noopFilter
|
||||
}
|
||||
|
||||
if p.group == nil {
|
||||
p.group = domainGrouper
|
||||
}
|
||||
|
||||
if p.read == nil {
|
||||
p.read = textReader(os.Stdin)
|
||||
}
|
||||
|
||||
if p.write == nil {
|
||||
p.write = textWriter(os.Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipeline) start() error {
|
||||
p.defaults()
|
||||
|
||||
// retrieve and process the lines
|
||||
sum := make(map[string]result)
|
||||
|
||||
process := func(r result) {
|
||||
if !p.filter(r) {
|
||||
return
|
||||
}
|
||||
|
||||
k := p.group(r)
|
||||
sum[k] = r.add(sum[k])
|
||||
}
|
||||
|
||||
// return err from input reader
|
||||
if err := p.read(process); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prepare the results for outputting
|
||||
var out []result
|
||||
for _, res := range sum {
|
||||
out = append(out, res)
|
||||
}
|
||||
|
||||
// return err from output reader
|
||||
return p.write(out)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const fieldsLength = 4
|
||||
|
||||
// result stores the parsed result for a domain
|
||||
type result struct {
|
||||
domain string
|
||||
page string
|
||||
visits int
|
||||
uniques int
|
||||
}
|
||||
|
||||
// add adds the metrics of another result
|
||||
func (r result) add(other result) result {
|
||||
r.visits += other.visits
|
||||
r.uniques += other.uniques
|
||||
return r
|
||||
}
|
||||
|
||||
// parseFields parses and returns the parsing result
|
||||
func parseFields(line string) (r result, err error) {
|
||||
fields := strings.Fields(line)
|
||||
|
||||
if len(fields) != fieldsLength {
|
||||
return r, fmt.Errorf("wrong number of fields %q", 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
|
||||
}
|
||||
|
||||
func fastParseFields(data []byte) (res result, err error) {
|
||||
const separator = ' '
|
||||
|
||||
var findex int
|
||||
|
||||
for i, j := 0, 0; i < len(data); i++ {
|
||||
c := data[i]
|
||||
last := len(data) == i+1
|
||||
|
||||
if c != separator && !last {
|
||||
continue
|
||||
}
|
||||
|
||||
if last {
|
||||
i = len(data)
|
||||
}
|
||||
|
||||
switch fval := data[j:i]; findex {
|
||||
case 0:
|
||||
res.domain = string(fval)
|
||||
case 1:
|
||||
res.page = string(fval)
|
||||
case 2:
|
||||
res.visits, err = atoi(fval)
|
||||
case 3:
|
||||
res.uniques, err = atoi(fval)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
j = i + 1
|
||||
findex++
|
||||
}
|
||||
|
||||
if findex != fieldsLength {
|
||||
err = fmt.Errorf("wrong number of fields %q", data)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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) inputFn {
|
||||
return func(process processFn) error {
|
||||
var (
|
||||
l = 1
|
||||
in = bufio.NewScanner(r)
|
||||
)
|
||||
|
||||
for in.Scan() {
|
||||
r, err := fastParseFields(in.Bytes())
|
||||
// r, err := parseFields(in.Text())
|
||||
if err != nil {
|
||||
return fmt.Errorf("line %d: %v", l, err)
|
||||
}
|
||||
|
||||
process(r)
|
||||
l++
|
||||
}
|
||||
|
||||
if c, ok := r.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
return in.Err()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO: sort by result key interfaces section
|
||||
|
||||
const (
|
||||
|
||||
// DOMAINS PAGES VISITS UNIQUES
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
header = "%-25s %-10s %10s %10s\n"
|
||||
line = "%-25s %-10s %10d %10d\n"
|
||||
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
|
||||
dash = "-"
|
||||
dashLength = 58
|
||||
)
|
||||
|
||||
func textWriter(w io.Writer) outputFn {
|
||||
return func(res []result) error {
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
return res[i].domain > res[j].domain
|
||||
})
|
||||
|
||||
var total result
|
||||
|
||||
fmt.Fprintf(w, header, "DOMAINS", "PAGES", "VISITS", "UNIQUES")
|
||||
fmt.Fprintf(w, strings.Repeat(dash, dashLength)+"\n")
|
||||
|
||||
for _, r := range res {
|
||||
total = total.add(r)
|
||||
fmt.Fprintf(w, line, r.domain, r.page, r.visits, r.uniques)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, footer, "", total.visits, total.uniques)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func noWhere() outputFn {
|
||||
return func(res []result) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
## CHANGES
|
||||
|
||||
### PROBLEM
|
||||
+ adding new fields makes the code complex
|
||||
+ needs to update: `result`, `parser`, `summarizer`
|
||||
+ needs to add new fields to `parser`: `totalVisits` + `totalUniques`
|
||||
+ in `parse()`: repeating line errors
|
||||
+ if we parsing out of it we'd need to have *parser — superfluous
|
||||
|
||||
### SOLUTION
|
||||
+ move all the result related logic to result.go
|
||||
|
||||
+ move `parser.go/result` -> `result.go`
|
||||
+ move `parser.go/parsing` logic -> `result.go`
|
||||
|
||||
+ add `addResult` -> `result.go`
|
||||
+ remove `parser struct`'s: `totalVisits`, `totalUniques`
|
||||
+ change `update()`'s last line: `p.sum[r.domain] = addResult`
|
||||
|
||||
+ remove `(line #d)` errors from `result.go`
|
||||
+ add: `return r, err` — named params are error prone
|
||||
+ always check for the error first
|
||||
+ `if r.visits < 0 || err != nil` -> `if err != nil || r.visits < 0`
|
||||
|
||||
+ `parser.go`: check the `parseFields()`:
|
||||
```golang
|
||||
r, err := parseFields(line)
|
||||
if err != nil {
|
||||
p.lerr = fmt.Errorf("line %d: %v", p.lines, err)
|
||||
}```
|
||||
|
||||
+ - `parser.go` and `summarize.go`
|
||||
- remove `total int`
|
||||
- let `summarize()` calculate the totals
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
func main() {
|
||||
p := newParser()
|
||||
|
||||
in := bufio.NewScanner(os.Stdin)
|
||||
for in.Scan() {
|
||||
parsed := parse(p, in.Text())
|
||||
update(p, parsed)
|
||||
}
|
||||
|
||||
summarize(p)
|
||||
dumpErrs([]error{in.Err(), err(p)})
|
||||
}
|
||||
|
||||
// dumpErrs simplifies handling multiple errors
|
||||
func dumpErrs(errs []error) {
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
fmt.Println("> Err:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// parser keeps track of the parsing
|
||||
type parser struct {
|
||||
sum map[string]result // metrics per domain
|
||||
domains []string // unique domain names
|
||||
lines int // number of parsed lines (for the error messages)
|
||||
lerr error // the last error occurred
|
||||
|
||||
// totalVisits int // total visits for all domains
|
||||
// totalUniques int // total uniques for all domains
|
||||
}
|
||||
|
||||
// newParser constructs, initializes and returns a new parser
|
||||
func newParser() *parser {
|
||||
return &parser{sum: make(map[string]result)}
|
||||
}
|
||||
|
||||
// parse a log line and return the result
|
||||
func parse(p *parser, line string) (r result) {
|
||||
if p.lerr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.lines++
|
||||
|
||||
r, err := parseResult(line)
|
||||
if err != nil {
|
||||
p.lerr = fmt.Errorf("line %d: %v", p.lines, err)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// update the parsing results
|
||||
func update(p *parser, r result) {
|
||||
if p.lerr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Collect the unique domains
|
||||
cur, ok := p.sum[r.domain]
|
||||
if !ok {
|
||||
p.domains = append(p.domains, r.domain)
|
||||
}
|
||||
|
||||
// Keep track of total and per domain visits
|
||||
// p.totalVisits += r.visits
|
||||
// p.totalUniques += r.uniques
|
||||
|
||||
// create and assign a new copy of `visit`
|
||||
// p.sum[r.domain] = result{
|
||||
// domain: r.domain,
|
||||
// visits: r.visits + cur.visits,
|
||||
// uniques: r.uniques + cur.uniques,
|
||||
// }
|
||||
p.sum[r.domain] = addResult(r, cur)
|
||||
}
|
||||
|
||||
// err returns the last error encountered
|
||||
func err(p *parser) error {
|
||||
return p.lerr
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const fieldsLength = 4
|
||||
|
||||
// result stores the parsed result for a domain
|
||||
type result struct {
|
||||
domain, page string
|
||||
visits, uniques int
|
||||
// add more metrics if needed
|
||||
}
|
||||
|
||||
// parseResult from a log line
|
||||
func parseResult(line string) (r result, err error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != fieldsLength {
|
||||
return r, fmt.Errorf("wrong input: %v", fields)
|
||||
}
|
||||
|
||||
r.domain = fields[0]
|
||||
r.page = fields[1]
|
||||
|
||||
r.visits, err = strconv.Atoi(fields[2])
|
||||
if err != nil || r.visits < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[2])
|
||||
}
|
||||
|
||||
r.uniques, err = strconv.Atoi(fields[3])
|
||||
if err != nil || r.uniques < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[3])
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// addResult to another one
|
||||
func addResult(r, other result) result {
|
||||
r.visits += other.visits
|
||||
r.uniques += other.uniques
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// DOMAINS PAGES VISITS UNIQUES
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
header = "%-25s %-10s %10s %10s\n"
|
||||
line = "%-25s %-10s %10d %10d\n"
|
||||
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
|
||||
dash = "-"
|
||||
dashLength = 58
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
func summarize(p *parser) {
|
||||
sort.Strings(p.domains)
|
||||
|
||||
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
|
||||
fmt.Println(strings.Repeat("-", dashLength))
|
||||
|
||||
var total result
|
||||
|
||||
for _, domain := range p.domains {
|
||||
r := p.sum[domain]
|
||||
total = addResult(total, r)
|
||||
|
||||
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
|
||||
}
|
||||
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
### PROBLEM
|
||||
+ `main.go` (api client) does a lot of things:
|
||||
+ read the log input
|
||||
+ parse line by line
|
||||
+ updates the results
|
||||
+ display the results
|
||||
|
||||
+ inflexible:
|
||||
+ filter by extension (can change)
|
||||
+ group by domain (can change) — group by page?
|
||||
|
||||
## SOLUTION
|
||||
+ hide the parsing api from the client
|
||||
|
||||
+ move `main.go/scanner` -> `parser.go/parse()`
|
||||
+ add `main.go`: err handling from `parse()`
|
||||
|
||||
+ `parser.go/parse()` -> return err directly
|
||||
+ remove: `if p.lerr != nil { return }` from parse() and update()
|
||||
+ remove: `dumpErrs`
|
||||
+ remove: `parser.go/err()`
|
||||
+ remove `parser.go/lerr`
|
||||
+ return `in.Err()` from `parse()`
|
||||
|
||||
+ remove: `p.lines++`
|
||||
+ `return r, fmt.Errorf("line %d: %v", p.lines, err)`
|
||||
+ remove: `lines int`
|
||||
+ `parse()` and `parse()` becomes:
|
||||
```golang
|
||||
func parse(p *parser, line string) (result, error) {
|
||||
return parseFields(line)
|
||||
}
|
||||
|
||||
func parse(p *parser) {
|
||||
// ...
|
||||
r, err := parse(p, in.Text())
|
||||
if err != nil {
|
||||
return fmt.Errorf("line %d: %v", p.lines, err)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
+ remove `parse()`
|
||||
+ call `parseFields` directly in `parse()`:
|
||||
```go
|
||||
var (
|
||||
l = 1
|
||||
in = bufio.NewScanner(os.Stdin)
|
||||
)
|
||||
|
||||
for in.Scan() {
|
||||
r, err := parseFields(in.Text())
|
||||
if err != nil {
|
||||
return fmt.Errorf("line %d: %v", l, err)
|
||||
}
|
||||
|
||||
update(p, r)
|
||||
l++
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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() {
|
||||
p := newParser()
|
||||
|
||||
if err := parse(p); err != nil {
|
||||
fmt.Println("> Err:", err)
|
||||
return
|
||||
}
|
||||
|
||||
summarize(p)
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// parser keeps track of the parsing
|
||||
type parser struct {
|
||||
sum map[string]result // metrics per domain
|
||||
domains []string // unique domain names
|
||||
}
|
||||
|
||||
// newParser constructs, initializes and returns a new parser
|
||||
func newParser() *parser {
|
||||
return &parser{sum: make(map[string]result)}
|
||||
}
|
||||
|
||||
// parse the log lines and return results
|
||||
func parse(p *parser) error {
|
||||
var (
|
||||
l = 1
|
||||
in = bufio.NewScanner(os.Stdin)
|
||||
)
|
||||
|
||||
for in.Scan() {
|
||||
r, err := parseResult(in.Text())
|
||||
if err != nil {
|
||||
return fmt.Errorf("line %d: %v", l, err)
|
||||
}
|
||||
|
||||
l++
|
||||
|
||||
update(p, r)
|
||||
}
|
||||
|
||||
return in.Err()
|
||||
}
|
||||
|
||||
// update the parsing results
|
||||
func update(p *parser, r result) {
|
||||
// Collect the unique domains
|
||||
if _, ok := p.sum[r.domain]; !ok {
|
||||
p.domains = append(p.domains, r.domain)
|
||||
}
|
||||
|
||||
// create and assign a new copy of `visit`
|
||||
p.sum[r.domain] = addResult(r, p.sum[r.domain])
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const fieldsLength = 4
|
||||
|
||||
// result stores the parsed result for a domain
|
||||
type result struct {
|
||||
domain, page string
|
||||
visits, uniques int
|
||||
// add more metrics if needed
|
||||
}
|
||||
|
||||
// parseResult from a log line
|
||||
func parseResult(line string) (r result, err error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != fieldsLength {
|
||||
return r, fmt.Errorf("wrong input: %v", fields)
|
||||
}
|
||||
|
||||
r.domain = fields[0]
|
||||
r.page = fields[1]
|
||||
|
||||
r.visits, err = strconv.Atoi(fields[2])
|
||||
if err != nil || r.visits < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[2])
|
||||
}
|
||||
|
||||
r.uniques, err = strconv.Atoi(fields[3])
|
||||
if err != nil || r.uniques < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[3])
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// addResult to another one
|
||||
func addResult(r, other result) result {
|
||||
r.visits += other.visits
|
||||
r.uniques += other.uniques
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
// + violation: accesses the parsing internals: p.domains + p.sum + p.total
|
||||
// + give it the []result only.
|
||||
// + let it calculate the total.
|
||||
const (
|
||||
|
||||
// DOMAINS PAGES VISITS UNIQUES
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
header = "%-25s %-10s %10s %10s\n"
|
||||
line = "%-25s %-10s %10d %10d\n"
|
||||
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
|
||||
dash = "-"
|
||||
dashLength = 58
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
func summarize(p *parser) {
|
||||
sort.Strings(p.domains)
|
||||
|
||||
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
|
||||
fmt.Println(strings.Repeat("-", dashLength))
|
||||
|
||||
var total result
|
||||
|
||||
for _, domain := range p.domains {
|
||||
r := p.sum[domain]
|
||||
total = addResult(total, r)
|
||||
|
||||
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
|
||||
}
|
||||
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
### PROBLEM
|
||||
+ `summarize()` knows a lot about the internals of the `parser`.
|
||||
+ coupled to the `parser`.
|
||||
|
||||
## SOLUTION
|
||||
+ remove: `parser.go` `sum` and `domains` fields
|
||||
+ remove: `parser.go/newParser()`
|
||||
+ change: `parser.go/parse(p *parser) error` -> `parse() ([]result, error)`
|
||||
+ initialize: `sum` inside `parse()`
|
||||
+ remove: `update()`
|
||||
+ call: `sum` update in the `parse()`
|
||||
+ collect the grouped results and return them from `parser()`
|
||||
|
||||
+ `summarize(p *parser)` -> `summarize([]result)`
|
||||
+ in `summarize()`
|
||||
+ `sort.Slice`
|
||||
+ range over `[]result`
|
||||
|
||||
+ `main.go`
|
||||
+ just: `res, err := parse()`
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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() {
|
||||
res, err := parse()
|
||||
if err != nil {
|
||||
fmt.Println("> Err:", err)
|
||||
return
|
||||
}
|
||||
|
||||
summarize(res)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// parser keeps track of the parsing
|
||||
type parser struct {
|
||||
}
|
||||
|
||||
// parse the log lines and return results
|
||||
func parse() ([]result, error) {
|
||||
var (
|
||||
l = 1
|
||||
in = bufio.NewScanner(os.Stdin)
|
||||
sum = make(map[string]result)
|
||||
)
|
||||
|
||||
// parse the log lines
|
||||
for in.Scan() {
|
||||
r, err := parseResult(in.Text())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("line %d: %v", l, err)
|
||||
}
|
||||
|
||||
l++
|
||||
|
||||
// group the log lines by domain
|
||||
sum[r.domain] = addResult(r, sum[r.domain])
|
||||
}
|
||||
|
||||
// collect the grouped results
|
||||
res := make([]result, 0, len(sum))
|
||||
for _, r := range sum {
|
||||
res = append(res, r)
|
||||
}
|
||||
|
||||
return res, in.Err()
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const fieldsLength = 4
|
||||
|
||||
// result stores the parsed result for a domain
|
||||
type result struct {
|
||||
domain, page string
|
||||
visits, uniques int
|
||||
// add more metrics if needed
|
||||
}
|
||||
|
||||
// parseResult from a log line
|
||||
func parseResult(line string) (r result, err error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != fieldsLength {
|
||||
return r, fmt.Errorf("wrong input: %v", fields)
|
||||
}
|
||||
|
||||
r.domain = fields[0]
|
||||
r.page = fields[1]
|
||||
|
||||
r.visits, err = strconv.Atoi(fields[2])
|
||||
if err != nil || r.visits < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[2])
|
||||
}
|
||||
|
||||
r.uniques, err = strconv.Atoi(fields[3])
|
||||
if err != nil || r.uniques < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[3])
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// addResult to another one
|
||||
func addResult(r, other result) result {
|
||||
r.visits += other.visits
|
||||
r.uniques += other.uniques
|
||||
return r
|
||||
}
|
||||
@@ -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"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
// + violation: accesses the parsing internals: p.domains + p.sum + p.total
|
||||
// + give it the []result only.
|
||||
// + let it calculate the total.
|
||||
const (
|
||||
|
||||
// DOMAINS PAGES VISITS UNIQUES
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
header = "%-25s %-10s %10s %10s\n"
|
||||
line = "%-25s %-10s %10d %10d\n"
|
||||
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
|
||||
dash = "-"
|
||||
dashLength = 58
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
func summarize(res []result) {
|
||||
// sort.Strings(p.domains)
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
return res[i].domain <= res[j].domain
|
||||
})
|
||||
|
||||
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
|
||||
fmt.Println(strings.Repeat("-", dashLength))
|
||||
|
||||
var total result
|
||||
|
||||
for _, r := range res {
|
||||
total = addResult(total, r)
|
||||
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
|
||||
}
|
||||
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
### PROBLEM
|
||||
+ `parser.go/parse()` also does updating. back to square one.
|
||||
+ we need to extract the reusable behavior: scanning.
|
||||
|
||||
+ inflexible:
|
||||
+ adding a filter is hard. needs to change the `scan()` code.
|
||||
+ adding a grouper is also hard. domain grouping is hardcoded.
|
||||
|
||||
## SOLUTION
|
||||
+
|
||||
|
||||
## IDEAS:
|
||||
|
||||
+ make domain filter accept variadic args
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
/*
|
||||
p := pipeline{
|
||||
read: textReader(os.Stdin),
|
||||
write: textWriter(os.Stdout),
|
||||
filterBy: notUsing(domainExtFilter("io")),
|
||||
groupBy: domainGrouper,
|
||||
}
|
||||
|
||||
if err := start(p); err != nil {
|
||||
fmt.Println("> Err:", err)
|
||||
}
|
||||
*/
|
||||
|
||||
func main() {
|
||||
p := newParser()
|
||||
|
||||
if err := parse(p); err != nil {
|
||||
fmt.Println("> Err:", err)
|
||||
return
|
||||
}
|
||||
|
||||
summarize(p)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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
|
||||
|
||||
// parser keeps track of the parsing
|
||||
type parser struct {
|
||||
sum map[string]result // metrics per domain
|
||||
domains []string // unique domain names
|
||||
}
|
||||
|
||||
// newParser constructs, initializes and returns a new parser
|
||||
func newParser() *parser {
|
||||
return &parser{sum: make(map[string]result)}
|
||||
}
|
||||
|
||||
// parse all the log lines and update the results
|
||||
func parse(p *parser) error {
|
||||
process := func(r result) {
|
||||
update(p, r)
|
||||
}
|
||||
|
||||
err := scan(process)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func update(p *parser, r result) {
|
||||
// Collect the unique domains
|
||||
if _, ok := p.sum[r.domain]; !ok {
|
||||
p.domains = append(p.domains, r.domain)
|
||||
}
|
||||
|
||||
// create and assign a new copy of `visit`
|
||||
p.sum[r.domain] = addResult(r, p.sum[r.domain])
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const fieldsLength = 4
|
||||
|
||||
// result stores the parsed result for a domain
|
||||
type result struct {
|
||||
domain, page string
|
||||
visits, uniques int
|
||||
// add more metrics if needed
|
||||
}
|
||||
|
||||
// parseResult from a log line
|
||||
func parseResult(line string) (r result, err error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != fieldsLength {
|
||||
return r, fmt.Errorf("wrong input: %v", fields)
|
||||
}
|
||||
|
||||
r.domain = fields[0]
|
||||
r.page = fields[1]
|
||||
|
||||
r.visits, err = strconv.Atoi(fields[2])
|
||||
if err != nil || r.visits < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[2])
|
||||
}
|
||||
|
||||
r.uniques, err = strconv.Atoi(fields[3])
|
||||
if err != nil || r.uniques < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[3])
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// addResult to another one
|
||||
func addResult(r, other result) result {
|
||||
r.visits += other.visits
|
||||
r.uniques += other.uniques
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
type processFn func(r result)
|
||||
|
||||
func scan(process processFn) error {
|
||||
var (
|
||||
l = 1
|
||||
in = bufio.NewScanner(os.Stdin)
|
||||
)
|
||||
|
||||
for in.Scan() {
|
||||
r, err := parseResult(in.Text())
|
||||
if err != nil {
|
||||
return fmt.Errorf("line %d: %v", l, err)
|
||||
}
|
||||
|
||||
l++
|
||||
|
||||
process(r)
|
||||
}
|
||||
return in.Err()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
// + violation: accesses the parsing internals: p.domains + p.sum + p.total
|
||||
// + give it the []result only.
|
||||
// + let it calculate the total.
|
||||
const (
|
||||
|
||||
// DOMAINS PAGES VISITS UNIQUES
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
header = "%-25s %-10s %10s %10s\n"
|
||||
line = "%-25s %-10s %10d %10d\n"
|
||||
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
|
||||
dash = "-"
|
||||
dashLength = 58
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
func summarize(p *parser) {
|
||||
sort.Strings(p.domains)
|
||||
|
||||
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
|
||||
fmt.Println(strings.Repeat("-", dashLength))
|
||||
|
||||
var total result
|
||||
|
||||
for _, domain := range p.domains {
|
||||
r := p.sum[domain]
|
||||
total = addResult(total, r)
|
||||
|
||||
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
|
||||
}
|
||||
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
### PROBLEM
|
||||
+ ...
|
||||
|
||||
## SOLUTION
|
||||
+ `parser struct` -> `pipeline struct`
|
||||
+ `parse()` -> `pipe(pipeline)`
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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() {
|
||||
pipes := pipeline{
|
||||
// read: textParser(),
|
||||
// write: textSummary(),
|
||||
// filterBy: notUsing(domainExtFilter("io", "com")),
|
||||
// groupBy: domainGrouper,
|
||||
}
|
||||
|
||||
res, err := pipe(pipes)
|
||||
if err != nil {
|
||||
fmt.Println("> Err:", err)
|
||||
return
|
||||
}
|
||||
|
||||
summarize(res)
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// pipeline determines the behavior of log processing
|
||||
type pipeline struct {
|
||||
}
|
||||
|
||||
// pipe the log lines through funcs and produce a result
|
||||
func pipe(opts pipeline) ([]result, error) {
|
||||
var (
|
||||
l = 1
|
||||
in = bufio.NewScanner(os.Stdin)
|
||||
sum = make(map[string]result)
|
||||
)
|
||||
|
||||
// parse the log lines
|
||||
for in.Scan() {
|
||||
r, err := parseResult(in.Text())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("line %d: %v", l, err)
|
||||
}
|
||||
|
||||
l++
|
||||
|
||||
// group the log lines by domain
|
||||
sum[r.domain] = addResult(r, sum[r.domain])
|
||||
}
|
||||
|
||||
// collect the grouped results
|
||||
res := make([]result, 0, len(sum))
|
||||
for _, r := range sum {
|
||||
res = append(res, r)
|
||||
}
|
||||
|
||||
return res, in.Err()
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const fieldsLength = 4
|
||||
|
||||
// result stores the parsed result for a domain
|
||||
type result struct {
|
||||
domain, page string
|
||||
visits, uniques int
|
||||
// add more metrics if needed
|
||||
}
|
||||
|
||||
// parseResult from a log line
|
||||
func parseResult(line string) (r result, err error) {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) != fieldsLength {
|
||||
return r, fmt.Errorf("wrong input: %v", fields)
|
||||
}
|
||||
|
||||
r.domain = fields[0]
|
||||
r.page = fields[1]
|
||||
|
||||
r.visits, err = strconv.Atoi(fields[2])
|
||||
if err != nil || r.visits < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[2])
|
||||
}
|
||||
|
||||
r.uniques, err = strconv.Atoi(fields[3])
|
||||
if err != nil || r.uniques < 0 {
|
||||
return r, fmt.Errorf("wrong input: %q", fields[3])
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// addResult to another one
|
||||
func addResult(r, other result) result {
|
||||
r.visits += other.visits
|
||||
r.uniques += other.uniques
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
// + violation: accesses the parsing internals: p.domains + p.sum + p.total
|
||||
// + give it the []result only.
|
||||
// + let it calculate the total.
|
||||
const (
|
||||
|
||||
// DOMAINS PAGES VISITS UNIQUES
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
header = "%-25s %-10s %10s %10s\n"
|
||||
line = "%-25s %-10s %10d %10d\n"
|
||||
footer = "\n%-36s %10d %10d\n" // -> "" VISITS UNIQUES
|
||||
dash = "-"
|
||||
dashLength = 58
|
||||
)
|
||||
|
||||
// summarize summarizes and prints the parsing result
|
||||
func summarize(res []result) {
|
||||
sort.Slice(res, func(i, j int) bool {
|
||||
return res[i].domain <= res[j].domain
|
||||
})
|
||||
|
||||
fmt.Printf(header, "DOMAIN", "PAGES", "VISITS", "UNIQUES")
|
||||
fmt.Println(strings.Repeat("-", dashLength))
|
||||
|
||||
var total result
|
||||
|
||||
for _, r := range res {
|
||||
total = addResult(total, r)
|
||||
fmt.Printf(line, r.domain, r.page, r.visits, r.uniques)
|
||||
}
|
||||
fmt.Printf(footer, "TOTAL", total.visits, total.uniques)
|
||||
}
|
||||