From 7d4820d793e491996963d1b9e41d6169fd9ad557 Mon Sep 17 00:00:00 2001 From: hakuta Date: Fri, 23 Nov 2018 01:16:53 +0900 Subject: [PATCH 1/6] implement typing game --- kadai3-1/akuchii/game/game.go | 76 +++++++++++++++++++++++++++++++++++ kadai3-1/akuchii/main.go | 20 +++++++++ 2 files changed, 96 insertions(+) create mode 100644 kadai3-1/akuchii/game/game.go create mode 100644 kadai3-1/akuchii/main.go diff --git a/kadai3-1/akuchii/game/game.go b/kadai3-1/akuchii/game/game.go new file mode 100644 index 0000000..c4ef335 --- /dev/null +++ b/kadai3-1/akuchii/game/game.go @@ -0,0 +1,76 @@ +package game + +import ( + "bufio" + "context" + "fmt" + "io" + "math/rand" + "os" + "time" +) + +// Game generates typing game +type Game struct { + writer io.Writer + idx int + words []string + timeout int +} + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +// NewGame generates new Game instance +func NewGame(w io.Writer, words []string, timeout int) *Game { + return &Game{writer: w, words: words, timeout: timeout} +} + +// Start starts typing game +func (g Game) Start() int { + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, time.Duration(g.timeout)*time.Second) + defer cancel() + + ch := input(os.Stdin) + cnt := 0 +LOOP: + for { + g.setNewIdx() + fmt.Fprintln(g.writer, "> "+g.word()) + select { + case v := <-ch: + if v == g.word() { + cnt++ + fmt.Fprintln(g.writer, "correct!") + } else { + fmt.Fprintln(g.writer, "incorrect!") + } + case <-ctx.Done(): + break LOOP + } + } + return cnt +} + +func (g *Game) setNewIdx() { + rand.Seed(time.Now().UnixNano()) + g.idx = rand.Intn(len(g.words)) +} + +func (g Game) word() string { + return g.words[g.idx] +} + +func input(r io.Reader) <-chan string { + ch := make(chan string) + go func() { + s := bufio.NewScanner(r) + for s.Scan() { + ch <- s.Text() + } + close(ch) + }() + return ch +} diff --git a/kadai3-1/akuchii/main.go b/kadai3-1/akuchii/main.go new file mode 100644 index 0000000..5793363 --- /dev/null +++ b/kadai3-1/akuchii/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "os" + + "github.com/gopherdojo/dojo4/kadai3-1/akuchii/game" +) + +func main() { + timeout := 5 + game := game.NewGame(os.Stdout, getWords(), timeout) + cnt := game.Start() + fmt.Printf("\nfinish!\ncorrect count is %d\n", cnt) + os.Exit(0) +} + +func getWords() []string { + return []string{"csharp", "python", "perl", "cplusplus", "ruby", "golang", "scala"} +} From b3217ec360263a25a8e0467a4bc8dbd98f416492 Mon Sep 17 00:00:00 2001 From: hakuta Date: Sun, 25 Nov 2018 22:41:00 +0900 Subject: [PATCH 2/6] implement range downloader --- kadai3-2/akuchii/downloader/downloader.go | 132 ++++++++++++++++++++++ kadai3-2/akuchii/main.go | 36 ++++++ 2 files changed, 168 insertions(+) create mode 100644 kadai3-2/akuchii/downloader/downloader.go create mode 100644 kadai3-2/akuchii/main.go diff --git a/kadai3-2/akuchii/downloader/downloader.go b/kadai3-2/akuchii/downloader/downloader.go new file mode 100644 index 0000000..4927bd6 --- /dev/null +++ b/kadai3-2/akuchii/downloader/downloader.go @@ -0,0 +1,132 @@ +package downloader + +import ( + "fmt" + "io" + "net/http" + "os" + "strings" + + "golang.org/x/sync/errgroup" +) + +// Downloader downloads data in parallel +type Downloader struct { + url string + fileSize uint + splitNum uint + fileName string +} + +// Range defines range of data of start and end +type Range struct { + start uint + end uint + idx uint +} + +// NewDownloader generate new Downloader instance +func NewDownloader(url string, splitNum uint) *Downloader { + return &Downloader{ + url: url, + splitNum: splitNum, + } +} + +// Prepare prepares for download in parallel +func (d *Downloader) Prepare() error { + res, err := http.Head(d.url) + if err != nil { + return err + } + d.fileSize = uint(res.ContentLength) + + elems := strings.Split(d.url, "/") + d.fileName = elems[len(elems)-1] + + return nil +} + +// Download downloads in parallel +func (d Downloader) Download() error { + var eg errgroup.Group + + for i := uint(0); i < d.splitNum; i++ { + r := d.makeRange(i) + eg.Go(func() error { + return d.request(r) + }) + } + if err := eg.Wait(); err != nil { + return err + } + + return nil +} + +// GetFileName gets name of download file +func (d Downloader) GetFileName() string { + return d.fileName +} + +// MergeFiles merges files downloads in parallel +func (d Downloader) MergeFiles() error { + f, err := os.Create(d.fileName) + if err != nil { + return err + } + defer f.Close() + for i := uint(0); i < d.splitNum; i++ { + tmpFileName := fmt.Sprintf("%s.%d", d.fileName, i) + tmpFile, err := os.Open(tmpFileName) + if err != nil { + return err + } + io.Copy(f, tmpFile) + tmpFile.Close() + if err := os.Remove(tmpFileName); err != nil { + return err + } + } + return nil +} + +func (d Downloader) request(r Range) error { + req, _ := http.NewRequest("GET", d.url, nil) + req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", r.start, r.end)) + client := new(http.Client) + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + tmpFileName := fmt.Sprintf("%s.%d", d.fileName, r.idx) + file, err := os.Create(tmpFileName) + if err != nil { + return err + } + defer file.Close() + + if _, err := io.Copy(file, resp.Body); err != nil { + return err + } + + return nil +} + +func (d Downloader) makeRange(i uint) Range { + splitSize := d.fileSize / d.splitNum + start := i * splitSize + end := (i+1)*splitSize - 1 + + if i == d.splitNum { + end = d.fileSize + } + + return Range{ + start: start, + end: end, + idx: i, + } +} diff --git a/kadai3-2/akuchii/main.go b/kadai3-2/akuchii/main.go new file mode 100644 index 0000000..bc1fde2 --- /dev/null +++ b/kadai3-2/akuchii/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "os" + + "github.com/gopherdojo/dojo4/kadai3-2/akuchii/downloader" +) + +func main() { + if len(os.Args) != 2 { + fmt.Println("require url") + os.Exit(1) + } + + var splitNum uint = 4 + url := os.Args[1] + d := downloader.NewDownloader(url, splitNum) + + if err := d.Prepare(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := d.Download(); err != nil { + fmt.Println(err) + os.Exit(1) + } + + if err := d.MergeFiles(); err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Printf("download complete! : %v\n", d.GetFileName()) + os.Exit(0) +} From c8f07b2905adce85c30c8908d14ae128bc929a1c Mon Sep 17 00:00:00 2001 From: hakuta Date: Mon, 26 Nov 2018 19:25:49 +0900 Subject: [PATCH 3/6] remove unnecessary code --- kadai3-1/akuchii/game/game.go | 1 - 1 file changed, 1 deletion(-) diff --git a/kadai3-1/akuchii/game/game.go b/kadai3-1/akuchii/game/game.go index c4ef335..e38e6ac 100644 --- a/kadai3-1/akuchii/game/game.go +++ b/kadai3-1/akuchii/game/game.go @@ -55,7 +55,6 @@ LOOP: } func (g *Game) setNewIdx() { - rand.Seed(time.Now().UnixNano()) g.idx = rand.Intn(len(g.words)) } From 94a1abd55fa8dbe02cc9dbd84cb4c56faad8d5c7 Mon Sep 17 00:00:00 2001 From: hakuta Date: Mon, 26 Nov 2018 19:27:56 +0900 Subject: [PATCH 4/6] replace os.Stdin with io.Reader interface --- kadai3-1/akuchii/game/game.go | 8 ++++---- kadai3-1/akuchii/main.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/kadai3-1/akuchii/game/game.go b/kadai3-1/akuchii/game/game.go index e38e6ac..2e54ae9 100644 --- a/kadai3-1/akuchii/game/game.go +++ b/kadai3-1/akuchii/game/game.go @@ -6,12 +6,12 @@ import ( "fmt" "io" "math/rand" - "os" "time" ) // Game generates typing game type Game struct { + reader io.Reader writer io.Writer idx int words []string @@ -23,8 +23,8 @@ func init() { } // NewGame generates new Game instance -func NewGame(w io.Writer, words []string, timeout int) *Game { - return &Game{writer: w, words: words, timeout: timeout} +func NewGame(r io.Reader, w io.Writer, words []string, timeout int) *Game { + return &Game{reader: r, writer: w, words: words, timeout: timeout} } // Start starts typing game @@ -33,7 +33,7 @@ func (g Game) Start() int { ctx, cancel := context.WithTimeout(ctx, time.Duration(g.timeout)*time.Second) defer cancel() - ch := input(os.Stdin) + ch := input(g.reader) cnt := 0 LOOP: for { diff --git a/kadai3-1/akuchii/main.go b/kadai3-1/akuchii/main.go index 5793363..0cdc4d5 100644 --- a/kadai3-1/akuchii/main.go +++ b/kadai3-1/akuchii/main.go @@ -9,7 +9,7 @@ import ( func main() { timeout := 5 - game := game.NewGame(os.Stdout, getWords(), timeout) + game := game.NewGame(os.Stdin, os.Stdout, getWords(), timeout) cnt := game.Start() fmt.Printf("\nfinish!\ncorrect count is %d\n", cnt) os.Exit(0) From 26d35640c69c188ed21867020180311f243006b0 Mon Sep 17 00:00:00 2001 From: hakuta Date: Mon, 26 Nov 2018 19:29:54 +0900 Subject: [PATCH 5/6] change from int to time.Duration --- kadai3-1/akuchii/game/game.go | 6 +++--- kadai3-1/akuchii/main.go | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/kadai3-1/akuchii/game/game.go b/kadai3-1/akuchii/game/game.go index 2e54ae9..590241d 100644 --- a/kadai3-1/akuchii/game/game.go +++ b/kadai3-1/akuchii/game/game.go @@ -15,7 +15,7 @@ type Game struct { writer io.Writer idx int words []string - timeout int + timeout time.Duration } func init() { @@ -23,14 +23,14 @@ func init() { } // NewGame generates new Game instance -func NewGame(r io.Reader, w io.Writer, words []string, timeout int) *Game { +func NewGame(r io.Reader, w io.Writer, words []string, timeout time.Duration) *Game { return &Game{reader: r, writer: w, words: words, timeout: timeout} } // Start starts typing game func (g Game) Start() int { ctx := context.Background() - ctx, cancel := context.WithTimeout(ctx, time.Duration(g.timeout)*time.Second) + ctx, cancel := context.WithTimeout(ctx, g.timeout) defer cancel() ch := input(g.reader) diff --git a/kadai3-1/akuchii/main.go b/kadai3-1/akuchii/main.go index 0cdc4d5..f57ca0a 100644 --- a/kadai3-1/akuchii/main.go +++ b/kadai3-1/akuchii/main.go @@ -3,12 +3,13 @@ package main import ( "fmt" "os" + "time" "github.com/gopherdojo/dojo4/kadai3-1/akuchii/game" ) func main() { - timeout := 5 + timeout := 5 * time.Second game := game.NewGame(os.Stdin, os.Stdout, getWords(), timeout) cnt := game.Start() fmt.Printf("\nfinish!\ncorrect count is %d\n", cnt) From c33add5f6d3f34f38f8061e4ae2cdba779191d71 Mon Sep 17 00:00:00 2001 From: hakuta Date: Tue, 27 Nov 2018 21:24:01 +0900 Subject: [PATCH 6/6] use constant and check error --- kadai3-2/akuchii/downloader/downloader.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kadai3-2/akuchii/downloader/downloader.go b/kadai3-2/akuchii/downloader/downloader.go index 4927bd6..cc7969f 100644 --- a/kadai3-2/akuchii/downloader/downloader.go +++ b/kadai3-2/akuchii/downloader/downloader.go @@ -92,7 +92,10 @@ func (d Downloader) MergeFiles() error { } func (d Downloader) request(r Range) error { - req, _ := http.NewRequest("GET", d.url, nil) + req, err := http.NewRequest(http.MethodGet, d.url, nil) + if err != nil { + return err + } req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", r.start, r.end)) client := new(http.Client) resp, err := client.Do(req)