Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kadai3 by chikuwa111 #47

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions kadai3/chikuwa111/README.md
Original file line number Diff line number Diff line change
@@ -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がそもそも小さいため、分割ダウンロードによる効率よりも分割したファイルをそれぞれ開く非効率の方が影響として勝ってしまったからだと推測しました。
41 changes: 41 additions & 0 deletions kadai3/chikuwa111/paralleldownload/main.go
Original file line number Diff line number Diff line change
@@ -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(&parallelCount, "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)
}
126 changes: 126 additions & 0 deletions kadai3/chikuwa111/paralleldownload/pdownload/pdownload.go
Original file line number Diff line number Diff line change
@@ -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
}
83 changes: 83 additions & 0 deletions kadai3/chikuwa111/wordtype/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
23 changes: 23 additions & 0 deletions kadai3/chikuwa111/wordtype/wordlist/wordlist.go
Original file line number Diff line number Diff line change
@@ -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{}{},
}