20200211のGoに関する記事は11件です。

Go mapのkeyにインターフェースを使う場合に気をつけるべきこと

小ネタです。

GoのMapのkey

goはmapの初期化時以下のように「keyの型」と「valueの型」を両方宣言しますが、

m := make(map[string]string)

この「keyの型」には当然interface(空interface含む)を指定することもできる。

m := make(map[io.Reader]string)   // これとか
m := make(map[interface{}]string) // これ

このような場合、実際に値を入れるときに型が違えば当然keyも違う。

要するにこういうことです

type dog int
type cat int

m := make(map[interface{}]string)
m[dog(1)] = "dog dayo"
m[cat(1)] = "cat dayo"

fmt.Printf("%v", m) // map[1:dog dayo 1:cat dayo] 
// 拡張int同士、同じ数字だがkeyは違う

同じintを拡張したdog,cat型ですが、keyの見た目は数字の1で重複しているのに
型が違うので上書きではなく新規登録になっていますね。

structで全く新しい型を作った場合は「まぁそうだろうな」と直感でわかりますが、
同じintを拡張した型同士でもしっかりと別型として扱われます。

さすがにinterface{}ほどゆるいkeyを使うことってないかもしれないですが、
(というか殆どの場合keyはintやstringといったプリミティブ型だと思いますが)
一応このような仕様になってますよということで。

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

バッファなしチャンネル(unbeffered channel)のブロッキング(blocking)について

バッファなしは同期通信する

バッファなし

ch1 := make(chan int)
ch2 := make(chan int, 0)

バッファあり

ch := make(chan int, 1)

バッファなしの場合は同期通信となるのでチャンネルに送信すると同時に受信しなければずっとそこでブロックしてしまう。そのため下記のようなコードは実行前にfatal errorとなってしまう。

func main() {
    ch := make(chan int, 0)
    ch <- 1 // ここでブロックし続けてしまう
    fmt.Println("finish")
}

解決するにはどこかで受信しておかなければならない

func main() {
    ch := make(chan int, 0)
    go func() {
        <-ch
    }()
    ch <- 1
    fmt.Println("finish")
}

バッファありはcapacityまではブロックしない

下記は正常に動作しfinishが出力される

func main() {
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    fmt.Println("finish")
}

capacityを超えるとブロックするため下記はエラーとなる

func main() {
    ch := make(chan int, 2)
    ch <- 1
    ch <- 2
    ch <- 3
    fmt.Println("finish")
}

selectの動作(バッファなしのチャネルに対して)

selectはチャンネル通信が起きた時の条件を複数登録してそれぞれの動作を記述することができるが、チャンネルのブロックには気をつけておく必要がある。

func main() {
    ch := make(chan int, 0)
    heartBeat := make(chan struct{}, 0)
    i := 0
    go func() {
        pulse := time.Tick(1000 * time.Millisecond)
        for {
            fmt.Println("==select==")
            select {
            case ch <- i:
                fmt.Println("send ch finish")
                i++
            case <-pulse:
                fmt.Println("send heartBeat start")
                heartBeat <- struct{}{}
                fmt.Println("send heartBeat finish")
            }
        }
    }()

    // mainのgoroutineでは何もしない
    for {
        select {
        }
    }
}

上のプログラムは下記の出力の後、動作しなくなる。

$ go run sample.go
==select==
send heartBeat start

heartBeatチャンネルはバッファなしのため、送信と受信が同時に起こらなければその場でブロックし続けるからだ。goroutineでのselectのloopは先に進むことなくheartBeatが受信されるまでブロックし続ける。

chへの送信は受信のロジックが書かれていないのでcaseでマッチしない。

selectの動作(バッファあり)

今度はheartBeatのチャンネルのcapacityを2にする

