Top Banner
JUS共催勉強会 なぜシェルに仕事をさせてはいけないのか? USP友の会 会員 鳥海秀一
63

2015.08.29 JUS共催勉強会資料

Jan 17, 2017

Download

Software

umidori
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: 2015.08.29 JUS共催勉強会資料

JUS共催勉強会なぜシェルに仕事をさせてはいけないのか?USP友の会 会員 鳥海秀一

Page 2: 2015.08.29 JUS共催勉強会資料

自己紹介

名前:鳥海秀一職業:金融系のSE

ここ2年間、業務ではプログラムを組んでません

所属:USP友の会(2009年4月~)

日本UNIXユーザ会(2014年12月~)

Page 3: 2015.08.29 JUS共催勉強会資料

この勉強会のきっかけ

2014年12月13日~14日開催JUS シェルスクリプトワークショップ in 鳥取

Page 4: 2015.08.29 JUS共催勉強会資料

シェルスクリプトワーックショップ IN 鳥取

講師

今泉光之(USP友の会)

斉藤博文(日本GNU AWKユーザ会)

斎藤明紀(鳥取環境大学)

Page 5: 2015.08.29 JUS共催勉強会資料

詳しくはWEBで

https://www.usptomo.com/PAGE=20141231JUSWORKSHOP

Page 6: 2015.08.29 JUS共催勉強会資料

斎藤先生が示したパネル

シェルに仕事をさせてはいけない

シェルは glue language

他のコマンドに仕事をさせる

シェルは仕事手順の制御だけ

Page 7: 2015.08.29 JUS共催勉強会資料

なぜ、シェルに仕事をさせてはいけないのか?

Page 8: 2015.08.29 JUS共催勉強会資料

実際に、仕事をさせてみよう!

Page 9: 2015.08.29 JUS共催勉強会資料

仕事?

Page 10: 2015.08.29 JUS共催勉強会資料

仕事=重めの処理

Page 11: 2015.08.29 JUS共催勉強会資料

本日は、nクイーン問題を選択

Page 12: 2015.08.29 JUS共催勉強会資料

nクイーン問題とは

もともとは1848年に考案された、チェス盤(8×8マス)と8個のクイーン(飛車と角行を合わせた動きをする駒)を利用したパズル

どのクイーンもお互いのきき筋にいないようにする配置を求める

8クーイン問題をn×nのマスに応用したのがnクイーン問題

2クイーンと3クイーンには解がない

nが増えると計算量が爆発的に増える。解が判明しているのは26クイーン問題まで

Page 13: 2015.08.29 JUS共催勉強会資料

nクイーン問題の解の表現方法

(例)4クイーン

2 4 1 3↩️3 1 4 2↩️ と数の列で表現

Page 14: 2015.08.29 JUS共催勉強会資料

nクイーン問題の解法

nクイーン問題はバックトラック法で解く問題の代表例

解法はプログラムの入門書に必ずと言って良いほど登場

Page 15: 2015.08.29 JUS共催勉強会資料

バックトラック法の概説

0 1 2 3

1

2

3

4

再帰

ループ

メモ行上斜め下斜め

1

1

1

3

4

2

Page 16: 2015.08.29 JUS共催勉強会資料

プログラム例(JavaScript)

