Successfully reported this slideshow.                                                               Upcoming SlideShare
×

# 2015.08.29 JUS共催勉強会資料

842 views

Published on

2015.08.29に行ったUSP友の会と日本ユニッックスユーザ会（JUS）共催の勉強会の資料。「なぜシェルに仕事をさせてはいけないのか？」を解説。

Published in: Software
• Full Name
Comment goes here.

Are you sure you want to Yes No • Be the first to comment

### 2015.08.29 JUS共催勉強会資料

1. 1. JUS共催勉強会 なぜシェルに仕事をさせてはいけないのか？ USP友の会 会員 鳥海秀一
2. 2. 自己紹介 名前：鳥海秀一 職業：金融系のSE ここ2年間、業務ではプログラムを組んでません 所属： USP友の会（2009年4月∼） 日本UNIXユーザ会（2014年12月∼）
3. 3. この勉強会のきっかけ 2014年12月13日∼14日開催  JUS シェルスクリプトワークショップ in 鳥取
4. 4. シェルスクリプトワーックショップ IN 鳥取 講師 今泉光之（USP友の会） 斉藤博文（日本GNU AWKユーザ会） 斎藤明紀（鳥取環境大学）
5. 5. 詳しくはWEBで https://www.usptomo.com/ PAGE=20141231JUSWORKSHOP
6. 6. 斎藤先生が示したパネル シェルに仕事をさせてはいけない シェルは glue language 他のコマンドに仕事をさせる シェルは仕事手順の制御だけ
7. 7. なぜ、シェルに仕事を させてはいけないのか？
8. 8. 実際に、仕事をさせてみよう！
9. 9. 仕事？
10. 10. 仕事＝重めの処理
11. 11. 本日は、nクイーン問題を選択
12. 12. nクイーン問題とは もともとは1848年に考案された、チェス盤（８×８マス）と８個の クイーン（飛車と角行を合わせた動きをする駒）を利用したパズル どのクイーンもお互いのきき筋にいないようにする配置を求める ８クーイン問題をn×nのマスに応用したのがnクイーン問題 2クイーンと3クイーンには解がない nが増えると計算量が爆発的に増える。解が判明しているのは26ク イーン問題まで
13. 13. nクイーン問題の解の表現方法 （例）４クイーン ２ ４ １ ３↩️３ １ ４ ２↩️ と数の列で表現
14. 14. nクイーン問題の解法 nクイーン問題はバックトラック法で解く問題の代表例 解法はプログラムの入門書に必ずと言って良いほど登場
15. 15. バックトラック法の概説 0 1 2 3 1 2 3 4 再帰 ループ メモ 行 上斜め 下斜め 1 1 1 3 4 2
16. 16. プログラム例（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);
17. 17. 様々な言語のプログラム例 https://github.com/umidori/ nqueens/tree/master/procedural
18. 18. 例を参考にBashでnクイーン問題の 解法プログラムを作成してください
19. 19. 実演してみます
20. 20. プログラムの作成方針 1. ４クイーン問題を解くプログラムを作成 2. ４クイーン問題をnクイーン問題に拡張 全てのクイーンの組みを出力するプログラムを作成 同一行のクイーンを排除 斜め上のクイーンを排除 斜め下のクイーンを排除
21. 21. ４クイーンの全ての組を出力 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
22. 22. 同一行のクイーンを排除 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
23. 23. 斜めのクイーンを排除 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
24. 24. 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
25. 25. 12クイーンを解いてみてください
26. 26. 言語別12クイーン解答時間（参考値） （Mac Mini CPU:2.3GHz Core i7 メモリ:8GB） 言語 処理時間 C言語 ０．１４秒 Java8 １．１２秒 C# ０．５５秒 Haskell １５．０秒 Ocaml ０．１４秒 Perl ２．５９秒 Python ２．４８秒 Ruby １．９４秒 PHP ３．０８秒 JavaScript ２．１１秒 Gauche １．２１秒 Bash ６分２４秒
27. 27. なぜシェルに仕事をさせてはいけないのか？ １．処理速度が遅い ２．スクリプトが組みづらい（環境からの支援が乏しい）
28. 28. シェルが遅いのはなぜか 答え 変数のアクセス効率が良くないから 1. 変数は全て「変数名＝値」という文字列 2. 配列は高速なランダムアクセスを実現しない
29. 29. では、どうするか？
30. 30. 斎藤先生が示したパネル シェルに仕事をさせてはいけない シェルは glue language 他のコマンドに仕事をさせる シェルは仕事手順の制御だけ
31. 31. シェルは glue language シェルスクリプトでは、glue（のり）は主にパイプのこと シェルスクリプトではパイプをうまく使うことが肝要 パイプをうまく使うには発想の転換が必要 例えば、nクイーン問題では後戻りしないバックトラック法 という発想が必要になる
32. 32. 後戻りしないバックトラック法の概説 1 2 3 4 計算の進行
33. 33. AWKとBashで作成してみます
34. 34. プログラムの作成方針 1. ４クイーン問題を解くプログラムを作成 2. ４クイーン問題をnクイーン問題に拡張 全てのクイーンの組みを出力するプログラムを作成 同一行のクイーンを排除 斜め上のクイーンを排除 斜め下のクイーンを排除
35. 35. ４クイーンの全ての組を出力 f() { awk '{for(i=1;i<=4;++i)print \$0,i}'; } echo | f | f | f | f
36. 36. 同一行のクイーンを排除 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
37. 37. 斜めのクイーンを排除 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
38. 38. 関数をまとめる 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
39. 39. 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
40. 40. さらにまとめる 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
41. 41. nクイーンへの拡張法 1. ループ文を使用して、繰り返しを実現する 1. 変数に中間結果を格納する 2. ファイルに中間結果を格納する 2. 再帰を使用して、繰り返しを実現する 1. パイプに中間結果を通す
42. 42. 変数に中間結果を格納する場合 n=\$1 f() { … } tmp="" for ((i=0;i<n;++i)); do tmp=\$(echo "\$tmp" | f) done echo "\$tmp"
43. 43. ファイルに中間結果を格納する場合 n=\$1 f() { … } echo >tmp for ((i=0;i<n;++i)); do (rm tmp; f >tmp) <tmp done cat tmp rm tmp
44. 44. パイプに中間結果を通す場合 n=\$1 f() { if ((\$1==0)); then echo else f \$((\$1-1)) | … fi } f \$1
45. 45. パイプを使って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
46. 46. 12クイーンを解いてみてください
47. 47. パイプを使ったシェルスクリプトの特徴 1. 利点 1. 処理速度が速い 2. スクリプトが組みやすい 2. 欠点 1. プロセス数を意識する必要がある 2. 手続き的な発想とは異なる発想を必要とする
48. 48. パイプを使うスクリプトの発想 パイプを使うには手続き型プログラミングの 発想ではなく関数型プログラミングの発想が 必要となる
49. 49. 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))
50. 50. 関数が第一級オブジェクト 参照透過性 遅延評価 カリー化 並列化 関数型プログラム言語とパイプを使う シェルスクリプトとの共通点は？ 閉包性の活用
51. 51. 閉包性とは 抽象代数からきた言葉 集合の要素にある演算を作用させて得られた要素が、また 集合の要素であるなら、要素の集合はその演算のもとで閉 じているという 例えば、整数は加算、減算、乗算に対しては閉包性をもつ。 整数に対するいずれの演算も、結果は整数になるため
52. 52. SICPでの閉包性の説明 困ったことに多くの普通のプログラム言語にあるデータ組 合せ演算は閉包性を満足せず、閉包性を活用するのは煩わ しい。（中略）対の使えるLispと違って、これらの言語には 合成データを一様に操作するのが容易になる汎用の糊はな い。
53. 53. 閉包性を活用する言語の例 Lisp リストに対するmap,ﬁlter,ﬂattenなどの演算 SQL リレーションに対する８つのリレーショナル代数演算 シェル テキストに対するhead,tail,sortなどのコマンド
54. 54. 関数型言語の発想に慣れるには 松 竹 梅 手続き的プログラムを関数的プログラムに書き換えてみる SICP（計算機プログラムの構造と解釈）を読む シェル芸勉強会に参加する
55. 55. 関数型言語としてのシェルの利点 開発環境 開発環境はターミナル画面であるため超軽量 処理速度 パイプを利用し、関数的に解いた方が速度的に有利 記述順と実行順の一致 シェルスクリプトでは記述順と実行順が一致する
56. 56. 言語別12クイーン解答時間（参考値） （Mac Mini CPU:2.3GHz Core i7 メモリ:8GB） 言語 手続き的プログラム 関数的プログラム C言語 ０．１４秒 ー Java8 １．１２秒 ２．７８秒 C# ０．５５秒 ２．１７秒 Haskell １５．０秒 ５．３６秒 Ocaml ０．１４秒 ３．５３秒 Perl ２．５９秒 ３４．４秒 Python ２．４８秒 １１．９秒 Ruby １．９４秒 １７．０秒 PHP ３．０８秒 ２３分０３秒 JavaScript ２．１１秒 １６分３５秒 Gauche １．２１秒 ２９．８秒 Bash ６分２４秒 １１．０秒
57. 57. シェルでは記述順と処理順が一致 普通、関数的なプログラムでは右から左 に処理が進む
58. 58. 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} []
59. 59. 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}}'
60. 60. まとめ シェルに仕事をさせてはいけない 仕事をさせる場合はパイプを使う パイプを使いこなすには発想の転換が必要 発想の転換にはシェル芸勉強会が最適
61. 61. ここで、シェル芸問題をひとつ nクイーンの数列（例えば、2 4 1 3）を次のようなアスキー アートに変換してみましょう。 +---+---+---+---+ | | | Q | | +---+---+---+---+ | Q | | | | +---+---+---+---+ | | | | Q | +---+---+---+---+ | | Q | | | +---+---+---+---+
62. 62. 解答例１ 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 ""}}'
63. 63. 解答例２ while read i; do a=(\$i); printf \$(printf "+%0\${#a[@]}sn %\${#a[@]}sn" | sed "s/ /|%\${#a[@]}sn+%0\${#a[@]}sn/ 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