func main() {
    ch := make(chan int, 0)
    heartBeat := make(chan struct{}, 2)
    i := 0
    go func() {
        pulse := time.Tick(1000 * time.Millisecond)
        for {
            fmt.Println("==select==")
            select {
            case ch <- i:
                fmt.Println("send ch finish")
                i++
            case <-pulse:
                fmt.Println("send heartBeat start")
                heartBeat <- struct{}{}
                fmt.Println("send heartBeat finish")
            }
        }
    }()

    // mainのgoroutineでは何もしない
    for {
        select {
        }
    }
}

上のプログラムは次の出力の後、動作しなくなる。

$ go run sample.go
==select==
send heartBeat start
send heartBeat finish
==select==
send heartBeat start
send heartBeat finish
==select==
send heartBeat start

想定通りcapacityの数だけブロックしない。

selectループ同士のdeadlockについて

バッファなしチャンネルを使ってメインルーチンとワーカールーチンでやりとりすると、簡単にデッドロックしてしまうモノが書けてしまった。。

func SuccWorker(ctx context.Context, srcCh chan int) (chan struct{}, chan int) {
    heartBeatCh := make(chan struct{})
    succResultCh := make(chan int)
    pulse := time.NewTicker(1000 * time.Millisecond)
    go func() {
        defer close(heartBeatCh)
        defer close(succResultCh)
        defer pulse.Stop() // not close channel
        for {
            select {
            case <-ctx.Done():
                return
            case i, ok := <-srcCh:
                if !ok {
                    return
                }
                i++
                succResultCh <- i
            case <-pulse.C:
                fmt.Println("\tsend heartbeat start")
                heartBeatCh <- struct{}{}
                fmt.Println("\tsend heartbeat end")
            }
        }
    }()
    return heartBeatCh, succResultCh
}

func main() {
    fmt.Println("Start")
    ctx := context.Background()
    srcCh := make(chan int)
    heartBeatCh, succResultCh := SuccWorker(ctx, srcCh)

    // kick
    go func() {
        <-time.After(3 * time.Second)
        srcCh <- 0
    }()

mainloop:
    for {
        select {
        case <-heartBeatCh:
            fmt.Println("HeartBeat: ", time.Now().Format("15:04:05"))
        case i, ok := <-succResultCh:
            if !ok {
                break mainloop
            }
            <-time.After(1000 * time.Millisecond)
            fmt.Println("succResult: ", i)
            fmt.Println("send srcCh start")
            srcCh <- i
            fmt.Println("send srcCh end")
        }
    }
    fmt.Println("Finish")
}

上記を動かすと以下のような出力が出て以降は止まってしまう。

Start
    send heartbeat start
    send heartbeat end
HeartBeat:  17:39:16
    send heartbeat start
    send heartbeat end
HeartBeat:  17:39:17
    send heartbeat start
    send heartbeat end
HeartBeat:  17:39:18
    send heartbeat start
succResult:  1
send srcCh start

ワーカーからはheartbeatを送信しようとしたが、mainloopではワーカーにsrcChを介して送信しようとしており、両方とも相手の受信待ちでブロックが発生してしまう。

deadlockの解消

解消方法は自分が調べた範囲では下記2つが有効だった。

  • selectループをなるべく止めない
  • バッファありチャネルを使う

selectループを止めない方法

mainloopの中でsuccResultChから受信してからの処理をgoroutineで実行してすぐにselectループに戻るようにして解消した。ように見えた。

func SuccWorker(ctx context.Context, srcCh chan int) (chan struct{}, chan int) {
    heartBeatCh := make(chan struct{}, 0)
    succResultCh := make(chan int, 0)
    pulse := time.NewTicker(1000 * time.Millisecond)
    go func() {
        defer close(heartBeatCh)
        defer close(succResultCh)
        defer pulse.Stop()
        for {
            select {
            case <-ctx.Done():
                return
            case i, ok := <-srcCh:
                if !ok {
                    return
                }
                i++
                succResultCh <- i
            case <-pulse.C:
                fmt.Println("\tsend heartbeat start")
                heartBeatCh <- struct{}{}
                fmt.Println("\tsend heartbeat end")
            }
        }
    }()
    return heartBeatCh, succResultCh
}

