Goことはじめ2
第2章 基本文法―覚えやすいコンパクトな言語仕様:はじめてのGo―シンプルな言語仕様,型システム,並行処理|gihyo.jp … 技術評論社
より学んだことメモ。
基本的な文法について
package main import ( "fmt" "os" "errors" "log" // f "fmt" -> と記述すると f.Println()のようにアクセスできる // . "strings" -> と記述すると ToUpper('xx') とパッケージ指定なしでアクセスできる ) // // 代入処理について // func substitute() { fmt.Println("substitute -------------------- ") // 変数代入 var message string = "hello world" fmt.Println(message) // 関数の中では型推論して代入もできる message2 := "hello world" fmt.Println(message2) // 定数定義 const Hello string = "Hello Const" // 変数を宣言して明示的に初期化しなかった場合は // ゼロ値で初期化される // 整数型: 0 // 浮動小数点型: 0.0 // bool: false // string: "" // 配列: 各要素がゼロ値の配列 // 構造体: 各フィールドがゼロ値の構造体 // そのほかの型: nil const message3 string = ` 複数行に渡る 文字列を使うときは バッククォートでくるる ` } // // 条件分岐・繰り返し // func repeat() { fmt.Println("repeat -------------------- ") // if文 Goでは丸かっこは不要 a, b := 10, 100 if a > b { fmt.Println("a is larger than b") } else if a < b { fmt.Println("a is smaller than b") } else { fmt.Println("a equals b") } // 繰り返しはfor文だけ n := 0 for n < 10 { fmt.Printf("n = %d\n", n) n++ } // 無限ループ・break・continue m := 0 for { m++ if m > 10 { break // ループを抜ける } if m%2 == 0 { continue // 偶数なら次の繰り返しに映る } fmt.Println(m) // 奇数のみ表示 } // switch文 // breakは書かなくていい p := 10 switch p { case 15: fmt.Println("FizzBuzz") case 10: fmt.Println("Buzz") case 3, 6, 9: // カンマ切りで複数指定可能 fmt.Println("Fizz") // そのまま下に処理をおろしたい場合は // fallthrough // と記述する(逆break) default: fmt.Println(p) } // if文と同じようにかける q := 10 switch { case (p % 15 == 0): fmt.Println("FizzBuzz") case (p % 5 == 0): fmt.Println("Buzz") default: fmt.Println(q) } } // // 関数 // // 引数の後に型を指定する func sum1(i, j int) { // func sum(i int, j int) と同じ fmt.Println(i + j) } // 戻り値がある場合は引数の次に指定する func sum2(i, j int) int { return i + j } // 関数は複数の値を返せる func swap(i, j int) (int, int) { return j, i } // 内部で発生したエラーは戻り値で表現する func errorFunc() { // _ で 第1戻り値を無視 _, err := os.Open("sample.txt") if err != nil { // エラー処理 fmt.Println("file open error") } fmt.Println("file open success") } // 自作のエラーはerrorsパッケージを用いて作ることができる func div(i, j int) (int, error) { if j == 0 { // 自作のエラーを返す return 0, errors.New("divied by zero") } return i / j, nil } // 名前付き戻り値 // 関数内ではゼロ値で初期化された変数として扱われる func divWithName(i, j int) (result int, err error) { if j == 0 { err = errors.New("divied by zero") return // return 0, err と同じ } result = i / j return // return result, nil と同じ } // 関数はオブジェクト func wrapFunc() { // 関数リテラル(無名関数) // 即時実行される func(i, j int) { fmt.Println(i + j) }(2, 4) // 関数は変数に代入できる var sum func(i, j int) = func(i, j int) { fmt.Println(i + j) } sum(2, 3) } // // 配列 // func arrayFunc() { fmt.Println("arrayFunc -------------------- ") // Goの配列は固定長 var arr [4]string arr[0] = "a" arr[1] = "b" arr[2] = "c" arr[3] = "d" fmt.Println(arr[0]) // 宣言と代入 arr2 := [4]string{"a", "b", "c", "d"} fmt.Println(arr2) // 暗黙的なサイズ指定 arr3 := [...]string{"a", "b", "c", "d"} fmt.Println(arr3) // 長さの違う配列は別の型 // 関数に配列を渡す場合は"値渡し"になる } // // スライス // スライスは,可変長配列として扱うことができます。 // 配列を直接使うのは,シビアなメモリ管理が必要な一部のプログラムだけなので, // 基本的にはスライスを用います。 // func sliceFunc() { fmt.Println("sliceFunc -------------------- ") // スライスの型には長さの情報はない var s []string // 追加 s = append(s, "a") // 追加した要素を返す s = append(s, "b") s = append(s, "c", "d") fmt.Println(s) // [a b c d] // 結合 s1 := []string{"a", "b"} s2 := []string{"c", "d"} s1 = append(s1, s2...) // s1にs2を追加 fmt.Println(s1) // [a b c d] // 繰り返し処理 // rangeは配列,スライス,string,マップに対して使うことができる s11 := []string{"a", "b", "c", "d"} for i, v := range s11 { // i = 添字, v = 値 fmt.Println(i, v) } // 値の切り出し s21 := []int{1, 2, 3, 4, 5} fmt.Println(s21[2:4]) // [2 3] fmt.Println(s21[0:len(s)]) // [0 1 2 3 4 5] fmt.Println(s21[:3]) // [0 1 2] fmt.Println(s21[3:]) // [3 4 5] fmt.Println(s21[:]) // [0 1 2 3 4 5] // 可変長引数 var _sum func(nums ...int) (result int) = func(nums ...int) (result int) { // numsは []int型 for _, n := range nums { result += n } return } fmt.Println(_sum(1, 2, 3, 4)) // 10 } // // マップ // マップは値をKey-Valueの対応で保存するデータ構造 // func mapFunc() { fmt.Println("mapFunc -------------------- ") // 宣言 key=int, value=string var month map[int]string = map[int]string{} month[1] = "January" month[2] = "February" fmt.Println(month) // 宣言と初期化を一緒にする場合 month2 := map[int]string { 1: "January", 2: "February", } fmt.Println(month2) // 値を取り出す jan := month[1] fmt.Println(jan) // マップにキーが存在するかチェック _, ok := month[1] if ok { fmt.Println("month[1] is present") } // 要素の削除 delete(month, 1) // 繰り返し処理 // 処理順番は保証されない for key, value := range month { fmt.Printf("%d %s\n", key, value) } } // // ポインタ // ポインタを使うことで参照渡しができる // func callByValue(i int) { i = 20; // 値を上書きする } func callByRef(i *int) { *i = 20; // 参照先を上書きする } // // 関数を抜けるときに必ず実行 // func deferFunc() { fmt.Println("deferFunc -------------------- ") file, err := os.Open("./error.go") if err != nil { fmt.Println("FileOpenError") } // 関数を抜ける前に必ず実行される defer file.Close() // 正常処理 fmt.Println("deferFuncProcess") } // // パニックと後処理 // func panicFunc() { fmt.Println("PanicFunc -------------------- ") // 後処理 defer func() { // パニックで発生したエラーはrecoverで取得できる err := recover() if err != nil { log.Print(err) } }() a := []int{1, 2, 3} fmt.Println(a[10]) // パニック発生 // 自前でパニックを起こすこともできる // panic(errors.New("index out of range")) } func main() { substitute() repeat() fmt.Println("Function -------------------- ") sum1(1, 2) s := sum2(2, 3) fmt.Println(s) x, y := swap(3, 4) fmt.Println(x, y) errorFunc() _, err := div(10, 0) if err != nil { log.Print(err) // Fatalを使用するとステータス=1で終了する // log.Fatal(err) } m, err := divWithName(10, 2) fmt.Println(m) wrapFunc() arrayFunc() sliceFunc() mapFunc() fmt.Println("Pointer -------------------- ") var i int = 10; callByValue(i) // 値渡し fmt.Println(i) // 10 callByRef(&i) // 参照渡し fmt.Println(i) // 20 deferFunc() panicFunc() }
シェルのtestコマンド
シェルのtestコマンドの理解が怪しかったので、使い方をメモしておく。
#!bin/sh # $?==0 で正常終了 # 数値の比較 ## 1 == 1 test 1 -eq 1; echo $? ## 1 != 2 test 1 -ne 2; echo $? ## 1 < 2 test 1 -lt 2; echo $? ## 2 > 1 test 2 -gt 1; echo $? ## 1 <= 1 test 1 -le 1; echo $? ## 1 >= 1 test 1 -ge 1; echo $? # 文字列の比較 ## "abc" == "abc" test "abc" = "abc"; echo $? ## "abc" != "aaa" test "abc" != "aaa"; echo $? # 空チェック ## 変数に値が入っているか test -n HOGE; echo $? ## 変数が空か test -z HOGE; echo $? # ファイルチェック ## file2が新しいか touch file1 touch file2 test ./file1 -ot ./file2 ## fileが存在するか touch file3 test -f ./file3; echo $? ## fileが存在しない test ! -f ./file4; echo $? ## ディレクトリが存在するか mkdir dir1 test -d ./dir1; echo $? # 略式 [ ] # [ は testコマンド # ] は [を使うためのオプション [ 1 -lt 2 ]; echo $? # AND条件 OR条件 [ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ]; echo $? # if文 ## 一般的な書き方 if [ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ]; then echo "if文の中の実行" fi ## これでもOK if [ 1 -eq 1 ] && [ 1 -lt 2 ] || [ 2 -eq 2 ] then echo "if文の中の実行" fi
Goことはじめ
http://gihyo.jp/dev/feature/01/go_4beginners で学んだことをメモしていく。
はじめに
# インストール brew install go # ./bash_profileの設定 export GOROOT=/usr/local/opt/go/libexec export GOPATH=$HOME export PATH=$PATH:$GOROOT/bin:$GOPATH/bin # ソースコードから実行 go run hello.go # コンパイルして実行 go build hello.go ./hello # コーディング規約に合わせてフォーマット go fmt hello.go # パッケージ(ここではfmt)のドキュメントを表示 go doc fmt
プロジェクトを作る
# プロジェクトディレクトリ基本形 # Goのコマンドは,このGOPATHとその下にある先の3つのディレクトリの命名規則を用いて,Makefileなどの構成ファイルは一切なしで,依存関係を解決してビルドできるようになっている。 tree hello/ ├── bin ├── pkg └── src # プロジェクトディレクトリにパスを通す cd hello export GOPATH=`pwd` # helloをGOPATHに登録 # ソースを書く1 cat src/hello/hello.go # --- package hello var Message string = "hello world" # ソースを書く2 cat src/main/main.go # --- package main import ( "fmt" "hello" ) func main() { fmt.Println(hello.Message) // hello world } # 実行してみる cd $GOPATH/src/main go run main.go hello world # 実行ファイルをbin以下に作る cd $GOPATH/src/main go install # binにパスを通しておくと、すぐに実行できて便利 export PATH=$PATH:$GOPATH/bin main hello world
ActiveRecord::Base.transactionで並列で同じレコードを扱ったときの動きメモ
排他制御まわりがちゃんとわかっていなかったので整理。
Article.find(1).value # => 1 def inc row = Article.find(1) row.value = row.value + 1 row.save end # 同時並行処理する Thread.new { inc } Thread.new { inc } Article.find(1).value # => 2になるか,3になるかは神のみぞ知る
だけど、
Article.find(1).value # => 1 def inc ActiveRecord::Base.transaction do row = Article.find(1) row.value = row.value + 1 row.save end end # 同時並行処理する Thread.new { inc } Thread.new { inc } Article.find(1).value # => これなら3になるよね!たぶん
こうするとうまくいくはず。
検証
Article.find(1).value # => 1 def inc(wait1 = 0, wait2 = 0) sleep wait1 if wait1 > 0 ActiveRecord::Base.transaction do row = Article.find(1) row.value = row.value + 1 sleep wait2 if wait2 > 0 row.save! end end # 同時並行処理する Thread.new { inc(0, 10) } # ... t1 Thread.new { inc(5, 0) } # ... t2 # t1.transaction -> t1.find -> t2.transaction(待ちになる) -> # t1.save -> t1.commit -> t2.find -> t2.save -> t2.commit # になる想定。 Article.find(1).value # => 3にならず、2になってしまった...
だめじゃん。
検証2
悲観的ロックをかける
https://qiita.com/upinetree/items/b3329501561268f7678a
Article.find(1).value # => 1 def inc(wait1 = 0, wait2 = 0) sleep wait1 if wait1 > 0 ActiveRecord::Base.transaction do # SELECT * FROM "articles" FOR UPDATE row = Article.all.lock.find(1) row.value = row.value + 1 sleep wait2 if wait2 > 0 row.save! end end # 同時並行処理する Thread.new { inc(0, 10) } # ... t1 Thread.new { inc(5, 0) } # ... t2 # t1.transaction -> t1.find -> t2.transaction(待ちになる) -> # t1.save -> t1.commit -> t2.find -> t2.save -> t2.commit # になる想定。 Article.find(1).value # => 3になった!
これだとうまくいった。
最後に
トランザクションについて
トランザクションは、完全に排他してくれるわけではなく、あくまで、複数テーブルのUPDATEで「すべて更新される」か「すべて更新されないか」を保証するだけのもの。
トランザクション内でロックがかかった場合は、COMMITかROLLBACKされるまでロックは維持される。
DBレベルのロックについて(PostgreSQLの場合)
【テーブルレベルのロック】
ROW EXCLUSIVE
UPDATE、DELETE、およびINSERTコマンドは、(参照される他の全てのテーブルに対するACCESS SHAREロックに加えて)対象となるテーブル上にこのモードのロックを獲得します。 通常、このロックモードは、テーブルのデータを変更する問い合わせにより獲得されます。
→ つまり何も指定せずに、INSERT、UPDATE、DELETEすると、ROW EXCLUSIVEが自動でかかる。
【行レベルのロック】
FOR UPDATE
FOR UPDATEによりSELECT文により取り出された行が更新用であるかのようにロックされます。
→ 基本的にSELECTではロックはかからないけど、かけたければFOR UPDATE使う。
https://www.postgresql.jp/document/9.4/html/explicit-locking.html より
ロックについて
ロックには種類がある。
【楽観的ロック】
アプリケーションレベルのロック。
更新する直前に、対象レコードが取得時と変わっていないかを確認する。
【悲観的ロック】
DBレベルのロック。
レコード取得した時点で、対象レコードにロックをかける。
SELECT … FOR UPDATE を利用したもの。
→ 今回話しているのはこっちのロックの方。
補足
RedisObject使っているとクラスメソッドのlockが上書きされちゃうようなので、Article.lock... ではなく Article.all.lock... としたほうが無難っぽい。
https://github.com/nateware/redis-objects/blob/master/lib/redis/lock.rb#L36
シェルでクロス集計するメモ
クロス集計をBash(とawk)だけで実装した話 より学んだことメモ
処理の流れ
1.必要なカラムの抽出
2.クロス集計の行と列の要素を取り出す。
# クロス集計表の行になる部分を抽出してユニーク化&ソート cat table.csv | cut -f 1 -d , | sort -u > rowname.txt # クロス集計の列になる部分を抽出、改行をタブに変えて縦横変換 cat table.csv | cut -f 2 -d , | sort -u | gtr '¥n' '¥t' > header.txt # cutコマンド補足 cut -f {取り出したい項目} -d {区切り文字}
3.awkでファイル分割する。
cat table.csv a,ice,130 a,ice,180 b,juice,120 b,ice,130 i,oreo,210 i,oreo,210 i,oreo,210 # awkで分割した結果をファイル出力できる cat table.csv | gawk -F, '{print > "split_"$2}' ls split_ice split_juice split_oreo table.csv
4.awkで集計する
5.sortでソート
ls | grep "split_" | gsed -e 's/split_//' | while read file; do gawk -F, '{a[$1]+=$3;}END{for(i in a)print i","a[i];}' split_${file} | sort -k 1 > sort_${file}; done
とか
gawk '{print FILENAME","$0}' split_* | gsed 's/split_//' | gawk -F, '{a[$1","$2]+=$4}END{for(i in a)print i","a[i];}' | sort -t, -k1,2 | gawk -F, '{print $2","$3 > "sort_"$1}'
6.2で取り出した行の要素と5の結果をJOIN
# joinできなかったキーも含めて出力し、空行は0で埋める ls | grep sort_ | gsed 's/sort_//' | while read file; do join -t ',' -1 1 -2 1 -a 1 rowname.txt sort_${file} | gawk -F, '{print $2}' | gsed 's/^$/0/g' > split_sum_${file}; done cat split_sum_juice 0 120 0 # joinコマンドメモ # -t 区切り文字を指定 # -1 1番目のファイルで結合キーに使う項目番号 # -2 2番目のファイルで結合キーに使う項目番号 # -a 1 LEFT JOIN (未指定の場合はINNNER JOIN) # -a 2 RIGHT JOIN join -t ',' -1 1 -2 1 -a 1 rowname.text sort_${file}
7.paste と cat でマージしてリストを表に加工
# pasteで列同士を結合する cp header.txt sum_result.txt paste rowname.txt split_sum_* >> sum_result.txt
初めの方のシェル芸勉強会メモ
初めの方の問題見たらとてもピュアだった。
ファイルの一括変換
# カレントディレクトリの中からUtilの文字をHogeに変えてDesktopに保存する。 # ※basenameはディレクトリ名を除外したファイル名を返すコマンド grep -r "Util" ./. 2> /dev/null | gsed 's/:..*$//' | while read f; do gsed 's/Util/Hoge/' $f> ~/Desktop/$(basename $f); done
集計
# 数値範囲でグルーピングして集計する cat /dev/urandom | gtr -dc 0-9 | fold -w 2 | gsed 's/^0//' | head -n 100000 > ages cat ages | gawk '{print int($1 / 10)}' | sort -n | uniq -c | gawk '{print $2*10"-"$2*10+9,$1}' 0-9 9961 10-19 9805 20-29 9813 30-39 10137 40-49 10026 50-59 10198 60-69 10144 70-79 9888 80-89 10025 90-99 10003
条件でデータを抽出
cat hoge a 12 a 13 b 13 a 432 b 111 b 43 cat hoge | gawk '{if(a[$1] < $2){a[$1] = $2}}END{for(i in a){print i, a[i]}}' # awkで配列の中身を出力 for(i in array) { print i, a[i] }
条件でデータを集計
cat num2 a 1 b 2 3 4 a 5 6 b 7 8 9 10 cat num2 | gawk '{for(i=2; i<=NF; i++){a[$1]+=$i}}END{for(i in a){print i,a[i]}}' a 12 b 43
曜日ごとの集計
01/01の曜日を各年ごとに集計
seq 1990 2020 | gawk '{print $1 "0101"}' | while read i; do gdate -d $i '+%w'; done | sort | uniq -c | gawk '{print $2, $1}' # 曜日を算出(0が日曜日) gdate -d YYYYMMDD '+%w'
ファイルの比較
# file2から、file1にない要素を取り出す diff <(sort file1.txt) <(sort file2.txt) | grep '>' | gsed -e 's/> //' # 標準入力同士の diff diff <(command1) <(command2)
RubyのThreadメモ
まずは超簡単なところから。
n = 1 # この記述に至った時点で非同期で実行開始される t1 = Thread.new do puts "t1 start" puts "t1 n is #{n}" n = 2 sleep 5 puts "t1 end" end t2 = Thread.new do puts "t2 start" sleep 5 puts "t2 n is #{n}" # => 2 スレッド間で変数は共有される puts "t2 end" end # joinメソッドを呼ぶと、 # スレッドの処理が終了するまで以下の処理が実行されない t1.join t2.join