2021/08/18
はじめに
そろそろ Golang へ入門したいと思い,LeetCode を通して Golang を触りの部分ですが,はじめてみました.
これまでは LeetCode は C++で解いていたため,C++との差に対して過度に反応しています.
同じく C++を触っていて,Golang を始める方にとっては読み物としてお役に立つかもしれません.よかったら作業の脇に置いてみてください.
そもそも
三項演算子がない
if-else で対応
declared but not used がデフォルト
変数を使っていないと怒ってくれる.使わない戻り値は_
対応.
クラスがない
struct
とメソッドで対応.(個人的に,クラスをなくしたのは英断だと思う)
自分で簡単なライブラリを作る時は,横着して class
内に実装を書いてしまうので,それを強制的に回避させられているので,今までの横着さを反省した.
命名規則の強制
struct
にメソッドを追加する際,大文字スタートであればパブリック,小文字スタートであればプライベートのように,命名規則で扱いが変わる.
range-loop 便利
C++とは違い,インデックスとイテレータを同時に取ることができる
for i, v := range nums {
//TODO: 要らない方は_で対応
}
hashset がない
map
で対応
queue がない
list
で対応
bitset 型がない
C++の bitset
よくわかってなかったので,助かった.
uint
で対応
自動で複数の戻り値を tie()してくれる
C++で複数の戻り値を受ける場合,関数の戻り値を std::tuple<T1,T2>()
としつつ,std::tie()
をする必要があるが,Go では自動で受け取ってくれる.必要ない値を受け取らなくてはならない時,_
で対応.
error 型が帰ってくるのが,競プロなどでは不要だが,真面目な開発などでは便利そう(やったこと無いが).
ラムダ式がない
ラムダ式とか関係なく関数の中に関数を書くことができる
func hoge() {
fuga := func () {
fmt.Println("fuga")
}
fmt.Println("hoge")
fuga()
var recurser func (T1, T2) T
recurser = func (v1 T1, v2 T2) {
return recurser(v1, v2)
}
}
カスタムソートが冗長
例えば,降順にするカスタムソートを作る場合,byLess
などといった名称で構造体を作り,そこに Len()
,Swap(i, j int)
,Less(i, j int)
のような関数を作り,sort.Sort(byLess(nums))
のように呼び出す.
C++であれば,sort(nums.begin(), nums.end(), [](int a, int b) {})
のように比較方法を指定することができ,数行で簡潔に書くことができるのに対し,冗長に感じた.
しかし,簡単な構造体に対するソートが冗長であるのに対して,複雑な構造体に対して複数のソートパターンを準備しておきたい開発では,こちらのほうが優位であるように感じる.byLess
や byFrequency
など,ひと目でわかりやすいソートを開発できそう.
抽象化という観点ではこちらのほうが良く,呼び出す行では簡潔になる印象.
三項演算子がない
悲しい.
が,複数人開発では助かりそう.(ネストされた三項演算子は苦手)
BFS が非常に冗長
queue
が無いのはかなりよいことだと思う一方で,list
が interface{}
型になっており,BFS の探索箇所を list
で管理しようとする.(Node)
など付ける必要があるようで,かなり複雑になって悲しい.
実際,Golang に慣れるために LeetCode を使わせてもらったが,木の探索系は C++に逃げたりもした.
暗黙的に true/false を選んでくれない
C++では暗黙的に true/false にしてくれていたところを Go では明示的にする必要がある.
動作をある程度把握していると文字数的にも見た目的にも簡潔になるため,好んで使っているのだが,それによって謎のバグが生じる可能性を考えると,一概に嫌なことではないのかもしれない.
だがやはり,nullptr
や int
型の 0 を false にしてくれないこと.
参照渡し(?)は呼び出し側で明記
C++であれば,関数の引数に&を付けると参照渡しになってくれるのに対し,Go では呼び出しの際に引数に&を付けるので,知らない間に変数の値が変わっていることがない(かも).
アロー演算子がない
記述が簡潔になる一方で,変数かポインタかを意識せずにメソッド等にアクセスできちゃう.
char 型がない
代わりに byte
型と rune
型を使う.
各種文法
Array 初期化
nums := []T{ n1, n2, n3}
strs := []string {""}
var strs []string
配列のサイズ
len(nums)
配列のマージ
append()
で第二引数をスプレッドしちゃう.
letters = append(letters, subletters...)
文字型とは?
uint8
のエイリアスらしい.int
型と同様,計算可能.
str[i] - '0'
空の map をつくる
mp := make(map[KeyT]ValT)
ポインタつくる
struct
からオブジェクト(?)を作る時に,右辺に&を付けるとポインタになってくれる.
ちなみに,ポインタを受け取る関数にポインタを渡す時も同様,&を付けるとよい.
構造体に自身のポインタを格納
木や双方向リストを実装する時には大抵,TreeNode*
とか Node*
とか作ると思うけど,そんな時は以下のようにするとよい.
type Node struct {
Val int
Prev *Node
Next *Node
}
// こんなふうにすると使いやすい
func NewNode(val int, prev, next, *Node) *Node {
retunr &Node{val, prev, next}
}
node := NewNode(2, node1, node2)
node := NewNode(1, nil, nil)
標準型
// 初期化
const keyval := map[keyType]valType { key1: val1, key2: val2, key3: val3,}
delete(keyval, key1) // 削除
if _, ok := m[key2]; ok {
// found
} else {
// not found
}
標準関数
ソート
# sort.Types(arr)の書式
sort.Ints(nums)
チップス
無理矢理ディープコピー
newletters := make([]string, len(letters))
copy(newletters, letters)
swap
node.Val, node.Next.Val = node.Next.Val, node.Val
ヌルチェック
if node == nil {
return nil
}
hashset の代用
st := make(map[int]bool)// insert
st[0] = true// erase
st[0] = false// contains
if st[0] == true {
// found
} else {
// not found
}
queue の代用
l := list.New()
for _, num := range nums {
l.PushBack(num)
}
for la.Len()>0 && lb.Len()>0 {
lfront := l.Front()
num := lfront.Value.(int32) // interface{}型であることに注意
fmt.Println(num)
l.Remove(lfront)
}
キャスト
num := int(fnum) // float->intは切り捨て
fnum = float64(num)
str := string(1 + '0') // "1"になる
小数点以下切り捨て
fnum = math.Floor(fnum)
スライス初期化
nums := make([]int, num_size, capacity)
スライス返す
return []int{0, 1, 2}
自作便利関数
abs_of()
func abs_of(num int)int {
if num>0 {
return num
} else {
return -num
}
}
sum_of(nums []int) int
func sum_of(nums []int) int {
sum_num := 0
for _, num := range nums {
sum_num += num
}
return sum_num
}
func sum_of(num1, num2 int) int {
return num1 + num2
}
bitmask(uint)uint
C++でも普通に使うような,ビットシフト演算
func bitmask(digit uint) uint {
return 1 << digit
}
checkbit(uint, uint) bool
func checkbit(num uint, digit uint) bool {
if num & bitmask(digit) != 0 {
return true
} else {
return false
}
}
所感
C++の std
ライブラリに含まれていて,重複しているもの(stack
,queue
,list
)が軒並み削られており,各人が利用する標準ライブラリが相違することは少なそうな印象.しかし,「C++のこれがない」となった時に,その中身を全く知らない場合は,代替となるものを見極めるのが難しそうな印象.
その一方で,http などの,boost
などのサードパーティライブラリに含まれているような処理が standard library になっているという面もあった.
また,C++の std
を使えば簡潔に書けるものが,結構長いコードになる面も感じていて,競技プログラミングには向かないのかもしれない.
しかし,エラーの扱い方が結構決まっており,アプリ開発などの用途においては非常に使いやすそう.さらに,
なので,LeetCode の木探索や AtCoder をやる時は C++を使い,アプリ開発などでは Golang を使う王と思った.
参考
- https://stackoverflow.com/questions/27055626concisely-deep-copy-a-slice
- https://pkg.go.devbuiltin#copy
- https://blog.golang.orgslices
- https://yourbasic.org/golang/last-item-in-slice
- https://pkg.go.devsort#Ints
- https://stackoverflow.com/questions/38571354/best-way-to-swap-variable-values-in-go38571485
- https://stackoverflow.com/questions/34018908golang-why-dont-we-have-a-set-datastructure
- https://pkg.go.dev/containerlist#section-sourcefiles
- https://cs.opensource.google/go/go/+/refs/tags/go1.16.7:src/container/listlist.go
- https://blog.golang.orgmaps
- https://groups.google.com/g/golang-nuts/c40vBKgflGjI
- https://play.golang.org/pi6-e4I7vih