diff --git a/kadai3/chikuwa111/README.md b/kadai3/chikuwa111/README.md new file mode 100644 index 0000000..4bdd31a --- /dev/null +++ b/kadai3/chikuwa111/README.md @@ -0,0 +1,38 @@ +# 課題3 + +## 課題3-1 + +タイピングゲームを作ろう。 + +* 標準出力に英単語を出す(出すものは自由) +* 標準入力から1行受け取る +* 制限時間内に何問解けたか表示する + +wordtypeです。 + +main.goにまとめて書いてしまいました。 +出題する英単語だけ切り分けています。ランダムに出題できるようにmapを使いました。 +また、制限時間をオプションで変更できるようにしました。 + +## 課題3-2 + +分割ダウンローダーを実装しよう。 + +* Rangeアクセスを用いる +* いくつかのゴルーチンでダウンロードしてマージする +* エラー処理を工夫する + * golang.org/x/sync/errgourpパッケージなどを使ってみる +* キャンセルが発生した場合の実装を行う + +paralleldownloadです。 + +オプションで並列数を指定できるようにしました。 +時間が取れずキャンセルが発生した場合の実装はできませんでした。 +また、テストもかけていません。動作確認はしました。 +jquery.min.jsのダウンロードを並列数を変えて試してみたところ、 +- p=2: 約1.8秒 +- p=4: 約2秒 +- p=6: 約2.5秒 + +のようになって期待通りとはなりませんでした。 +これは、jquery.min.jsがそもそも小さいため、分割ダウンロードによる効率よりも分割したファイルをそれぞれ開く非効率の方が影響として勝ってしまったからだと推測しました。 diff --git a/kadai3/chikuwa111/paralleldownload/main.go b/kadai3/chikuwa111/paralleldownload/main.go new file mode 100644 index 0000000..6897ad1 --- /dev/null +++ b/kadai3/chikuwa111/paralleldownload/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "flag" + "fmt" + "os" + "runtime" + + "paralleldownload/pdownload" +) + +var parallelCount int + +func usage() { + fmt.Fprintf(os.Stderr, "usage: %v [option] [url]\n", os.Args[0]) + fmt.Fprintln(os.Stderr, "option:") + flag.PrintDefaults() +} + +func init() { + flag.IntVar(¶llelCount, "p", 2, "The number of parallel download") + flag.Usage = usage + flag.Parse() +} + +func main() { + args := flag.Args() + if len(args) != 1 { + flag.Usage() + os.Exit(2) + } + + runtime.GOMAXPROCS(parallelCount) + url := args[0] + err := pdownload.Run(url, parallelCount) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/kadai3/chikuwa111/paralleldownload/pdownload/pdownload.go b/kadai3/chikuwa111/paralleldownload/pdownload/pdownload.go new file mode 100644 index 0000000..2dd877b --- /dev/null +++ b/kadai3/chikuwa111/paralleldownload/pdownload/pdownload.go @@ -0,0 +1,126 @@ +package pdownload + +import ( + "errors" + "fmt" + "io" + "net/http" + "os" + "strings" + + "golang.org/x/sync/errgroup" +) + +func checkAcceptRanges(res *http.Response) bool { + acceptRanges := res.Header.Get("Accept-Ranges") + return acceptRanges == "bytes" +} + +func generateFileName(url string) string { + slice := strings.Split(url, "/") + return slice[len(slice)-1] +} + +func generateRange(contentLength int64, parallelCount int) []string { + ranges := []string{} + onepart := contentLength / int64(parallelCount) + var borderByte int64 // = 0 + for i := 0; i < parallelCount-1; i++ { + ranges = append(ranges, fmt.Sprintf("bytes=%v-%v", borderByte, borderByte+onepart)) + borderByte += onepart + 1 + } + ranges = append(ranges, fmt.Sprintf("bytes=%v-%v", borderByte, contentLength-1)) + return ranges +} + +func download(url, tmpFileName, rangeStr string) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + req.Header.Set("Range", rangeStr) + + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + file, err := os.Create(tmpFileName) + if err != nil { + return err + } + defer file.Close() + + io.Copy(file, res.Body) + + return nil +} + +func parallelDownload(url, fileName string, ranges []string) error { + var errgrp errgroup.Group + for i, rangeStr := range ranges { + tmpFileName := fmt.Sprintf("%v.%v", fileName, i) + errgrp.Go(func() error { + return download(url, tmpFileName, rangeStr) + }) + } + err := errgrp.Wait() + return err +} + +func bundleOneFile(fileName string, parallelCount int) error { + file, err := os.Create(fileName) + if err != nil { + return err + } + defer file.Close() + + for i := 0; i < parallelCount; i++ { + tmpFileName := fmt.Sprintf("%v.%v", fileName, i) + tmpFile, err := os.Open(tmpFileName) + if err != nil { + return err + } + + io.Copy(file, tmpFile) + + tmpFile.Close() + + err = os.Remove(tmpFileName) + if err != nil { + return err + } + } + return nil +} + +// Run executes parallel download. +func Run(url string, parallelCount int) error { + res, err := http.Head(url) + if err != nil { + return err + } + + if !checkAcceptRanges(res) { + return errors.New("Cannot download parallel") + } + + contentLength := res.ContentLength + if contentLength <= 0 { + return errors.New("Invalid Content-Length: " + string(contentLength)) + } + + fileName := generateFileName(url) + ranges := generateRange(contentLength, parallelCount) + + if err := parallelDownload(url, fileName, ranges); err != nil { + return err + } + + if err := bundleOneFile(fileName, parallelCount); err != nil { + return err + } + + return nil +} diff --git a/kadai3/chikuwa111/wordtype/main.go b/kadai3/chikuwa111/wordtype/main.go new file mode 100644 index 0000000..161600c --- /dev/null +++ b/kadai3/chikuwa111/wordtype/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "bufio" + "context" + "flag" + "fmt" + "os" + "time" + + "wordtype/wordlist" +) + +var ( + statusCode = 0 + quizCount = 0 + correctCount = 0 + timeLimit int +) + +func handleError(err error) { + fmt.Fprintln(os.Stderr, err) + statusCode = 1 +} + +func setQuiz(word string) { + quizCount++ + fmt.Println(" " + word) + fmt.Print("> ") +} + +func checkAnswer(input, answer string) { + if input == answer { + correctCount++ + } +} + +func startGame() { + bc := context.Background() + t := time.Duration(timeLimit) * time.Second + ctx, cancel := context.WithTimeout(bc, t) + defer cancel() + + scanner := bufio.NewScanner(os.Stdin) + ch := make(chan bool) + for { + for word := range wordlist.Words { + setQuiz(word) + go func() { + ch <- scanner.Scan() + }() + select { + case ok := <-ch: + if ok { + checkAnswer(scanner.Text(), word) + } else { + if err := scanner.Err(); err != nil { + handleError(err) + } + return + } + case <-ctx.Done(): + return + } + } + } +} + +func printResult() { + fmt.Println("") + fmt.Printf("(正答数/問題数)は、(%v/%v)でした", correctCount, quizCount) +} + +func init() { + flag.IntVar(&timeLimit, "t", 10, "Time limit (sec)") + flag.Parse() +} + +func main() { + startGame() + printResult() + os.Exit(statusCode) +} diff --git a/kadai3/chikuwa111/wordtype/wordlist/wordlist.go b/kadai3/chikuwa111/wordtype/wordlist/wordlist.go new file mode 100644 index 0000000..5747ad8 --- /dev/null +++ b/kadai3/chikuwa111/wordtype/wordlist/wordlist.go @@ -0,0 +1,23 @@ +package wordlist + +// Words provides the map of English words. +// This is map because a word want to be selected randomly when use `range Words` +var Words = map[string]struct{}{ + "apple": struct{}{}, + "banana": struct{}{}, + "grape": struct{}{}, + "orange": struct{}{}, + "strawberry": struct{}{}, + "cherry": struct{}{}, + "watermelon": struct{}{}, + "pear": struct{}{}, + "pineapple": struct{}{}, + "peach": struct{}{}, + "pumpkin": struct{}{}, + "cabbage": struct{}{}, + "cucumber": struct{}{}, + "potato": struct{}{}, + "onion": struct{}{}, + "carrot": struct{}{}, + "tomato": struct{}{}, +}