20210715のGoに関する記事は9件です。

go-cmpでpanicがおきたら

対処方法 package main import ( "fmt" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ) type Struct struct { XXX int xxx int } func main() { s1 := Struct{ 1, 1} s2 := Struct{ 1, 2} opt := cmpopts.IgnoreUnexported(Struct{}) // ここ大事 fmt.Println("Diff: ", cmp.Diff(s1, s2, opt)) } PlayGroundで試す もう少し詳しく どうやらgo-cmpは、プライベートな変数があるとパニックになってしまうらしい。 実はこのプライベートの取り扱い方でいくつかパターンが用意されている。 プライベート変数は一致しなくてよい場合 プライベート変数も一致していてほしい場合 指定した変数のみ無視してほしい場合 プライベート変数は一致しなくてよい場合 import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ... opt := cmpopts.IgnoreUnexported(Struct{}) // 最初のものと一緒 fmt.Println("Diff: ", cmp.Diff(s1, s2, opt)) PlayGroundで試す プライベート変数も一致していてほしい場合 import ( "github.com/google/go-cmp/cmp" ... opt := cmp.AllowUnexported(Struct{}) // こちらはcmpoptsではないので注意 fmt.Println("Diff: ", cmp.Diff(s1, s2, opt)) PlayGroundで試す 指定した変数のみ無視してほしい場合 import ( "github.com/google/go-cmp/cmp" ... // 変数名を文字列で指定, パブリック変数も指定可能 opt := cmpopts.IgnoreFields(Struct{}, "xxx") fmt.Println("Diff: ", cmp.Diff(s1, s2, opt)) PlayGroundで試す さらに詳しく ここまでで解決できない方は恐らく、構造体が複数の層に分かれているかと思います。 今回は、各構造体のプライベート変数の一致を無視した場合の対処法を以下に示します。 各自のユースケースに合わせてご使用ください。 package main import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" ) var ( ignoreOpts = cmpopts.IgnoreUnexported( Car{}, Wheel{}, ) car1 = &Car { speed: 2.0, Wheels: []*Wheel{ &Wheel{ Size: 3.0, hardness: 1.0, }, &Wheel{ Size: 4.0, hardness: 2.0, }, &Wheel{ Size: 5.0, hardness: 3.0, }, &Wheel{ Size: 6.0, hardness: 4.0, }, }, } car2 = &Car { speed: 4.0, Wheels: []*Wheel{ &Wheel{ Size: 3.0, hardness: 4.0, }, &Wheel{ Size: 4.0, hardness: 3.0, }, &Wheel{ Size: 5.0, hardness: 2.0, }, &Wheel{ Size: 6.0, hardness: 1.0, }, }, } car3 = &Car { speed: 2.0, Wheels: []*Wheel{ &Wheel{ Size: 3.0, hardness: 1.0, }, &Wheel{ Size: 4.0, hardness: 2.0, }, &Wheel{ Size: 5.0, hardness: 3.0, }, }, } ) type Car struct { speed float64 Wheels []*Wheel } type Wheel struct { Size float64 hardness float64 } func CarDiff(left *Car, right *Car) string { return cmp.Diff(left, right, ignoreOpts) } func main() { println("car1 & car2\n", CarDiff(car1, car2)) println("car1 & car3\n", CarDiff(car1, car3)) } PlayGroundで試す その他 まとめて比較してくれる機能は大変便利ですが、使用する場合はテストの結果を比較するときか、簡単なコードを書くときくらいにしときましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonしか知らない人がGoに入門した時モジュール管理がわからんかった

注意 この記事は 2021/03/16 にGoを始めたときに書いた記事です。 あとからもうちょい修正しようと思って寝かしてたんですが、結局直さなかったので 途中までになりますが、載せておきます。 Goはじめたい理由 噂によるとGoは動作が速いらしいので、PythonのFlaskで書いてたAPIをGoのEchoで置き換えたい。 大体それだけが理由です。() 用意したもの Windows 10 - Macは持っていないのでとりあえず Python 3.8.6 windows/amd64 - いつもよく使っているPythonくん  - 公式サイトからインストーラをダウンロードして実行しただけ Go 1.15.6 windows/amd64  - 割と最近入れたGoくん  - 公式サイトからインストーラをダウンロードして実行しただけ VSCode - メインで使っているエディタ まずはじめにしたこと Go 入門 でググってみる。 すると Go言語の初心者が見ると幸せになれる場所 が出てきたので 各ページを若干読んでみることとした。 この中だと、友人いわく A Tour of Goがおすすめとのことらしいが、とりあえず何かしら作ってみないと構文を覚えられる気がしないので、ハローワールド、BMI、ガチャを作ってみることにした。 VSCodeを設定しよう VSCode → 拡張機能タブ → Goと検索 → 一番上に出てくる拡張機能をインストールする。 その時点で Goが綺麗に表示されるようになるはず。 よくわからないがおすすめっぽいので、Go言語をVSCodeで開発する環境をインストールするを読んで、拡張機能の依存関係もインストールした。 Helloworldをしよう まぁこれは一瞬でできる。見た通りだよなって感じだったので特に苦労していない。 main.go package main import ( "fmt" ) func main() { fmt.Println("Hello world!") } このファイルをVSCodeで作って保存する。 そして、F5キーを押すと実行されて Hello world! と出るはず。 Hello world! という文字を 別モジュールから読み出したい ハローワールドはわかったが、よくあるやつすぎて面白くないので、 Hello world! って文字列を 別ファイルから読み出したくなった。 Pythonではpip Goでは Go modules が定番? チュートリアルの一部では GOPATH がどうこうという話が出てきていたが、 最近(Go 1.11以降)では Go modules という依存関係管理ツールが使われることが多いらしい。Go言語の依存モジュール管理ツール Modules の使い方を読んで、やってみようとした。 とりあえずまず、コマンドプロンプトで go env -w GO111MODULE=onする。 次に main.goと同じフォルダ内で、 fixed_valueというフォルダを作り、 go mod init fixed_value してみた。 すると、go.mod というファイルができて、モジュールとして扱ってくれるようになる。 最後にパッケージとして fixed_value/fixed_value.go package fixed_value var Message string = "Hello world" っていうのを作ってみた。 これをmain.goで読み出したいので main.go package main import ( "fmt" "./fixed_value" ) func main() { fmt.Println(fixed_value.Message) } Pythonの__init__.pyみたいには動かない で、実行してみると build command-line-arguments: cannot find module for path _/D_/Documents/Gits/go-practice/helloworld/fixed_value と怒られてしまった。 どうやら Pythonの __init__.py のつもりで パッケージフォルダ内に置いても動かないようだった。 (VSCodeのlintが起きてる場合はそもそも保存させてくれなくて実行できない) じゃあどうするの? 相対インポートというものは、許されざる悪みたいな立ち位置にいるらしく、絶対インポートしないとダメらしい。 絶対インポートと言っても、フォルダのパスではなく、リポジトリのパスを入れるってことらしい。 リポジトリを使う絶対インポート?? まずさっき 作った fixed_value/go.mod は削除する。その上で 直下に改めて go.modを作成する。 その際には、 go mod init github.com/Dosugamea/go-practice/helloworld というようにリポジトリ名(+必要ならフォルダパス) を付ける。 これで go.modを作った。じゃあどうやって main.goからfixed_valueをインポートをするのかというと以下のようにする。 main.go package main import ( "fmt" "github.com/Dosugamea/go-practice/helloworld/fixed_value" ) func main() { fmt.Println(fixed_value.Message) } これでいざできたと思って実行しようとすると、実は実行できない。先に go.mod と fixed_value を コミットして リポジトリにプッシュした後で初めて main.goが実行できるようになるようだ。 go.modの中で Replaceという細工をして、相対インポートをさせる方法もあるが、リポジトリにプッシュしてしまうのが一番丸いらしい。 これでなんとなく Goでのモジュールの扱い方がわかった。gg。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GO言語】Twitterで自動いいねbotの作り方

久しぶりに投稿します。 今回はタイムラインに流れてくるツイートに対して、 自動いいねできるコードを作成したので、そのご紹介です。 1ヶ月くらいやるとフォローが減ったりします。 やったこと ・Go言語で自動いいねbotの作成 環境 ・Go (version 1.158.8) ・WindowsServer2019  ※実行方法はタスクスケジューラで定期実行してました。 実装 Twiter認証 Twiter認証.go import ( "github.com/ChimeraCoder/anaconda" ) //Twitterの認証を行うメソッド func GetTwitterApi() *anaconda.TwitterApi { anaconda.SetConsumerKey("XXXXXX") anaconda.SetConsumerSecret("XXXXXXXX") api := anaconda.NewTwitterApi("XXXXXX", "XXXXXXXX") return api } まず、これをやらないとはじまらない! Twitter認証を行います。今回もanacondaを利用しました。 Twitterであれば基本的にanacondaで実装できるのでおすすめです。 https://pkg.go.dev/github.com/ChimeraCoder/anaconda タイムライン取得 タイムライン取得.go import ( "net/url" ) ---中略--- //Twitterの認証情報を取得 api := GetTwitterApi() //タイムライン取得用の引数を設定 v := url.Values{} v.Set("count", "10") //タイムラインを10件取得する tweets, err := api.GetHomeTimeline(v) if err != nil { panic(err) //error時の処理 } 先程のAPIを呼び出して、タイムラインを取得します。 vはhttps://pkg.go.dev/net/urlの型になります。 タイムラインを取得するAPIの引数に必要なため、宣言してます。 api.GetHomeTimeline(v)でtweetsに最新10件のツイートをリスト型で格納します。 いいね処理 いいね処理.go //取得したツイート分繰り返し for _, tweet := range tweets { //ツイートがいいねされているかで条件分岐 if tweet.Favorited { //いいねされていれば何もしない fmt.Println("いいね済みでした") //いいねされていない場合 } else { _, err := api.Favorite(tweet.Id) //いいね処理 if err != nil { panic(err) //error時の処理 } } 取得したツイート(リスト型)に対して、いいねされているかどうかで条件分岐し、 いいねされていない場合にFavorite(Id)でいいね処理を行います。 tweet.Idはツイートに対して一意に割り当てられているIDになります。 全処理 まとめると以下の通りです。 いいねbot.go import ( "fmt" "net/url" "github.com/ChimeraCoder/anaconda" ) func main() { //Twitterの認証情報を取得 api := GetTwitterApi() //タイムライン取得用の引数を設定 v := url.Values{} v.Set("count", "10") //タイムラインを10件取得する tweets, err := api.GetHomeTimeline(v) if err != nil { panic(err) //error時の処理 } //取得したツイート分繰り返し for _, tweet := range tweets { //ツイートがいいねされているかで条件分岐 if tweet.Favorited { //いいねされていれば何もしない fmt.Println("いいね済みでした") //いいねされていない場合 } else { _, err := api.Favorite(tweet.Id) //いいね処理 if err != nil { panic(err) //error時の処理 } } } //Twitterの認証を行うメソッド func GetTwitterApi() *anaconda.TwitterApi { anaconda.SetConsumerKey("XXXXXX") anaconda.SetConsumerSecret("XXXXXXXX") api := anaconda.NewTwitterApi("XXXXXX", "XXXXXXXX") return api } 以上です。ありがとうございました。 楽しいTwitter生活を送ってください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【tmux】smugを利用したワークフロー管理及び自動化

普段tmuxを利用して複数端末を起動した状態で作業することが多い。 非常に便利だが、以下の点を解消するべく調査。 定型作業の自動化 同様のサーバー接続や複数画面分割等の定型的なtmuxワークフローの手間。 プロジェクトや作業単位での管理 日々行なっている複数作業の内容の把握が難航。 調査の結果、複数の類似プロジェクトがあるが比較的導入が容易であろうsmugというツールがあったため、今回は利用方法を記録する。 概要 Go製のtmuxワークフロー管理ツール ウィンドウやペイン分割数、起動コマンド等の単一のファイル(yaml)で管理可能。 同様のツールにRuby製のtmuxinatorがある。 Github 結果 以下のように単一の構成ファイルを作成して、単一のコマンドでペインの分割や起動時のコマンドを管理することが可能。 環境 Mac OS X 10.15.6 tmux 3.2 手順 インストール Mac mkdir -p /tmp/work; cd $_ # GithubのReleaseから任意のバージョンを選択。wgetがなければcurlで代替。 wget https://github.com/ivaaaan/smug/releases/download/v0.2/smug_0.2_Darwin_x86_64.tar.gz tar -xzf smug_0.2_Darwin_x86_64.tar.gz mv smug /usr/local/bin rm -rf /tmp/work; cd /tmp その他 その他のインストールはこちらを参照。 ワークフローの定義 普段の作業を要件として定義する必要があるため、今回利用する簡単な例は以下。 Dockerを開発環境とするsampleプロジェクトが対象 4画面に分割。 1画面 : コンテナ接続画面。接続している状態である。 2画面 : ソースファイル編集画面 3画面 : ローカルの通常ターミナル 4画面 : Dockerfile編集画面 起動時にプロセス起動、終了時に終了 ※対象プロジェクトではDockerを利用しているため、先にdocker-compose buildを済ましておく。 構成ファイルの作成 ワークフロー定義後、それに従い以下の構成ファイルに記述。 ~/.config/smugディレクトがない場合は新たに作成 ※このディレクト内に作成することで、実行時はsmug start session名だけで良い。 ※configディレクトリの作成が難しい場合、単一のyamlファイルを作成して実行時にsmug start -f ***.ymlとして直接パスを指定すれば良い。 ~/.config/smug/sample.yml # セッション名 session: sample # 対象ルートパス root: ~/work/sample # 起動前のコマンド指定 before_start: - docker-compose up -d # 終了時のコマンド指定 stop: - docker-compose down # ウィンドウ情報 windows: - name: sample layout: tiled # レイアウト情報(https://github.com/tmux/tmux/blob/2.7/layout-set.c) commands: # 起動時のコマンド指定 - docker-compose exec sample bash panes: # ペイン情報 - type: tiled # レイアウト情報 commands: - vim Dockerfile - type: tiled commands: - vim README.md - type: tiled commands: - vim bin/ncname 実行 構成ファイル作成後、以下で開始。 smug start sample 停止するには以下のコマンドで実行 smug stop sample 結果 以下のようになっていれば完了。 まとめ 日々利用している定型作業を管理及び自動化できたため安心。 こうしたわずかな地味な効率化が重要と実感。 ただ日の浅いツールのため、様子を見ながらの利用を継続。 参考 https://github.com/ivaaaan/smug
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】fmt.Printfには改行を入れよう

概要 先日、db周りの変数を調べるためにfmt.Printfを使って確認していたらfmt.Printfの中身が出力されなくなったので、自分の解決方法を書いておきます。 解決方法 main.go fmt.Printf("hoge変数の中身=%v",hoge)//これを fmt.Printf("hoge変数の中身=%v\n",hoge)//こうする(\nを付ける) 初歩的な事だとは思いますが改行とともに何の変数かがわかる文字列も一緒に出力すると分かりやすくていいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 196 ~Goの練習~

A問題 考察 $x$の最大値から$y$の最小値を引けばよく、$b-c$を出力するのみ。 コード package main import ( "bufio" "fmt" "os" ) func main() { scin := bufio.NewReader(os.Stdin) prout := bufio.NewWriter(os.Stdout) defer prout.Flush() // code var a, b, c, d int fmt.Fscan(scin, &a, &b) fmt.Fscan(scin, &c, &d) fmt.Fprintln(prout, b-c) } B問題 考察 浮動小数点数であってもおそらく表現できないので、文字列として扱えば良い。 以下では、.を見つけるまで探していくという方法をとっているが、.を境にsplitするなど色々方法はあるはず。 文法 string型の配列であれば、joinして文字列として出力することができる。 また、byte型の変数は組み込みのstring関数をかませば型変換できる。 コード package main import ( "bufio" "fmt" "os" "strings" ) func main() { scin := bufio.NewReader(os.Stdin) prout := bufio.NewWriter(os.Stdout) defer prout.Flush() // code var x string fmt.Fscan(scin, &x) var y []string for _, s := range x { if s == '.' { break } y = append(y, string(s)) } fmt.Fprintln(prout, strings.Join(y, "")) } C問題 考察 このようなパターンの問題は周期的に数えられる部分と数えられない部分に分けて考えると良い。 ここで、簡単のために「$x$の十進表記が偶数桁で、その前半と後半は文字列で等しい」という条件をここでは$P$と置き、$n$は$l$桁であるとする。 まず、$l$が奇数の時は、$2$桁から$2 \times [\frac{l}{2}]$桁までの整数は$1$以上$N$以下の条件を満たす。したがって、この桁数の数のうち$P$を満たすものを探せば必要十分となる。また、整数を文字列として見た時に前半の部分のみを決めれば後半も決まるので、$2 \times m$桁の数のうち$9 \times 10^{m-1}$個が$P$の条件を満たす。 次に、$l$が偶数の時は、$2$桁から$2 \times [\frac{l}{2}]-2$桁までの整数は$1$以上$N$以下の条件を満たす。これらの整数については、$l$が奇数の場合と同様に個数を数えれば良い。また、$l$桁の整数は$9 \times 10^{[\frac{l}{2}]-1}$個の中から探せば良く、$l < 12$なので全探索を行うことができる。 文法 累乗の計算が繰り返し自乗法とテイラー展開のどちらを使っているかで速度がだいぶ変わるのと、いずれにせよfloat64で定義されているのは不安なので、自分で関数を作ることになるかもしれない。 また、桁数の計算は、文字列に直して長さを計算するのが手っ取り早く、len(strconv.Itoa(n))とやるのが楽だと思われる。別の方法があれば有識者の方に教えてもらいたい。 コード package main import ( "bufio" "fmt" "math" "os" "strconv" ) func main() { scin := bufio.NewReader(os.Stdin) prout := bufio.NewWriter(os.Stdout) defer prout.Flush() // code var n int fmt.Fscan(scin, &n) l := len(strconv.Itoa(n)) ans := 0 if l%2 == 1 { for m := 1; m <= l/2; m++ { ans += (9 * int(math.Pow10(m-1))) } } else { for m := 1; m < l/2; m++ { ans += (9 * int(math.Pow10(m-1))) } for x := int(math.Pow10(l/2 - 1)); x*int(math.Pow10(l/2))+x <= n; x++ { ans++ } } fmt.Fprintln(prout, ans) } D問題 考察 だいぶ実装の面倒そうな問題ではあったが、意外と難しくはなかった。 まず、$H$と$W$の制約が厳しいことがわかる。ここで長方形と正方形のどちらのタイルが敷かれているのかをそれぞれのグリッドで$0,1$として保存すると、$HW \leqq 16$より$2^{16}$程度の状態数になることがわかる。また、実際の畳の敷き詰め方はその状態数よりかなり小さくなるのではということが、例を見ても見なくても想像に難くない。 よって、何らかの方法で任意の敷き詰め方を調べ、その中で長方形のタイルが$A$枚で正方形のタイルが$B$枚である敷き詰め方をカウントすれば良いことがわかる。ここで、調べ方については、どのようなタイルの置き方がされているかをグリッド一つずつで調べ、複数通りの置き方がある場合も再帰関数を分岐させていくことで調べるようにした。また、グリッドを一つずつ調べるときは、通常の二次元配列の操作と同様に上から下、左から右へと走査することにした。 実装としては、再帰関数searchの引数に$grid,i,j,t1$を渡す。また、再帰関数の返り値としては、グリッド$(i,j)$までそれぞれのグリッドでのタイルの置き方を決めて部屋全体のグリッドが$grid$となる場合の合計の敷き詰め方である。また、$t1$は長方形のタイル数となる。ここで、$grid$には長方形のタイルを置いたグリッドに$1$を記録し、正方形のタイル ここまで方針が立てば、場合分けを行うのみであり、下記に示す。また、下記で、「縦方向に」とある場合は縦方向が長くなるような長方形の置き方、「横方向に」とある場合は横方向が長くなるような長方形の置き方を示す。 最終マスの時:i == h-1 && j == w-1 右下のマスまで敷き詰め方が決まった時に再帰は終了 返り値:$t1 == a$の時は1、$t1 != a$の時は0 最終行の時:i == h-1 縦方向に長方形のタイルを置くことはできないことを意識して場合分け 長方形のタイルを横方向に置けない場合:grid[i][j] == 1 || grid[i][j+1] == 1 そのマスに正方形のタイルを置く場合のみ grid_n:gridをコピーしたもの 返り値:search(grid_n, i, j+1, t1) 長方形のタイルを横方向に置ける場合:else そのマスに正方形のタイルを置くor長方形のタイルを横方向に置く grid_n2:gridをコピーしたもの grid_n1:gridをコピーし、grid_n1[i][j]とgrid_n1[i][j+1]を1に変える 返り値:search(grid_n1, i, j+1, t1+1) + search(grid_n2, i, j+1, t1) 最終列の時:j == w-1 横方向に長方形のタイルを置くことはできないことを意識して場合分け 長方形のタイルを縦方向に置けない場合:grid[i][j] == 1 そのマスに正方形のタイルを置く場合のみ grid_n:gridをコピーしたもの 返り値:search(grid_n, i, j+1, t1) 長方形のタイルを縦方向に置ける場合:else そのマスに正方形のタイルを置くor長方形のタイルを縦方向に置く grid_n2:gridをコピーしたもの grid_n1:gridをコピーし、grid_n1[i][j]とgrid_n1[i+1][j]を1に変える 返り値:search(grid_n1, i+1, 0, t1+1) + search(grid_n2, i+1, 0, t1) それ以外の時:else 横方向にも縦方向にも長方形のタイルを置くことができることを意識して場合分け この条件分岐の説明は長くなりそうなので省略します。 文法 再帰を久しぶりに書き、当然Goでも書いたことがなかったので時間がかかってしまった。 具体的には二次元スライスのdeepcopyのやり方で多くの時間を費やしてしまった。 また、下記のdeepcopyについての記述は、後々記事にまとめる予定である。 スライスのdeepcopy まず、一次元スライスの場合のdeep copyは下記のようになる。 スライスとして指す配列の領域を確保しておき、その領域へとcopy関数によりコピーを行う。 package main import "fmt" func main() { s := []int{1, 2, 3, 4} // deep copy t := make([]int, 4) copy(t, s) // 要素の変更 t[1] = 100 //出力:[1 2 3 4] fmt.Println(s) //出力:[1 100 3 4] fmt.Println(t) } それでは、二次元スライス(多次元スライス)の場合はどうなるだろうか? 二次元スライスの場合は、t1の方法では、shallow copyとなってしまう。 なぜなら、func copy(dst, src []Type) intであり、Typeがスライスとなる場合は配列の実態を共有してしまうからである。 それに対し、t2の方法では、deep copyとなる。 なぜなら、先程のTypeはスライスではなくintであり、実体をその変数が持つからである。 package main import "fmt" func main() { s := [][]int{{1, 2, 3, 4}, {5, 6, 7, 8}} // t1はshallow copy t1 := make([][]int, 2) for i := range s { t1[i] = make([]int, 4) } copy(t1, s) // t2はdeep copy t2 := make([][]int, 2) for i := range s { t2[i] = make([]int, 4) copy(t2[i], s[i]) } // 要素の変更 t1[0][0] = 100 t2[0][0] = 99 //出力:[[100 2 3 4] [5 6 7 8]] fmt.Println(s) //出力:[[100 2 3 4] [5 6 7 8]] fmt.Println(t1) //出力:[[99 2 3 4] [5 6 7 8]] fmt.Println(t2) } コード package main import ( "bufio" "fmt" "os" ) var h, w, a, b int // 新しいgrid作らないと参照してしまうので注意 // 与えられたグリッドの(i,j)を決めて、次を決める関数 // t1:2*1の畳の枚数 func search(grid [][]int, i int, j int, t1 int) int { if i == h-1 && j == w-1 { // 最終マスの時 if t1 == a { return 1 } else { return 0 } } else if i == h-1 { // 最終行の時(縦方向はない) if grid[i][j] == 1 || grid[i][j+1] == 1 { grid_n := make([][]int, h) for i := range grid { grid_n[i] = make([]int, w) copy(grid_n[i], grid[i]) } return search(grid_n, i, j+1, t1) } else { grid_n1 := make([][]int, h) grid_n2 := make([][]int, h) for i := range grid { grid_n1[i] = make([]int, w) grid_n2[i] = make([]int, w) copy(grid_n1[i], grid[i]) copy(grid_n2[i], grid[i]) } // 横で2枚 grid_n1[i][j] = 1 grid_n1[i][j+1] = 1 return search(grid_n1, i, j+1, t1+1) + search(grid_n2, i, j+1, t1) } } else if j == w-1 { // 最終列の時(横方向はない) if grid[i][j] == 1 { grid_n := make([][]int, h) for i := range grid { grid_n[i] = make([]int, w) copy(grid_n[i], grid[i]) } return search(grid_n, i+1, 0, t1) } else { grid_n1 := make([][]int, h) grid_n2 := make([][]int, h) for i := range grid { grid_n1[i] = make([]int, w) grid_n2[i] = make([]int, w) copy(grid_n1[i], grid[i]) copy(grid_n2[i], grid[i]) } // 縦で2枚 grid_n1[i][j] = 1 grid_n1[i+1][j] = 1 return search(grid_n1, i+1, 0, t1+1) + search(grid_n2, i+1, 0, t1) } } else { // その他(縦方向も横方向もあり得る) if grid[i][j] == 1 { // そのマスがダメ grid_n := make([][]int, cap(grid)) for i := range grid { grid_n[i] = make([]int, w) copy(grid_n[i], grid[i]) } return search(grid_n, i, j+1, t1) } else if grid[i][j+1] == 1 { // 横方向がダメ grid_n1 := make([][]int, h) grid_n2 := make([][]int, h) for i := range grid { grid_n1[i] = make([]int, w) grid_n2[i] = make([]int, w) copy(grid_n1[i], grid[i]) copy(grid_n2[i], grid[i]) } // 縦で2枚 grid_n1[i][j] = 1 grid_n1[i+1][j] = 1 return search(grid_n1, i, j+1, t1+1) + search(grid_n2, i, j+1, t1) } else { // どちらもOK grid_n1 := make([][]int, h) grid_n2 := make([][]int, h) grid_n3 := make([][]int, h) for i := range grid { grid_n1[i] = make([]int, w) grid_n2[i] = make([]int, w) grid_n3[i] = make([]int, w) copy(grid_n1[i], grid[i]) copy(grid_n2[i], grid[i]) copy(grid_n3[i], grid[i]) } // 横で2枚 grid_n1[i][j] = 1 grid_n1[i][j+1] = 1 // 縦で2枚 grid_n2[i][j] = 1 grid_n2[i+1][j] = 1 return search(grid_n1, i, j+1, t1+1) + search(grid_n2, i, j+1, t1+1) + search(grid_n3, i, j+1, t1) } } } func main() { scin := bufio.NewReader(os.Stdin) prout := bufio.NewWriter(os.Stdout) defer prout.Flush() // code fmt.Fscan(scin, &h, &w, &a, &b) grid := make([][]int, h) for i := 0; i < h; i++ { grid[i] = make([]int, w) } fmt.Fprintln(prout, search(grid, 0, 0, 0)) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】入門(メソッドとレシーバ)

はじめに Go初心者です。 メソッドとレシーバについてまとめてみました。 メソッド / レシーバとは メソッドとはレシーバに呼び出される関数のこと。 レシーバとはメソッドを呼び出す側のオブジェクトのこと。 以下のコードだと、 (p Person)がレシーバで、 Greet()がメソッド。 type Person struct { Name string } func (p Person) Greet() string { return "Hi, " + p.Name } メソッドを使ってみる メソッドはレシーバで定義した構造体の変数から呼ぶことができる。 下の例では、Person型の変数personが、Greetメソッドを呼んでいる。 type Person struct { Name string } func (p Person) Greet() string { return "Hi, " + p.Name } func main() { person := Person{Name: "hoge"} fmt.Println(person.Greet()) } 値レシーバとポインタレシーバ レシーバには値レシーバとポインタレシーバの2種類がある。 ポインタレシーバ:ポインタ型を引数に渡すので、関数内でオブジェクトの値を変更できる。 値レシーバ:元の値とは別のコピーした値を引数に渡すので、関数内で元の値は変更できない。 以下のコードでは、 (p Person)をレシーバとして定義しているのが値レシーバ。 (p *Person)をレシーバとして定義しているのがポインタレシーバ。 type Person struct { Name string } // 値レシーバ func (p Person) Greet() string { return "Hi, " + p.Name } // ポインタレシーバ func (p *Person) Rename(newName string) string{ p.Name = newName return p.Name } ポインタレシーバを使う場面 使う場面は2点ありそう その構造体型のデータの値を変えたい時 メモリを効率的に利用したい時 値レシーバがコピーした引数を用いるのに対して、ポインタレシーバでは構造体そのもののデータを操作できる。 また、引数をコピーするとそれだけ余分にメモリを消費するため、メモリ消費を抑えたい場合はポインタレシーバを使うと良さそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ebitenで画像の表示を関数化する

はじめに 先日、このゲームエンジンはアートであるという記事を読んで、go言語のことやebitenの設計思想を見ているうちに「これは自分に合ってるかもしれない!」と強く思い手を出してみた。しかしebitenはじめgo言語は触ったことがなく、とりあえず学習として画像の表示を関数化することを目標にした。 ebitenとは? go言語で作られた汎用2Dゲームエンジンで、設計思想にある「ミニマムな API で実用的な 2D ゲームが作れるかどうか」の部分で、RigidChipsをやっていた頃を思い出し、今回手を出してみることにしました。 インストールとかチュートリアル 公式がとてもよくわかりやすいのでまずはこちらへ サンプルの画像が回転するやつのソースコードを見てみる 公式のソースコードを見てみましょう(英語の解説の部分を日本語に翻訳してちょっとコメントを追加しています) func (g *Game) Draw(screen *ebiten.Image) { // 画像のサイズを取得 w, h := gophersImage.Size() // 画像のオプションの準備 op := &ebiten.DrawImageOptions{} // 画像の中心をスクリーンの左上に移動させる // これは回転の準備で、 ジオメトリマトリックス(回転や移動の処理)が適用されるときの // 原点が画面の左上だからである op.GeoM.Translate(-float64(w)/2, -float64(h)/2) // 画像を回転させる。結果的に回転のアンカーとなるポイントは画像の中心となる op.GeoM.Rotate(float64(g.count%360) * 2 * math.Pi / 360) // 画像をスクリーンの中心に持ってくる op.GeoM.Translate(screenWidth/2, screenHeight/2) // 画像を描画する screen.DrawImage(gophersImage, op) } 画像を任意の位置と角度で表示するためだけに、結構記述をする必要があります。 ここにこの設計思想である「ミニマムなAPI」というのが出てますね。基本的なパーツを用意して、自分で組み立てていく感じが本当にたまらんのです。 とはいえ、毎回画像を表示させるためだけにこれだけ書くのはしんどいので関数化して簡単に書けるようにしましょう。 ファイル構成 . ├── src │ └── image │ └── image.go └── main.go └── go.mod └── go.sum └── testicon.png (適当に用意してください) 完成したコード main.go package main import ( "log" // これはインポートしたときの効果を得るために_つけてる _ "image/png" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" // github.com/username/projectname の部分はgo.modのmoduleを入れてください "github.com/username/projectname/src/image" ) // グローバルにimage使うためにここで宣言 var img *ebiten.Image // これは一番最初に一度だけ呼ばれる func init() { //画像を読み込む var err error img, _, err = ebitenutil.NewImageFromFile("testicon.png") if err != nil { log.Fatal(err) } } type Game struct{ // Gameオブジェクトにint型のcountを宣言する count int } func (g *Game) Update() error { // マイフレームごとにgを1つずつ増加させる g.count++ return nil } func (g *Game) Draw(screen *ebiten.Image) { // 画像をグルグル回転させながら表示させる image.Show(screen, img, 0.1, 100, 100, float64(g.count%360)) // 画像を45度傾けた状態で表示させる image.Show(screen, img, 0.1, 150, 150, 45) } func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { return 320, 240 } func main() { ebiten.SetWindowSize(640, 480) ebiten.SetWindowTitle("Hello, World!") if err := ebiten.RunGame(&Game{}); err != nil { log.Fatal(err) } } src/image/image.go package image import ( "math" "github.com/hajimehoshi/ebiten/v2" ) /* 引数解説 screen : *ebiten.Image 描画するスクリーン img : *ebiten.Image 描画したい画像 coefficient : float64 画像の縮小/拡大の係数 x_coodinates : float64 画像のX座標 y_coodinates : float64 画像のY座標 angle : float64 画像の回転角度(degree 度 ) */ func Show(screen *ebiten.Image, img *ebiten.Image, coefficient float64, x_coodinates float64, y_coodinates float64, angle float64) { // 画像のサイズを取得 w, h := img.Size() // 係数で画像を拡大/縮小したときの大きさを計算しておく var sw, sh float64 = float64(w) * coefficient, float64(h) * coefficient // オプションを宣言 op := &ebiten.DrawImageOptions{} // 画像を拡大/縮小する op.GeoM.Scale(coefficient, coefficient) // 縮小したサイズに合わせて、画面の左上に縦横半分めり込む形にする op.GeoM.Translate(-sw/2, -sh/2) // 画像を画面の左上を中心に回転させる(縦横半分めり込んでいるので、中心で回転することになる) op.GeoM.Rotate(angle / 180 * math.Pi) // 好きな位置へ移動させる op.GeoM.Translate(x_coodinates, y_coodinates) // 画像を描画する screen.DrawImage(img, op) } 画像表示を切り出したおかげで、main.goの中で画像を描画する際には1行で済むようになりました。 importは絶対パスで書く 相対パスで書いて全然読み込まれない、、、と思ったら、絶対パスで書くらしいです go.modに書いてあるモジュール名を使って、絶対パスを書いてください 参考:https://qiita.com/momotaro98/items/23fa4356389a7e610acc おわりに いろんなエフェクトのライブラリ作っていきたいです。 まじでrigidchips思い出すなぁ。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ホットリロードツールの作り方(GoCon 2021 Spring)

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