- 投稿日:2020-03-20T22:34:40+09:00
A Tour of Go の Exercise: Web Crawler を書いてみた
A Tour of Go
面白法人カヤックでGo書いてるギリ新卒のkoyoです!
次の新卒が入ってくる時期になってきましたね。新卒でGo書く人はA Tour of Go
やる時期なのかなーと思います。
Goを覚えるにはとてもいい題材なのですが、Exerciseが難しい笑(なので新卒の皆さんは全部解けなくてもいいと思います。私も去年は解けなかった)
特に最後のExercise: Web Crawler
が難しいですよね。1年仕事で書いてると、理解も深まるもので、苦戦しましたが良さそうなコードを書くことができたので、記事を書いてみました!https://go-tour-jp.appspot.com/concurrency/10
コード
package main import ( "fmt" "sync" ) type Fetcher interface { Fetch(url string) (body string, urls []string, err error) } type Crawler struct { cache *sync.Map } func NewCrawler() *Crawler { return &Crawler{ cache: &sync.Map{}, } } func (c *Crawler) Crawl(url string, depth int, fetcher Fetcher) { wg := &sync.WaitGroup{} c.crawl(url, depth, wg) wg.Wait() return } func (c *Crawler) crawl(url string, depth int, wg *sync.WaitGroup) { if depth <= 0 { return } if _, ok := c.cache.Load(url); ok { return } c.cache.Store(url, struct{}{}) body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) wg.Add(len(urls)) for _, u := range urls { go func(u string) { c.crawl(u, depth-1, wg) wg.Done() }(u) } } func main() { NewCrawler().Crawl("https://golang.org/", 4, fetcher) } type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f fakeFetcher) Fetch(url string) (string, []string, error) { if res, ok := f[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } var fetcher = fakeFetcher{ "https://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "https://golang.org/pkg/", "https://golang.org/cmd/", }, }, "https://golang.org/pkg/": &fakeResult{ "Packages", []string{ "https://golang.org/", "https://golang.org/cmd/", "https://golang.org/pkg/fmt/", "https://golang.org/pkg/os/", }, }, "https://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, "https://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, }実行結果
found: https://golang.org/ "The Go Programming Language" not found: https://golang.org/cmd/ found: https://golang.org/pkg/ "Packages" found: https://golang.org/pkg/os/ "Package os" found: https://golang.org/pkg/fmt/ "Package fmt" Program exited.簡単な解説
sync.WaitGroup
を使ってCrawler
を定義する方法でやってみました。
channelを使うやり方や、クロージャで済ませる方法もあるかなと思いますね。ただクロージャだと可読性と拡張性が低くなるので、struct定義しちゃう方がいいかなーと思う派ですね。実装のポイントは
sync.Map{}
を使うことです。sync.Mutex
とmap[string]bool
を組み合わせるやり方もあると思うんですが、これを使うと一つだけで済むのでラクですね。
簡単なキャッシュを実装したい場合に使えるのでオススメです!厳密に考えるとするとエラー返しているので
sync.WaitGroup
ではなくerrgroup.Group
使う方がいいかもですが、実装例の一つとして参考になればと思います。おまけ: クロージャで解いてみた
package main import ( "fmt" "sync" ) type Fetcher interface { Fetch(url string) (body string, urls []string, err error) } func Crawl(url string, depth int, fetcher Fetcher) { var ( cache = &sync.Map{} wg = &sync.WaitGroup{} crawl func(url string, depth int) ) crawl = func(url string, depth int) { if depth <= 0 { return } if _, ok := cache.Load(url); ok { return } cache.Store(url, struct{}{}) body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) wg.Add(len(urls)) for _, u := range urls { go func(u string) { crawl(u, depth-1) wg.Done() }(u) } } crawl(url, depth) wg.Wait() return } func main() { Crawl("https://golang.org/", 4, fetcher) } type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f fakeFetcher) Fetch(url string) (string, []string, error) { if res, ok := f[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } var fetcher = fakeFetcher{ "https://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "https://golang.org/pkg/", "https://golang.org/cmd/", }, }, "https://golang.org/pkg/": &fakeResult{ "Packages", []string{ "https://golang.org/", "https://golang.org/cmd/", "https://golang.org/pkg/fmt/", "https://golang.org/pkg/os/", }, }, "https://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, "https://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "https://golang.org/", "https://golang.org/pkg/", }, }, }
- 投稿日:2020-03-20T19:58:00+09:00
Goでデスクトップアプリを作る (QlovaSeed HelloWorld編)
概要
QlovaSeedというクロスプラットフォームフレームワークを使ってデスクトップアプリを作ってみます。
環境
OS: Windows [Version 10.0.18362.720]
Go: go1.13.8 windows/amd64
準備
1. プロジェクト作成
まず、プロジェクトを以下の手順で作成します。1
mkdir hello-world go mod init hello-world
2. go.modを編集
QlovaSeedをそのまま
go get
するとblackfriday/v2
が取得できないので、以下のようにgo.mod
を編集して解決します。2go.modmodule hello-world go 1.13 require ( // … //追加!! gopkg.in/russross/blackfriday.v2 v2.0.1 ) //追加!! replace gopkg.in/russross/blackfriday.v2 => github.com/russross/blackfriday/v2 v2.0.13. QlovaSeedライブラリを入れる
下記コマンドをターミナルに入力し、ライブラリを取得します。
go get github.com/qlova/seed4. アプリ本体のコードを書く
最後にコードを書いていきます。
まず、
seed.NewApp(string)
でアプリを作成します。次に、スペースを配置するために
expander.AddTo(App)
を配置します。これは、他にものが存在しない限り広がります。これを複数組み合わせることによって、テキストなどを任意の場所に配置できます。以下の例ではウインドウの縦における 3/1, 3/2 のところにテキストを配置しています。スペースを配置したら
text.AddTo(App, string)
でtextを配置します。この定義したtextに対して何も装飾などを行わない場合はこのまま記述するだけですが、そうでない場合はText := Text.AddTo(App, string)
と結果を変数に代入することであとから装飾をすることができます。下記の例ではText.SetColor(seed.RGB(116, 196, 133))
とすることで色を付けています。最後に、
App.Launch()
とすることでアプリを立ち上げます。main.gopackage main import ( "github.com/qlova/seed" "github.com/qlova/seeds/text" "github.com/qlova/seeds/expander" ) func main() { //アプリを作成 var App = seed.NewApp("Hello World") expander.AddTo(App) //スペース text.AddTo(App, "Hello World!") //テキストを配置 expander.AddTo(App) Text := text.AddTo(App, "Hello QlovaSeed!") //テキストを配置後Textに代入 Text.SetColor(seed.RGB(116,196,133)) //Textに色を設定 expander.AddTo(App) //アプリを起動 App.Launch() }5. ビルドして、HelloWorld!
go run main.go
を打つと、ウインドウが立ち上がります!最後に
作る時はGithub上のexampleが結構参考になると思います。3
- 投稿日:2020-03-20T15:09:35+09:00
「Azure SDK for Go」とは別の独自サービスパッケージ
「Azure SDK for Go のドキュメント」サイトの「Azure SDK for Go のインストール」ページを読み進みています。
「Azure SDK for Go を入手する」セクションにて、いくつかのAzure サービス用のパッケージは Azure/azure-sdk-for-go とは別に用意されているとの記述があり、それらは主にストレージ サービスとメッセージング サービスのパッケージです。
ストレージ サービス
File Storage(パッケージ:Azure/azure-storage-file-go)
- Azure上に構築されたSMB プロトコルのファイル共有システム。
- 汎用 OS(Linux, macOS, Windows、など)でのファイル共有で使用。
Blob Storage(パッケージ:Azure/azure-storage-blob-go)
- ファイルをBLOBで扱うストレージ。
- 大量データの蓄積や操作(アーカイブ、ランダム アクセス、データ解析、など)
Queue storage(パッケージ:Azure/azure-storage-queue-go)
- RESTでのテキストメッセージをキューイングする。
- Service Busと比べてキューイング(格納)に重きが置かれている。
Table storage(パッケージ:なし)
- 分散Key-Valueストア。
メッセージング サービス
Service Bus(パッケージ:Azure/azure-service-bus-go)
- TCP プロトコルのメッセージをキューイングしたりPub/Subで処理する。
- Queue Storageと比べてメッセージングに重きが置かれている。
Event Hubs(パッケージ:Azure/azure-event-hubs-go)
- HTTPS/AMQP/Kafka プロトコルの大量のイベントを受け取って、Kafka プロトコルに対応したサービスに振り分ける。
- ビッグ データ向け。
Event Grid(パッケージ:なし)
- 対応したAzure サービスからのイベントを受け取って、対応したAzure サービスに振り分ける。
サービスのパッケージが分家する動きは、Azure SDK for Goがベータ版だった時からあり、最初はストレージでした。
- 投稿日:2020-03-20T01:02:04+09:00
Golangで「アルゴリズム図鑑」で紹介されているソートを実装する
アルゴリズムの勉強とGoの勉強を兼ねて、アルゴリズム図鑑 絵で見てわかる26のアルゴリズム で紹介されているソートを実装してみました。
各ソートの仕組みは、検索するとわかりやすい解説が出てくるかと思います。
初めてGoを触ったので、書き方がGoっぽくない可能性大です。
自分向けの備忘用途がメインですが、参考程度に使ってもらえれば嬉しいです。バルブソート
サンプルコード
main.gopackage main import ( "fmt" ) func sort(nums []int) []int { for i := 0; i < len(nums); i++ { isSwapped := false for j, value := range nums[:len(nums)-i-1] { if value > nums[j+1] { nums[j] = nums[j+1] nums[j+1] = value isSwapped = true } fmt.Println(nums) } if !isSwapped { break } fmt.Println("-----") } return nums } func main() { nums := []int{5, 9, 3, 1, 2, 8, 4, 7, 6} fmt.Println(nums) fmt.Println("-----BEGIN-----") result := sort(nums) fmt.Println("----- END -----") fmt.Println(result) }
出力結果
[5 9 3 1 2 8 4 7 6] -----BEGIN----- [5 9 3 1 2 8 4 7 6] [5 3 9 1 2 8 4 7 6] [5 3 1 9 2 8 4 7 6] [5 3 1 2 9 8 4 7 6] [5 3 1 2 8 9 4 7 6] [5 3 1 2 8 4 9 7 6] [5 3 1 2 8 4 7 9 6] [5 3 1 2 8 4 7 6 9] ----- [3 5 1 2 8 4 7 6 9] [3 1 5 2 8 4 7 6 9] [3 1 2 5 8 4 7 6 9] [3 1 2 5 8 4 7 6 9] [3 1 2 5 4 8 7 6 9] [3 1 2 5 4 7 8 6 9] [3 1 2 5 4 7 6 8 9] ----- [1 3 2 5 4 7 6 8 9] [1 2 3 5 4 7 6 8 9] [1 2 3 5 4 7 6 8 9] [1 2 3 4 5 7 6 8 9] [1 2 3 4 5 7 6 8 9] [1 2 3 4 5 6 7 8 9] ----- [1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9] ----- END ----- [1 2 3 4 5 6 7 8 9]選択ソート
サンプルコード
main.gopackage main import ( "fmt" ) func sort(nums []int) []int { for i, v1 := range nums { minValueIndex := i minValue := v1 for j, v2 := range nums[i:] { if minValue > v2 { minValueIndex = i + j minValue = v2 } } nums[i] = minValue nums[minValueIndex] = v1 fmt.Println(nums) } return nums } func main() { nums := []int{5, 9, 3, 1, 2, 8, 4, 7, 6} fmt.Println(nums) fmt.Println("-----BEGIN-----") result := sort(nums) fmt.Println("----- END -----") fmt.Println(result) }
出力結果
[5 9 3 1 2 8 4 7 6] -----BEGIN----- [1 9 3 5 2 8 4 7 6] [1 2 3 5 9 8 4 7 6] [1 2 3 5 9 8 4 7 6] [1 2 3 4 9 8 5 7 6] [1 2 3 4 5 8 9 7 6] [1 2 3 4 5 6 9 7 8] [1 2 3 4 5 6 7 9 8] [1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9] ----- END ----- [1 2 3 4 5 6 7 8 9]挿入ソート
サンプルコード
main.gopackage main import ( "fmt" ) func sort(nums []int) []int { result := nums[0:1] fmt.Println(result, nums[1:]) for i, value := range nums[1:] { for j, sortedValue := range result { if value < sortedValue { result = append(result[:j], append([]int{value}, result[j:]...)...) break } if j == len(result)-1 { result = append(result, value) break } } fmt.Println(result, nums[i+2:]) } return result } func main() { nums := []int{5, 9, 3, 1, 2, 8, 4, 7, 6} fmt.Println(nums) fmt.Println("-----BEGIN-----") result := sort(nums) fmt.Println("----- END -----") fmt.Println(result) }
出力結果
[5 9 3 1 2 8 4 7 6] -----BEGIN----- [5] [9 3 1 2 8 4 7 6] [5 9] [3 1 2 8 4 7 6] [3 5 9] [1 2 8 4 7 6] [1 3 5 9] [2 8 4 7 6] [1 2 3 5 9] [8 4 7 6] [1 2 3 5 8 9] [4 7 6] [1 2 3 4 5 8 9] [7 6] [1 2 3 4 5 7 8 9] [6] [1 2 3 4 5 6 7 8 9] [] ----- END ----- [1 2 3 4 5 6 7 8 9]ヒープソート
ヒープを実装する元気はなかったので、既存のパッケージを使いました。
heap - The Go Programming Language
サンプルコード
main.gopackage main import ( "container/heap" "fmt" ) // An IntHeap is a min-heap of ints. type IntHeap []int func (h IntHeap) Len() int { return len(h) } func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *IntHeap) Push(x interface{}) { // Push and Pop use pointer receivers because they modify the slice's length, // not just its contents. *h = append(*h, x.(int)) } func (h *IntHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } func sort(nums []int) []int { h := &IntHeap{} heap.Init(h) for i:=0; i <len(nums); i++ { heap.Push(h, nums[i]) } var result []int for h.Len() > 0 { value := heap.Pop(h) result = append(result, value.(int)) fmt.Println(result, *h) } return result } func main() { nums := []int{5, 9, 3, 1, 2, 8, 4, 7, 6} fmt.Println(nums) fmt.Println("-----BEGIN-----") result := sort(nums) fmt.Println("----- END -----") fmt.Println(result) }
出力結果
[5 9 3 1 2 8 4 7 6] -----BEGIN----- [1] [2 3 4 6 7 8 5 9] [1 2] [3 6 4 9 7 8 5] [1 2 3] [4 6 5 9 7 8] [1 2 3 4] [5 6 8 9 7] [1 2 3 4 5] [6 7 8 9] [1 2 3 4 5 6] [7 9 8] [1 2 3 4 5 6 7] [8 9] [1 2 3 4 5 6 7 8] [9] [1 2 3 4 5 6 7 8 9] [] ----- END ----- [1 2 3 4 5 6 7 8 9]マージソート
サンプルコード
main.gopackage main import ( "fmt" ) func sort(nums []int) []int { if len(nums) == 1 { return nums } halfIndex := len(nums) / 2 leftNums := sortMerge(nums[:halfIndex]) rightNums := sortMerge(nums[halfIndex:]) leftIndex := 0 rightIndex := 0 var result []int for i := 0; i < len(leftNums)+len(rightNums); i++ { if len(rightNums) <= rightIndex || (len(leftNums) > leftIndex && leftNums[leftIndex] <= rightNums[rightIndex]) { result = append(result, leftNums[leftIndex]) leftIndex++ } else { result = append(result, rightNums[rightIndex]) rightIndex++ } } fmt.Println(leftNums, rightNums) fmt.Println(result) fmt.Println("-----") return result } func main() { nums := []int{5, 9, 3, 1, 2, 8, 4, 7, 6} fmt.Println(nums) fmt.Println("-----BEGIN-----") result := sort(nums) fmt.Println("----- END -----") fmt.Println(result) }
出力結果
[5 9 3 1 2 8 4 7 6] -----BEGIN----- [5] [9] [5 9] ----- [3] [1] [1 3] ----- [5 9] [1 3] [1 3 5 9] ----- [2] [8] [2 8] ----- [7] [6] [6 7] ----- [4] [6 7] [4 6 7] ----- [2 8] [4 6 7]クイックソート
サンプルコード
main.gopackage main import ( "fmt" "math/rand" "time" ) func sort(nums []int) []int { if len(nums) == 1 { return nums } rand.Seed(time.Now().Unix()) index := rand.Intn(len(nums)) var smallerNums []int var largerNums []int baseValue := nums[index] for i := 0; i < len(nums); i++ { if i == index { continue } value := nums[i] if baseValue <= value { largerNums = append(largerNums, value) } else { smallerNums = append(smallerNums, value) } } var result []int if len(smallerNums) > 0 { result = sortQuick(smallerNums) } result = append(result, baseValue) if len(largerNums) > 0 { result = append(result, sortQuick(largerNums)...) } fmt.Println(smallerNums, baseValue, largerNums) return result } func main() { nums := []int{5, 9, 3, 1, 2, 8, 4, 7, 6} fmt.Println(nums) fmt.Println("-----BEGIN-----") result := sort(nums) fmt.Println("----- END -----") fmt.Println(result) }
出力結果
[5 9 3 1 2 8 4 7 6] -----BEGIN----- [1] 2 [3] [3 1 2] 4 [5] [] 8 [9] [] 7 [9 8] [5 3 1 2 4] 6 [9 8 7] ----- END ----- [1 2 3 4 5 6 7 8 9]