ys memos
Blog

go勉強メモ


go

2021/08/18

はじめに

そろそろ Golang へ入門したいと思い,LeetCode を通して Golang を触りの部分ですが,はじめてみました.

これまでは LeetCode は C++で解いていたため,C++との差に対して過度に反応しています.

同じく C++を触っていて,Golang を始める方にとっては読み物としてお役に立つかもしれません.よかったら作業の脇に置いてみてください.

そもそも

三項演算子がない

if-else で対応

declared but not used がデフォルト

変数を使っていないと怒ってくれる.使わない戻り値は_対応.

クラスがない

struct とメソッドで対応.(個人的に,クラスをなくしたのは英断だと思う)

自分で簡単なライブラリを作る時は,横着して class 内に実装を書いてしまうので,それを強制的に回避させられているので,今までの横着さを反省した.

命名規則の強制

struct にメソッドを追加する際,大文字スタートであればパブリック,小文字スタートであればプライベートのように,命名規則で扱いが変わる.

range-loop 便利

C++とは違い,インデックスとイテレータを同時に取ることができる

range-loop
for i, v := range nums {
	//TODO: 要らない方は_で対応
}

hashset がない

map で対応

queue がない

list で対応

bitset 型がない

C++の bitset よくわかってなかったので,助かった.

uint で対応

自動で複数の戻り値を tie()してくれる

C++で複数の戻り値を受ける場合,関数の戻り値を std::tuple<T1,T2>()としつつ,std::tie()をする必要があるが,Go では自動で受け取ってくれる.必要ない値を受け取らなくてはならない時,_で対応.

error 型が帰ってくるのが,競プロなどでは不要だが,真面目な開発などでは便利そう(やったこと無いが).

ラムダ式がない

ラムダ式とか関係なく関数の中に関数を書くことができる

inner_func
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) {})のように比較方法を指定することができ,数行で簡潔に書くことができるのに対し,冗長に感じた.

しかし,簡単な構造体に対するソートが冗長であるのに対して,複雑な構造体に対して複数のソートパターンを準備しておきたい開発では,こちらのほうが優位であるように感じる.byLessbyFrequency など,ひと目でわかりやすいソートを開発できそう.

抽象化という観点ではこちらのほうが良く,呼び出す行では簡潔になる印象.

三項演算子がない

悲しい.

が,複数人開発では助かりそう.(ネストされた三項演算子は苦手)

BFS が非常に冗長

queue が無いのはかなりよいことだと思う一方で,listinterface{}型になっており,BFS の探索箇所を list で管理しようとする.(Node)など付ける必要があるようで,かなり複雑になって悲しい.

実際,Golang に慣れるために LeetCode を使わせてもらったが,木の探索系は C++に逃げたりもした.

暗黙的に true/false を選んでくれない

C++では暗黙的に true/false にしてくれていたところを Go では明示的にする必要がある.

動作をある程度把握していると文字数的にも見た目的にも簡潔になるため,好んで使っているのだが,それによって謎のバグが生じる可能性を考えると,一概に嫌なことではないのかもしれない.

だがやはり,nullptrint 型の 0 を false にしてくれないこと.

参照渡し(?)は呼び出し側で明記

C++であれば,関数の引数に&を付けると参照渡しになってくれるのに対し,Go では呼び出しの際に引数に&を付けるので,知らない間に変数の値が変わっていることがない(かも).

アロー演算子がない

記述が簡潔になる一方で,変数かポインタかを意識せずにメソッド等にアクセスできちゃう.

char 型がない

代わりに byte 型と rune 型を使う.

各種文法

Array 初期化

nums := []T{ n1, n2, n3}
strs := []string {""}
var strs []string

配列のサイズ

size_of_array
len(nums)

配列のマージ

append()で第二引数をスプレッドしちゃう.

letters = append(letters, subletters...)

文字型とは?

uint8 のエイリアスらしい.int 型と同様,計算可能.

ascii-calc
str[i] - '0'

空の map をつくる

create_map
mp := make(map[KeyT]ValT)

ポインタつくる

struct からオブジェクト(?)を作る時に,右辺に&を付けるとポインタになってくれる.

ちなみに,ポインタを受け取る関数にポインタを渡す時も同様,&を付けるとよい.

構造体に自身のポインタを格納

木や双方向リストを実装する時には大抵,TreeNode*とか Node*とか作ると思うけど,そんな時は以下のようにするとよい.

nested_struct
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)

標準型

map
// 初期化
const keyval := map[keyType]valType {	key1: val1,	key2: val2,	key3: val3,}
delete(keyval, key1) // 削除
if _, ok := m[key2]; ok {
	// found
} else {
	// not found
}

標準関数

ソート

sort
# sort.Types(arr)の書式
sort.Ints(nums)

チップス

無理矢理ディープコピー

newletters := make([]string, len(letters))
copy(newletters, letters)

swap

swap
node.Val, node.Next.Val = node.Next.Val, node.Val

ヌルチェック

check_null
if node == nil {
	return nil
}

hashset の代用

alt_hashset
st := make(map[int]bool)// insert
st[0] = true// erase
st[0] = false// contains
if st[0] == true {
	// found
} else {
	// not found
}

queue の代用

alt_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)
}

キャスト

cast
num := int(fnum) // float->intは切り捨て
fnum = float64(num)
str := string(1 + '0') // "1"になる

小数点以下切り捨て

cut
fnum = math.Floor(fnum)

スライス初期化

alt_vector
nums := make([]int, num_size, capacity)

スライス返す

return_slice
return []int{0, 1, 2}

自作便利関数

abs_of()

abs
func abs_of(num int)int {
	if num>0 {
		return num
	} else {
		return -num
	}
}

sum_of(nums []int) int

sum_of()
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++でも普通に使うような,ビットシフト演算

bitmask
func bitmask(digit uint) uint {
	return 1 << digit
}

checkbit(uint, uint) bool

checkbit
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 を使う王と思った.

参考

関連タグを探す