func main() {
    fmt.Println("Start")
    ctx := context.Background()
    srcCh := make(chan int, 0)
    heartBeatCh, succResultCh := SuccWorker(ctx, srcCh)

    // kick
    go func() {
        <-time.After(3 * time.Second)
        srcCh <- 0
    }()

mainloop:
    for {
        select {
        case <-heartBeatCh:
            fmt.Println("HeartBeat: ", time.Now().Format("15:04:05"))
        case i, ok := <-succResultCh:
            if !ok {
                break mainloop
            }
            go func(){
                <-time.After(100 * time.Millisecond)
                fmt.Println("succResult: ", i)
                fmt.Println("send srcCh start")
                srcCh <- i
                fmt.Println("send srcCh end")
            }()
        }
    }
    fmt.Println("Finish")
}

バッファありチャネルを使う方法

使っているチャンネルを全てcapacity 1のバッファありに変更するとひとまずうまく動いているように見えた。こちらのmainLoopのselectループの中ではgoroutineでの処理はしていない。

func SuccWorker(ctx context.Context, srcCh chan int) (chan struct{}, chan int) {
    heartBeatCh := make(chan struct{}, 1)
    succResultCh := make(chan int, 1)
    pulse := time.NewTicker(1000 * time.Millisecond)
    go func() {
        defer close(heartBeatCh)
        defer close(succResultCh)
        defer pulse.Stop()
        for {
            select {
            case <-ctx.Done():
                return
            case i, ok := <-srcCh:
                if !ok {
                    return
                }
                i++
                succResultCh <- i
            case <-pulse.C:
                fmt.Println("\tsend heartbeat start")
                heartBeatCh <- struct{}{}
                fmt.Println("\tsend heartbeat end")
            }
        }
    }()
    return heartBeatCh, succResultCh
}

func main() {
    fmt.Println("Start")
    ctx := context.Background()
    srcCh := make(chan int, 1)
    heartBeatCh, succResultCh := SuccWorker(ctx, srcCh)

    // kick
    go func() {
        <-time.After(3 * time.Second)
        srcCh <- 0
    }()

mainloop:
    for {
        select {
        case <-heartBeatCh:
            fmt.Println("HeartBeat: ", time.Now().Format("15:04:05"))
        case i, ok := <-succResultCh:
            if !ok {
                break mainloop
            }
            <-time.After(100 * time.Millisecond)
            fmt.Println("succResult: ", i)
            fmt.Println("send srcCh start")
            srcCh <- i
            fmt.Println("send srcCh end")
        }
    }
    fmt.Println("Finish")
}

最後に

selectループ同士で通信する時はなるべくselectループを止めないのと、基本的にはバッファありチャンネルを利用する、ことで全てではないだろうがデッドロックの可能性を少なくすることができるように見えた。

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

ABC049C - 白昼夢を簡単に導く方法が知りたい(Golang)

はじめに

ABC049C - 白昼夢を検索文字列の特性上、文字列をリバースして導く方法で解きました。
解きかたはさておき、検索能力の低さゆえ、Goに以下標準関数が見つけられず手作りとなってしまったので、検索能力向上した際には改良したいと思いメモです。

  • 文字列のリバース
  • 開始〜終了桁数を指定した文字列の抜き出し(例:abcを1文字目から2文字目で抜き出して「b」を求める)

コード

package main

import (
  "fmt"
)

