コピペコードで快適生活

明日使えるソースを自分のために

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


すっかりスルー気味でしたが、クロス集計用のコマンドがあります。 – 上田ブログ

初めの方のシェル芸勉強会メモ

初めの方の問題見たらとてもピュアだった。

2012年10月27日 Hbstudy#38

ファイルの一括変換

# カレントディレクトリの中から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

Uspstudy20121208qonly

条件でデータを抽出

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