20200320のGoに関する記事は4件です。

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.Mutexmap[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/",
        },
    },
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を編集して解決します。2

go.mod
module 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.1

3. QlovaSeedライブラリを入れる

下記コマンドをターミナルに入力し、ライブラリを取得します。

go get github.com/qlova/seed 

4. アプリ本体のコードを書く

最後にコードを書いていきます。

まず、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.go
package 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 を打つと、ウインドウが立ち上がります!

image.png

最後に

作る時はGithub上のexampleが結構参考になると思います。3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「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がベータ版だった時からあり、最初はストレージでした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golangで「アルゴリズム図鑑」で紹介されているソートを実装する

アルゴリズムの勉強とGoの勉強を兼ねて、アルゴリズム図鑑 絵で見てわかる26のアルゴリズム で紹介されているソートを実装してみました。

各ソートの仕組みは、検索するとわかりやすい解説が出てくるかと思います。

初めてGoを触ったので、書き方がGoっぽくない可能性大です。
自分向けの備忘用途がメインですが、参考程度に使ってもらえれば嬉しいです。

バルブソート

サンプルコード
main.go
package 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.go
package 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.go
package 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.go
package 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.go
package 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.go
package 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]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む