func main() {
  var s string
  annwer := "YES"
  divide := [4]string{"dream", "dreamer", "erase", "eraser"}
  fmt.Scanf("%s", &s)

  // リバース
  for i :=0; i < len(divide); i++ {
    divide[i] = getReverse(divide[i])
  }
  reverse := getReverse(s)

  // 後ろから一致するものを抜き出す
  j := 0 // 開始位置
  for j < len(reverse) {
    annwer = "NO"
    for i := 0; i < 4; i++ {

      // 比較する文字列を必要な範囲で抜き出す
      var str string
      for k := j; k < j + len(divide[i]); k++ {
        if k >= len(reverse) {
          continue
        }
        str += string(reverse[k])
      }
      // 比較
      if str == divide[i] {
        // 一致した分開始位置をずらす
        j += len(divide[i])
        annwer = "YES"
        break
      }
    }
    // ここにNOで来た時点でアウトなので終わり
    if annwer == "NO" {
      break
    }
  }
  fmt.Println(annwer)
}

func getReverse(s string) string {
  rs := []rune(s) // 今回マルチバイト文字ではないが想定してrune
  for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
    rs[i], rs[j] = rs[j], rs[i]
  }
  return string(rs)
}

おわりに

冗長すぎておかしくなりそうでした。
あまりニッチではないライブラリで実現可能でしたら教えていただけると嬉しいです。
追伸:別解としてReplaceで一致したら""と置換してしまい、最後0文字になったらYESという方法は認識しております。

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

GoのサンプルWebアプリをローカルで実行した時にはまったことのメモ

背景

やっていたこと

  • ホームディレクトリにgit cloneしてそのままgo buildをした
  • すると以下のエラーが
route_main.go:5:2: cannot find package "chitchat/data" in any of:
    /usr/local/Cellar/go/1.13.4/libexec/src/chitchat/data (from $GOROOT)
    /Users/XXXXXX/go/src/chitchat/data (from $GOPATH)
  • route_main.goを見てみると、importで自プロジェクトのパッケージを呼び出しており、そこが悪さをしていそう
import (
    "github.com/mushahiroyuki/gowebprog/ch02/chitchat/data"
)

解決策

  • 今回のように、単純に動かすだけならgo getコマンドでソースを取得すれば良い
$ go get github.com/mushahiroyuki/gowebprog/ch02/chitchat/data
  • go getコマンドによって、以下の処理が行われる

    • $GOPATH/binに実行ファイルが置かれる
    • $GOPATH/srcにパッケージのソースコードが置かれる
      • $GOPATH/src/github.com/mushahiroyuki/gowebprog/ch02/chitchatというパスに置かれる
  • 実行するだけなら、$GOPATH/bin配下に取得された実行ファイルを起動させればよい

    • つまり、自分でbuildする必要がなかった
  • ソースを変更しても、go buildで新しい実行ファイルが生成されるので問題なさそう

なぜエラーになっていたのか

  • 実行できるにしても最初に発生していたエラーは何なのか
  • それは、importに指定するgithub.com/mushahiroyuki/...というパスは、$GOPATH/srcからのパスに対応しているため
  • go getでソースを取得すると、$GOPATH/src配下に作成されるパスがimportに記載したパスと一致することになる
    • importへの記載は公式ドキュメントに書かれているように、同じプロジェクト内であってもgithub.com/...という絶対パスでの書き方が推奨されている
  • 何も考えずにgit cloneでローカルに落としてくると、importに記載したパスに合わないため、パッケージが見つからずエラーになる

Gitで管理したい場合はどうするのか

  • 個人的にローカルで実行させるだけなら上記の方法で問題無いが、チームで開発するなどの理由でGitで管理したい場合はどうするのか
  • 正解は無さそうだが、以下の2パターンがありそう
    • importのパスに合うようにgit cloneする
    • 大元のリポジトリをforkし、go getで取得したパスにgit remote addでforkしたリポジトリへの参照を追加
      • fork先へプッシュし、originへのプルリクエストを作成する

参考

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

【Go】VSCodeの自動フォーマットが効かないときにやったこと

はじめに

goenv経由でGo 1.11.13をインストールしたが
VSCodeの自動フォーマットが効かず
インデントも揃わない、パッケージも自動で入ってくれない。
この問題解決のためにやったことの備忘録。