function nqueens(size) { var row=[], up=[], down=[], board=[]; (function queen(n) { if (n >= size) { console.log(board.join(' ‘));

} else { for (var i = 0; i < size; ++i) { if (!row[i] && !up[n+i] && !down[n-i+size-1]) { row[i]=up[n+i]=down[n-i+size-1]=1; board[n] = i+1; queen(n+1); row[i]=up[n+i]=down[n-i+size-1]=0;

} }

} }(0));

} nqueens(process.argv[2]);

Page 17: 2015.08.29 JUS共催勉強会資料

様々な言語のプログラム例

https://github.com/umidori/nqueens/tree/master/procedural

Page 18: 2015.08.29 JUS共催勉強会資料

例を参考にBashでnクイーン問題の解法プログラムを作成してください

Page 19: 2015.08.29 JUS共催勉強会資料

実演してみます

Page 20: 2015.08.29 JUS共催勉強会資料

プログラムの作成方針

1. 4クイーン問題を解くプログラムを作成

2. 4クイーン問題をnクイーン問題に拡張

全てのクイーンの組みを出力するプログラムを作成

同一行のクイーンを排除

斜め上のクイーンを排除

斜め下のクイーンを排除

Page 21: 2015.08.29 JUS共催勉強会資料

4クイーンの全ての組を出力board=() queen() { local i

if (($1 >= 4)); then echo ${board[@]} else for ((i = 1; i <= 4; ++i)); do board[$1]=$i queen $(($1 + 1)) done fi } queen 0

Page 22: 2015.08.29 JUS共催勉強会資料

同一行のクイーンを排除board=() row=() queen() { local i

if (($1 >= 4)); then echo ${board[@]} else for ((i = 1; i <= 4; ++i)); do if ((!row[i])); then row[$i]=1 board[$1]=$i queen $(($1 + 1)) row[$i]=0 fi done fi } queen 0

Page 23: 2015.08.29 JUS共催勉強会資料

斜めのクイーンを排除board=() row=() up=() down=() queen() { local i if (($1 >= 4)); then echo ${board[@]} else for ((i = 1; i <= 4; ++i)); do if ((!row[i]&&!up[$1+i]&&!down[$1-i+4])); then ((row[$i] = up[$1+i] = down[$1-i+4] = 1)) board[$1]=$i queen $(($1 + 1)) ((row[$i] = up[$1+i] = down[$1-i+4] = 0)) fi done fi } queen 0

Page 24: 2015.08.29 JUS共催勉強会資料

nクイーン問題に拡張n=$1 board=() row=() up=() down=() queen() { local i if (($1 >= n)); then echo ${board[@]} else for ((i = 1; i <= n; ++i)); do if ((!row[i]&&!up[$1+i]&&!down[$1-i+n])); then ((row[$i] = up[$1+i] = down[$1-i+n] = 1)) board[$1]=$i queen $(($1 + 1)) ((row[$i] = up[$1+i] = down[$1-i+n] = 0)) fi done fi } queen 0

Page 25: 2015.08.29 JUS共催勉強会資料

12クイーンを解いてみてください

Page 26: 2015.08.29 JUS共催勉強会資料

言語別12クイーン解答時間(参考値)(Mac Mini CPU:2.3GHz Core i7 メモリ:8GB)

言語 処理時間C言語 0.14秒Java8 1.12秒C# 0.55秒

Haskell 15.0秒Ocaml 0.14秒

Perl 2.59秒Python 2.48秒Ruby 1.94秒PHP 3.08秒

JavaScript 2.11秒Gauche 1.21秒

Bash 6分24秒

Page 27: 2015.08.29 JUS共催勉強会資料

なぜシェルに仕事をさせてはいけないのか?

1.処理速度が遅い

2.スクリプトが組みづらい(環境からの支援が乏しい)

Page 28: 2015.08.29 JUS共催勉強会資料

シェルが遅いのはなぜか

答え 変数のアクセス効率が良くないから

1. 変数は全て「変数名=値」という文字列

2. 配列は高速なランダムアクセスを実現しない

Page 29: 2015.08.29 JUS共催勉強会資料

では、どうするか?

Page 30: 2015.08.29 JUS共催勉強会資料

斎藤先生が示したパネル

シェルに仕事をさせてはいけない

シェルは glue language

他のコマンドに仕事をさせる

シェルは仕事手順の制御だけ

Page 31: 2015.08.29 JUS共催勉強会資料

シェルは glue language

シェルスクリプトでは、glue(のり)は主にパイプのこと

シェルスクリプトではパイプをうまく使うことが肝要

パイプをうまく使うには発想の転換が必要

例えば、nクイーン問題では後戻りしないバックトラック法という発想が必要になる

Page 32: 2015.08.29 JUS共催勉強会資料

後戻りしないバックトラック法の概説

1

2

3

4

計算の進行

Page 33: 2015.08.29 JUS共催勉強会資料

AWKとBashで作成してみます

Page 34: 2015.08.29 JUS共催勉強会資料

プログラムの作成方針

1. 4クイーン問題を解くプログラムを作成

2. 4クイーン問題をnクイーン問題に拡張

全てのクイーンの組みを出力するプログラムを作成

同一行のクイーンを排除

斜め上のクイーンを排除

斜め下のクイーンを排除

Page 35: 2015.08.29 JUS共催勉強会資料

4クイーンの全ての組を出力f() { awk '{for(i=1;i<=4;++i)print $0,i}'; }

echo | f | f | f | f

Page 36: 2015.08.29 JUS共催勉強会資料

同一行のクイーンを排除f() { awk '{for(i=1;i<=4;++i)print $0,i}'; } g() { awk '{for(i=1;i<NF;++i)if($i==$NF)next;print}'; }

echo | f | g | f | g | f | g | f | g

Page 37: 2015.08.29 JUS共催勉強会資料

斜めのクイーンを排除f() { awk '{for(i=1;i<=4;++i)print $0,i}'; } g() { awk '{for(i=1;i<NF;++i)if($i==$NF)next;print}'; } h() { awk '{for(i=1;i<NF;++i)if($i+i==$NF+NF)next;print}'; } i() { awk '{for(i=1;i<NF;++i)if($i-i==$NF-NF)next;print}'; }

echo | f | g | h | i | f | g | h | i | f | g | h | i | f | g | h | i

Page 38: 2015.08.29 JUS共催勉強会資料

関数をまとめるf() { awk '{for(i=1;i<=4;++i)print $0,i}' | awk '{for(i=1;i<NF;++i)if($i==$NF)next;print}' | awk '{for(i=1;i<NF;++i)if($i+i==$NF+NF)next;print}' | awk '{for(i=1;i<NF;++i)if($i-i==$NF-NF)next;print}' }

echo | f | f | f | f

Page 39: 2015.08.29 JUS共催勉強会資料

AWKプログラムをまとめる

f() { awk '{for(i=1;i<=4;++i)print $0,i}' | awk '{for(i=1;i<NF;++i)if($i==$NF||$i+i==$NF+NF||$i-i==$NF-NF)next;print}' }

echo | f | f | f | f

Page 40: 2015.08.29 JUS共催勉強会資料

さらにまとめるf() { awk '{for(i=1;i<=4;++i){for(j=1;j<=NF;++j)if($j==i||$j+j==i+NF+1||$j-j==i-NF-1)break;if(j>NF)print $0,i}}' }

echo | f | f | f | f

Page 41: 2015.08.29 JUS共催勉強会資料

nクイーンへの拡張法

1. ループ文を使用して、繰り返しを実現する

1. 変数に中間結果を格納する

2. ファイルに中間結果を格納する

2. 再帰を使用して、繰り返しを実現する

1. パイプに中間結果を通す

Page 42: 2015.08.29 JUS共催勉強会資料

変数に中間結果を格納する場合

n=$1

f() { … }

tmp="" for ((i=0;i<n;++i)); do tmp=$(echo "$tmp" | f) done echo "$tmp"

Page 43: 2015.08.29 JUS共催勉強会資料

ファイルに中間結果を格納する場合

n=$1

f() { … }

echo >tmp for ((i=0;i<n;++i)); do (rm tmp; f >tmp) <tmp done cat tmp rm tmp

Page 44: 2015.08.29 JUS共催勉強会資料

パイプに中間結果を通す場合

n=$1

f() { if (($1==0)); then echo else f $(($1-1)) | … fi }

f $1

Page 45: 2015.08.29 JUS共催勉強会資料

パイプを使ってnクイーンに拡張

n=$1 queen() { if (($1 == 0)); then echo else queen $(($1 - 1)) | awk '{for(i=1;i<='$n';++i){for(j=1;j<=NF;++j)if($j==i||$j+j==i+NF+1||$j-j==i-NF-1)break;if(j>NF)print $0,i}}' fi } queen $1

Page 46: 2015.08.29 JUS共催勉強会資料

12クイーンを解いてみてください

Page 47: 2015.08.29 JUS共催勉強会資料

パイプを使ったシェルスクリプトの特徴

1. 利点

1. 処理速度が速い

2. スクリプトが組みやすい

2. 欠点

1. プロセス数を意識する必要がある

2. 手続き的な発想とは異なる発想を必要とする

Page 48: 2015.08.29 JUS共催勉強会資料

パイプを使うスクリプトの発想

パイプを使うには手続き型プログラミングの発想ではなく関数型プログラミングの発想が必要となる

Page 49: 2015.08.29 JUS共催勉強会資料

SICPに載っているLISPによるnクイーンの解法

(define (queens board-size) (define (queen-cols k) (if (= k 0) (list empty-board) (filter↲ (lambda (positions) (safe? k positions)) (flatmap (lambda (rest-of-queens) (map (lambda (new-row) (adjoin-position new-row k rest-of-queens)) (enumerate-interval 1 board-size))) (queen-cols (- k 1)))))) (queen-cols board-size))

Page 50: 2015.08.29 JUS共催勉強会資料

関数が第一級オブジェクト参照透過性遅延評価カリー化並列化

関数型プログラム言語とパイプを使うシェルスクリプトとの共通点は?

閉包性の活用

Page 51: 2015.08.29 JUS共催勉強会資料

閉包性とは

抽象代数からきた言葉

集合の要素にある演算を作用させて得られた要素が、また集合の要素であるなら、要素の集合はその演算のもとで閉じているという

例えば、整数は加算、減算、乗算に対しては閉包性をもつ。整数に対するいずれの演算も、結果は整数になるため

Page 52: 2015.08.29 JUS共催勉強会資料

SICPでの閉包性の説明

困ったことに多くの普通のプログラム言語にあるデータ組合せ演算は閉包性を満足せず、閉包性を活用するのは煩わしい。(中略)対の使えるLispと違って、これらの言語には合成データを一様に操作するのが容易になる汎用の糊はない。

Page 53: 2015.08.29 JUS共催勉強会資料

閉包性を活用する言語の例

Lispリストに対するmap,filter,flattenなどの演算

SQLリレーションに対する8つのリレーショナル代数演算

シェルテキストに対するhead,tail,sortなどのコマンド

Page 54: 2015.08.29 JUS共催勉強会資料

関数型言語の発想に慣れるには

梅手続き的プログラムを関数的プログラムに書き換えてみる

SICP(計算機プログラムの構造と解釈)を読む

シェル芸勉強会に参加する

Page 55: 2015.08.29 JUS共催勉強会資料

関数型言語としてのシェルの利点

開発環境開発環境はターミナル画面であるため超軽量

処理速度パイプを利用し、関数的に解いた方が速度的に有利

記述順と実行順の一致シェルスクリプトでは記述順と実行順が一致する

Page 56: 2015.08.29 JUS共催勉強会資料

言語別12クイーン解答時間(参考値)(Mac Mini CPU:2.3GHz Core i7 メモリ:8GB)

言語 手続き的プログラム 関数的プログラムC言語 0.14秒 ーJava8 1.12秒 2.78秒C# 0.55秒 2.17秒

Haskell 15.0秒 5.36秒Ocaml 0.14秒 3.53秒

Perl 2.59秒 34.4秒Python 2.48秒 11.9秒Ruby 1.94秒 17.0秒PHP 3.08秒 23分03秒

JavaScript 2.11秒 16分35秒Gauche 1.21秒 29.8秒

Bash 6分24秒 11.0秒

Page 57: 2015.08.29 JUS共催勉強会資料

シェルでは記述順と処理順が一致

普通、関数的なプログラムでは右から左に処理が進む

Page 58: 2015.08.29 JUS共催勉強会資料

Perlを使った4クイーンの解答例(右(この場合は下)から左(この場合は上)に処理が進行)

print map{join(" ",@$_)."\n"} map{$l=scalar(@x=@$_);map{[@x,$_]}grep{$y=$_;!grep{$y==$x[$_]||$l-$_==abs($y-$x[$_])}0..$l-1}1..4} map{$l=scalar(@x=@$_);map{[@x,$_]}grep{$y=$_;!grep{$y==$x[$_]||$l-$_==abs($y-$x[$_])}0..$l-1}1..4} map{$l=scalar(@x=@$_);map{[@x,$_]}grep{$y=$_;!grep{$y==$x[$_]||$l-$_==abs($y-$x[$_])}0..$l-1}1..4} map{$l=scalar(@x=@$_);map{[@x,$_]}grep{$y=$_;!grep{$y==$x[$_]||$l-$_==abs($y-$x[$_])}0..$l-1}1..4} []

Page 59: 2015.08.29 JUS共催勉強会資料

Bashを使った4クイーンの解答例(左(この場合は上)から右(この場合は下)に処理が進行)

echo | awk '{for(i=1;i<=4;++i){for(j=1;j<=NF;++j)if($j==i||$j+j==i+NF+1||$j-j==i-NF-1)break;if(j>NF)print $0,i}}' | awk '{for(i=1;i<=4;++i){for(j=1;j<=NF;++j)if($j==i||$j+j==i+NF+1||$j-j==i-NF-1)break;if(j>NF)print $0,i}}' | awk '{for(i=1;i<=4;++i){for(j=1;j<=NF;++j)if($j==i||$j+j==i+NF+1||$j-j==i-NF-1)break;if(j>NF)print $0,i}}' | awk '{for(i=1;i<=4;++i){for(j=1;j<=NF;++j)if($j==i||$j+j==i+NF+1||$j-j==i-NF-1)break;if(j>NF)print $0,i}}'

Page 60: 2015.08.29 JUS共催勉強会資料

まとめ

シェルに仕事をさせてはいけない

仕事をさせる場合はパイプを使う

パイプを使いこなすには発想の転換が必要

発想の転換にはシェル芸勉強会が最適

Page 61: 2015.08.29 JUS共催勉強会資料

ここで、シェル芸問題をひとつ

nクイーンの数列(例えば、2 4 1 3)を次のようなアスキーアートに変換してみましょう。+---+---+---+---+ | | | Q | | +---+---+---+---+ | Q | | | | +---+---+---+---+ | | | | Q | +---+---+---+---+ | | Q | | | +---+---+---+---+

Page 62: 2015.08.29 JUS共催勉強会資料

解答例1

awk '{ printf "+";for(i=1;i<=NF;++i)printf "---+";print ""; for(i=1;i<=NF;++i){printf "|";for(j=1;j<=NF;++j) if(i==$j) printf " Q |"; else printf " |";print ""; printf "+";for(j=1;j<=NF;++j)printf "---+";print ""}}'

Page 63: 2015.08.29 JUS共催勉強会資料

解答例2

while read i; do a=($i); printf $(printf "+%0${#a[@]}s\\\n%${#a[@]}s\n" | sed "s/ /|%${#a[@]}s\\\n+%0${#a[@]}s\\\n/g") | sed "s/0/---+/g;s/ / |/g" | eval sed "$(eval echo {1..${#a[@]}} | sed 's#[0-9][0-9]*# -e "$((${a[&-1]}*2))s/...|/ Q |/&"#g')"; done