The "gopls" command is not available.
Use "go get -v golang.org/x/tools/cmd/gopls" to install.

っていうポップアップが、VSCodeを開くたびに表示されて
「Install」 →→→「SUCCESS! You are ready to Go!」
実際入ってないんですけど。

gopls(発音はGo Please)というLanguage serverが入らないから
goimports(フォーマッタの正体)も動いてくれない(らしい)。

環境

MacBook Air
macOS Catalina ver.10.15.2
Visual Studio Code ver.1.42.0
Go 1.11.13 (これが問題の原因のひとつ)

解決策

1.goenvとGoをアンインストール。

// goenvのアンインストール
$ brew uninstall goenv

// Goをインストールすると自動で作成されるGoディレクトリ削除
$ rm -rvf /usr/local/go/

2.Goの環境構築

// goenvの再インストール
$ brew install goenv

// インストール可能なGoのバージョンを調べる
$ goenv install -l

// Go1.13.7をインストール
$ goenv install 1.13.7

ここでのGoのバージョンは1.12.x以上であればOK。
というか、1.12.x以上じゃないとこのあとインストールするgoplsが入ってくれない。

3.VSCodeに必要なモジュールをインストール

VSCodeを開いて、Cmd + Shift + P、「Go」とか打つと
Go Install/Update Tools と表示されるので、それを選択。
18個(2020年2月11日時点)のモジュールすべてを選択してインストール。

でも、goplsだけやっぱり入ってくれない。

gopls
↑↑この方法でインストール。
ここには『gopls入れるならGo1.12.x以上』って書いてある。

gopls is a go module project, so you need install Go 1.12 or above, to install the gopls, please run ...

$ git clone -b bingo https://github.com/saibing/tools.git
$ cd tools/gopls
$ go install

このあとVSCodeに戻って設定をsettings.jsonから変更。
このsettings.jsonは以下のパスからでも直接開くことができる。
~/Library/Application Support/Code/User/settings.json

settings.json

{
    "go.useLanguageServer": true,
    "go.alternateTools": {
        "go-langserver": "gopls"
    },
    "go.languageServerExperimentalFeatures": {
        "format": true,
        "autoComplete": true
    },
    "[go]": {
        "editor.snippetSuggestions": "none",
        "editor.formatOnSave": true,
        "editor.codeActionsOnSave": {
            "source.organizeImports": true
        },
    },
}

これで自動フォーマットやコード補完が動いた。
そして、もともと使っていたGo1.11.13をインストールしても
コード補完が効くことが確認できた。

さいごに

今回使用したBingo

Bingo is a Go language server that speaks Language Server Protocol.

これもgoplsと同様のLanguage Server Protocolだけど、
今後のサポートやメンテを行わないようです。

Note
I am very sorry that I have planned not continue to maintain this project.

今回紹介した方法もいつかはできなくなるかも。

参考サイト

Go公式
gopls

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

Scannerを使って、任意の文字列で区切りながら読み込む方法(golang初心者の備忘録)

Scannerを使って、任意の文字列で区切りながら読み込む方法

動機

Goならわかるシステムプログラミングの3章を読んでいて疑問に思ったところ。

Scannerを使って読み込むとき

  • 改行を区切り文字とするときは何もしなくてよい
  • 単語単位(空白区切り)はbufio.ScanWordsをSplitに渡せばよい

が、任意の文字列の場合はどうすればよいかわからないため、調査した。
また、

  • 区切り文字列を含む読み込み
  • 区切り文字列を含まない読み込み

についても違いを調査した。

実装方法

func(data []byte, atEOF bool) (advance int, token []byte, err error)
  • advance: 次回の読み込みが始まる開始位置
  • token: 今回の読み込まれるデータ
  • err: エラー

を実装して、Scanner.Splitに渡す。

コード例

区切り文字を含めて読み込む

"行"を区切り文字としてScannerの読み込みをする。

package main

import (
    "bufio"
    "fmt"
    "strings"
)

var source = `1行め
2行め
3行め`

func main() {
    scanner := bufio.NewScanner(strings.NewReader(source))
    delim := []byte("行")
    var splitFunction = func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        for i := 0; i < len(data); i++ {
            if data[i] == delim[0] && data[i+1] == delim[1] && data[i+2] == delim[2] {
                return i + 3, data[:i+3], nil //tokenをdata[:i+3]としているので、区切り文字は含まれる
            }
        }
        return 0, data, bufio.ErrFinalToken
    }
    scanner.Split(splitFunction)
    for scanner.Scan() {
        fmt.Printf("%#v\n", scanner.Text())
    }

}

実行結果

"1行"
"め\n2行"
"め\n3行"
"め"

区切り文字を含めないで読み込む

"、","。"を区切り文字としてScannerの読み込みをする。

package main

import (
    "bufio"
    "fmt"
    "strings"
)

var source = `真夏の宿場は空虚であった。ただ眼の大きな一疋いっぴきの蠅だけは、薄暗い厩うまやの隅すみの蜘蛛くもの巣にひっかかると、後肢あとあしで網を跳ねつつ暫しばらくぶらぶらと揺れていた。`

func main() {
    scanner := bufio.NewScanner(strings.NewReader(source))
    delim1 := []byte("、")
    delim2 := []byte("。")
    var splitFunction = func(data []byte, atEOF bool) (advance int, token []byte, err error) {
        for i := 0; i < len(data); i++ {
            if data[i] == delim1[0] && data[i+1] == delim1[1] && data[i+2] == delim1[2] {
                return i + 3, data[:i], nil//tokenをdata[:i]としているので、区切り文字は含まれない
            }
            if data[i] == delim2[0] && data[i+1] == delim2[1] && data[i+2] == delim2[2] {
                return i + 3, data[:i], nil//tokenをdata[:i]としているので、区切り文字は含まれない
            }
        }
        return 0, data, bufio.ErrFinalToken
    }
    scanner.Split(splitFunction)
    for scanner.Scan() {
        fmt.Printf("%#v\n", scanner.Text())
    }

}

実行結果

"真夏の宿場は空虚であった"
"ただ眼の大きな一疋いっぴきの蠅だけは"
"薄暗い厩うまやの隅すみの蜘蛛くもの巣にひっかかると"
"後肢あとあしで網を跳ねつつ暫しばらくぶらぶらと揺れていた"
""

入力文は青空文庫、蝿(横光利一)から持ってきた

参考

Go言語のScannerの使い方

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

HackerRankの30Days of Codeで学んだことメモ

HackerRankとは

  • 課題を通じてプログラミング言語を学習できるサイト
  • 無料
  • 扱っている言語は多数
  • その中の30Days of Codeというカリキュラムに挑戦

背景

  • Goの基本的な書き方を学ぶために始めます
  • 初心者です
  • 自分の解答やEditorialの内容を基に書きます

Day1 Data Types

ポイント

  • 標準入力から整数、浮動少数点数、文字列を取得する

メモ

  • 標準入力の取得にはfmtパッケージのScanf関数が使える
  • 引数にアドレスを指定することで、スペース区切りで順繰りに値を格納してくれる
入力
12 
4.0
    //標準入力取得用変数
    var stdin_i uint64
    var stdin_f float64

    //標準入力から整数と浮動少数点数を取得
    fmt.Scanf("%d\n%f\n", &stdin_i, &stdin_f)

参考

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

Golang - Knuth-Durstenfeld Shuffle with crypto/rand

package main

import (
    "crypto/rand"
    "fmt"
    "math/big"
)

func shuffle(array []int) {
    for i := len(array) - 1; i >= 0; i-- {
        result, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)))

        if err != nil {
            panic(err)
        }

        j := int(result.Int64())

        array[i], array[j] = array[j], array[i]
    }
}

func main() {
    a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    fmt.Println(a)
    shuffle(a)
    fmt.Println(a)
}

[1 2 3 4 5 6 7 8 9 10]
[7 2 1 10 3 8 9 6 5 4]

[1 2 3 4 5 6 7 8 9 10]
[9 2 1 5 3 10 7 4 6 8]

[1 2 3 4 5 6 7 8 9 10]
[8 1 6 7 2 3 10 9 4 5]

https://play.golang.org/p/hNmgRWnrZVy

https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle

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

ABC081B - Shift onlyを2で割れる回数の最小値から導く方法(Golang)

はじめに

ABC081B - Shift onlyを実際の操作のシミュレーションでのカウントではなく、
2で割れる回数の最小値から導く方法です。

package main

import (
  "fmt"
)

func main() {
  var n int
  min := 1000000000 // Aiの取りうる値の最大値
  fmt.Scanf("%d", &n)
  nums := make([]int, n)

  // n個の値を取得
  for i := 0; i < n; i++ {
    fmt.Scan(&nums[i])
  }

  for i :=0; i < n; i++ {
    lcnt := 0;
    // 2で割り切れる限り回し続ける
    for nums[i]%2 == 0 {
      nums[i] = nums[i]/2
      lcnt++ // 割った数をカウント
    }

    // 最小値より小さいカウントの場合入れ替え
    if min > lcnt {
      min = lcnt
    }
  }
  fmt.Println(min)
}

おわりに

説明文の操作の通り上から順序よく処理したくなりますが、選択肢として持っておきたいため書きました。

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

文系プログラマーがAtCoderをする前に確認するメモ

はじめに

AtCorderを開始する前に確認する自分用メモです。
こちらの記事は、習慣的に過去問をこなしていくときのハードルをなくすことを目的としています。

なお、私はJAVA、PHPの経験が長いですが、
現在はQAを経て、外資系コンサルティングファームに所属しており日常コードに触れる機会はありません。

前提(以下には触れていません)

  • AtCorderへのアカウント登録
  • コンテストが対象の方は、参加したいコンテストの参加登録と参加日の到来

参加対象

例です。コンテストに参加する習慣がついている方はTOPから遷移できるかと思いますが、
過去問をこなしていこうと思っている方はどこからいくんだっけ、どこまで消化したっけと迷うくらいなら直リンクで飛びましょう。
URL:https://atcoder.jp/contests/abs/tasks
やっていることメモ:AtCoder Beginners Selectionの10問目
参考:競技プログラミングって何? (はじめての高校生向け)

コンテスト(過去問や練習含む)の「参加」ボタンを押す前の準備

1. 必ず必要となる構文をすぐにコピーできる状態にしておく

競技プログラムでは、標準入力がインプットとされます。特に複数回にわたって与えられる標準入力を取得する方法がイメージつかない方はかならず準備しておいてください。

Goの例
入力
a
b c
s
上記入力を受け取って出力するための最低限の記述
package main

import (
    "fmt"
)

func main() {
    var a, b, c int
    var s string
    fmt.Scanf("%d", &a)
    fmt.Scanf("%d %d", &b, &c)
    fmt.Scanf("%s", &s)
    fmt.Printf("%d %s\n", a+b+c, s)
}

参考(Go以外の言語もこちらから):https://practice.contest.atcoder.jp/tasks/practice_1

2.よく出てくる計算や構文も同様のメモ

構文に関しては、日常業務でコーディングやられている方はソラで書けると思いますが、
業務で使わないようなニッチな計算方式(公約数など)はメモしておいたほうがいいと感じました。

パッと出ないものの例
文字列の分割取り出し(配列として扱う)
    s = "abc"
    string(s[0]) // "a"
入力数がnの場合の取得方法
nums := make([]int, n)
for i := 0; i < n; i++ {
  fmt.Scan(&nums[i])
}
各桁の和
// 83の例
// 1回目:83/10=8あまり3(1桁目の数字)
// 2回目:8/10=0あまり8(10桁目の数字)
    sum := 0
    for n > 0 {
      sum += n % 10
      n /= 10
    }
ソート
sort.Ints(nums) // 照準
sort.Sort(sort.Reverse(sort.IntSlice(nums))) // 降順
for文、range
// 普通のforの繰り返し
    for i := 0; i < 10; i++ {
    }
// while文的にも作成可能
    for n < 10 {
        n++
    }
// range
    for i, v := range s {
        fmt.Println(i, v)
    }
if文
if score := 52; score > 80 {
        fmt.Println("Great!")
    } else if score > 60 {
        fmt.Println("Good!")
    } else {
        fmt.Println("soso")
    }
switch文
    switch signal {
    case "red":
        fmt.Println("Stop")
    default:
        fmt.Println("wrong")
    }
スライス
    a := [5]int{2, 10, 8, 15, 4}
    b := a[2:4] // [8, 15]
// make関数でいきなりスライスを作成する
    s1 := make([]int, 3) // [0 0 0]
// いきなり値を割り当てたスライスを作成する
    s2 := []int{1, 3, 5} // 配列の宣言と似ている
// appendでスライスの末尾に要素を追加
    s3 := append(s2, 8, 2, 10)
配列
// 宣言と代入を分ける
    var a [5]int
        a [2] = 3
// 宣言と代入を同時にする
    b := [3]int{1, 3, 6}
// 配列の個数が未定の場合
    c := [...]int{2, 4, 7, 5, 5}
マップ
// 値を指定しながら宣言する
    m := map[string]int{"fujimoto": 100, "arita": 200}
// キーの存在を調べる
    v, ok := m["fujimoto"]
// 要素を削除する
    delete(m, "fujimoto")

参考:他言語プログラマがgolangの基本を押さえる為のまとめ

3.macに置けるバックスラッシュの入力の仕方を思い出す

optionキーを押しながら、¥キーを押す。(Mac mini 等で Windows用のキーボードを接続している場合は Alt キー)
※IME「ことえり」の設定を変えることで「¥」を「\」にすることは可能

提出が終わったらすること

2のメンテ

自身の不足点やパッと出なかったものを追加します。
ソラで書けるようになったものがあれば削除します。

次の参加対象URLを更新

これをしておくことで、次にPCを開いたとき、何をするのか考えることなく画面遷移して始められます。

おわりに

日常的にコードに触れていないことと過去の経験言語と微妙に違う書き方(for文、if文が特に)でコンパイルエラーが出てしまうことや間隔が開いたときの「何するんだっけ(どこまでしていたんだっけ)」でハードルが上がっていたので書きました。
まずは、増える一方の2のメモを0個にすることを目標とします。

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

WSL上でgolangを使ってMP3の情報を取得する

go環境の用意

$ git clone https://github.com/syndbg/goenv.git ~/.goenv

~/.bashrc に追記

export GOENV_ROOT=$HOME/.goenv
export PATH=$GOENV_ROOT/bin:$PATH
eval "$(goenv init -)"

go のバージョン設定、確認

$ goenv install 1.13.7
$ go version
go version go1.13.7 linux/amd64

id3-go を使ってMP3の情報を取得する

GitHub - mikkyang/id3-go: ID3 library for Go

$ go get github.com/mikkyang/id3-go

サンプルコード

package main

import (
    "fmt"
    id3 "github.com/mikkyang/id3-go"
)

func main() {
  mp3File, _:= id3.Open("sample.mp3")
  defer mp3File.Close()

  fmt.Println(mp3File.Artist())
}

同じディレクトリに sample.mp3 を用意して実行

$ go run mp3test.go
SampleArtist

参考にしたサイト

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