ジョギング
- 山王公園往復
- 少しだけ胸の痛みが治まってきた感じ
日課
- お休み
ギョームにて GraphQL について, Hello World レベルから調査する. AWS だと AppSync を利用すると, サクッと GraphQL の環境を用意出来ることが解った. AppSync はデータソースとして DynamoDB や Amazon Elasticsearch Service, Lambda を利用出来る. スキーマを定義して, スキーマとデータソースはリゾルバマッピングを書くことで関連付けることが出来ることがだいたい理解出来た.
www.slideshare.net
しかし, この仕組を導入するメリットがあるのかどうかについては, GraphQL 自体がどのようなものなのかの理解をより深める必要があるなと思った.
AWS SDK for Ruby を使ってコマンドラインツールを作る時にテストまで含めた雛形みたいなのがあったら楽だよなーと思いつつ, ソフトバンクが日本シリーズを制したり, 楽しそうな JAWS FESTA の様子が SNS のタイムラインに流れてくるのを羨ましく横目に見つつ, もくもくサンプル的な何かを作ってみました.
これ.
EC2 インスタンス ID 一覧, S3 バケット名一覧を返すだけのあくまでもサンプル的なものとなります.
$ bundle exec sample-cli --help Commands: sample-cli buckets # list up bucket name. sample-cli help [COMMAND] # Describe available commands or one specific command sample-cli input WORD # input words print. sample-cli instances # list up instance ids. sample-cli version # version print.
環境変数に AWS_PROFILE と AWS_REGION を定義して利用することを想定しています.
$ export AWS_PROFILE=xxxxxxxxxxxxxxxxxxx $ export AWS_REGION=ap-northeast-1 $ bundle exec sample-cli instances i-xxxxxxxxxxxxxxxx1 i-xxxxxxxxxxxxxxxx2 i-xxxxxxxxxxxxxxxx3 i-xxxxxxxxxxxxxxxx4 i-xxxxxxxxxxxxxxxx5 $ bundle exec sample-cli buckets bucket-a bucket-b bucket-c bucket-d bucket-e ...
で, コマンドラインツールを作る上でいろいろと検討した内容を以下の通り書いていきたいと思います. 誤りや認識不足がありおかしなことが書かれているかと思いますので, コメント等で指摘いただければ幸いでございます.
コマンドラインツールを作る際に引数の処理等を良しなに面倒を見てくれる erikhuda/thor がとても便利だと思います. 例えば, 上述の例で実行されている instances
や buckets
のサブコマンドをメソッドとして記述することで, 簡単にサブコマンドを追加することが出来ます.
以下, 実装例です.
module SampleCli class CLI < Thor desc 'version', 'version print.' def version puts SampleCli::VERSION end desc 'input WORD', 'input words print.' def input(word = nil) puts 'Please input `word`' if word.nil? puts word end desc 'instances', 'list up instance ids.' def instances ec2 = SampleCli::Ec2.new puts ec2.instances end desc 'buckets', 'list up bucket name.' def buckets s3 = SampleCli::S3.new puts s3.buckets end end end
Thor
クラスを継承して, 上記のように実装しておくと, 以下のような感じでサブコマンド化してくれます.
$ bundle exec sample-cli --help Commands: sample-cli buckets # list up bucket name. sample-cli help [COMMAND] # Describe available commands or one specific command sample-cli input WORD # input words print. sample-cli instances # list up instance ids. sample-cli version # version print.
これだけでも十分にテンションが上がります.
各サブコマンドで呼ばれる処理を実装していくことで, それなりにコマンドラインツールが数分で出来上がってしまいます.
好みの問題になってしまうかもしれませんが, 今回は以下のようにファイルを分割してみました. よく利用するツールのフォルダ構成を程よく参考させて頂きました.
$ tree -L 3 . . ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin │ ├── console │ └── setup ├── exe │ └── sample-cli ├── lib │ ├── sample_cli │ │ ├── cli.rb │ │ ├── client.rb │ │ ├── ec2.rb │ │ ├── s3.rb │ │ ├── stub │ │ ├── stub.rb │ │ └── version.rb │ └── sample_cli.rb ├── sample-cli.gemspec ├── spec │ ├── default_spec.rb │ ├── ec2_spec.rb │ ├── s3_spec.rb │ └── spec_helper.rb └── vendor └── bundle └── ruby
instances
で利用する処理を実装buckets
で利用する処理を実装instances
や buckets
の処理内容をテストする際に利用するスタブを定義 (テストについは後述)という感じです.
今後, S3 オブジェクトの一覧を取得するサブコマンドを追加する場合には, lib/sample_cli/cli.rb に objects
という名前のメソッドを追加して, lib/sample_cli/s3.rb に実際の処理を追加していく感じを想定しています.
# lib/sample_cli/cli.rb に以下を追加 ... desc 'objects', 'list up S3 objects' option :bucket, type: :string, aliases: '-b', desc: 'S3 バケットを指定する.' def objects s3 = SampleCli::S3.new puts s3.objects(option[:bucket]) end ... # lib/sample_cli/s3.rb に以下を追加 def objects(bucket) puts list_objects(bucket) end private def list_objects(bucket) objects = [] options = { bucket: bucket } loop do res = s3.list_objects_v2(options) objects << res.contents.map(&:key) options[:continuation_token] = res.next_continuation_token break unless options[:continuation_token] end objects end
尚, stub まわりの処理については, awspec を参考にさせて頂きました. 有難うございました.
Thor のテストが参考になることを最近知りました ありがとうございます. コマンド出力を以下の capture というメソッドで取得して, その出力をパースして評価しているようです.
# https://github.com/erikhuda/thor/blob/master/spec/helper.rb#L51-L62 ... def capture(stream) begin stream = stream.to_s eval "$#{stream} = StringIO.new" yield result = eval("$#{stream}").string ensure eval("$#{stream} = #{stream.upcase}") end result end ...
このメソッドは, eval で文字列をコマンドとして実行されるようになっていて, 一見, 何をやっているか良く解りませんでしたが, よく見ると以下のような処理になっています.
$stdout = StringIO.new # 標準出力の出力先を StringIO クラスに変更 yield # ブロックで渡された処理を実行する output = $stdout.string # 標準入力を変数 output に代入 $stdout = STDOUT # 標準出力の出力先を STDOUT に戻す (デフォルトが STDOUT なので)
これをそのまま spec_helper.rb に押し込んでおいて, テストには以下のように実装しました.
describe 'sample_cli check subcommand objects' do it 'have object keys by cli' do output = capture(:stdout) { SampleCli::CLI.start(%w{objects --bucket=foo}) } expect(output).to match('foo\nbar\nbaz/key\n') end end
このテストの場合, 先述のオブジェクト一覧を取得する objects
サブコマンドの正常系テストを想定しています.
sample-cli objects --bucket=foo
これを実行すると以下のようにテストがパスします.
$ bundle exec rake spec:s3 ... sample_cli check subcommand buckets have bucket names have bucket names by cli sample_cli check subcommand objects have object keys have object keys by cli Finished in 0.11143 seconds (files took 3.24 seconds to load) 4 examples, 0 failures
これは, Docker を利用して仮想的な AWS 環境, Ruby の実行環境を用意し, 実際にコマンドを AWS 環境に対して実行する方法を検討しました. これについては, 別の記事でまとめたいと思います.
AWS 環境に対するテストを行う場合, 実際の AWS リソースを叩くというのは出来るだけ避けたいところです. もちろん, テスト用の環境を AWS 上に用意して実際にリソースを叩くことでより精度の高いテスト結果を得られる可能性があると思いますが, うっかり変更してはいけない環境を操作してしまう事故の可能性も 0 ではありませんし, 利用料が発生することも有り得ます. これらを解決する方法として, 以下の 2 点のアプローチが取れると考えています.
1 については, 前節の「コマンドラインの実行をどのようにテストするか (2)」でも触れていますが, localstack や moto 等のローカル環境に AWS 環境を実行するツールを Docker 上で起動して, その環境に対して実際のコマンドを発行して結果を解析してテストを行います.
本節 (今回) は 2 のスタブを利用したテストについて検討しています. スタブを利用する場合, AWS SDK for Ruby ではクライアントを初期化する際に以下のように指定することで, スタブ化されたデータを返すようになります.
require 'aws-sdk' s3 = Aws::S3::Client.new(stub_responses: true) ...
詳細は以下のドキュメントに記載されています.
このドキュメントによると, スタブ化した場合, 特にデータを用意しない場合には, 以下のようなデータを返却するとのことです.
例えば, S3 バケットの一覧を取得する list_buckets というメソッドをスタブ化した場合には以下のような結果が返ってきます.
# スタブデータを用意しない場合 irb(main):001:0> require 'aws-sdk-s3' => true irb(main):002:0> s3 = Aws::S3::Client.new(stub_responses: true) => #<Aws::S3::Client> irb(main):003:0> s3.list_buckets => #<struct Aws::S3::Types::ListBucketsOutput buckets=[], owner=#<struct Aws::S3::Types::Owner display_name="DisplayName", id="ID">> irb(main):004:0> s3.list_buckets.buckets => [] # スタブデータを用意した場合 (foo と bar というバケット名を返されることを想定する) irb(main):005:0> bucket_data = s3.stub_data(:list_buckets, :buckets => [{name:'foo'}, {name:'bar'}]) => #<struct Aws::S3::Types::ListBucketsOutput buckets=[#<struct Aws::S3::Types::Bucket name="foo", creation_date=nil>, #<struct Aws::S3::Types::Bucket name="bar", creation_date=nil>], owner=#<struct Aws::S3::Types::Owner display_name="DisplayName", id="ID">> irb(main):006:0> s3.stub_responses(:list_buckets, bucket_data) => [{:data=>#<struct Aws::S3::Types::ListBucketsOutput buckets=[#<struct Aws::S3::Types::Bucket name="foo", creation_date=nil>, #<struct Aws::S3::Types::Bucket name="bar", creation_date=nil>], owner=#<struct Aws::S3::Types::Owner display_name="DisplayName", id="ID">>}] irb(main):007:0> s3.list_buckets.buckets => [#<struct Aws::S3::Types::Bucket name="foo", creation_date=nil>, #<struct Aws::S3::Types::Bucket name="bar", creation_date=nil>] irb(main):008:0> s3.list_buckets.buckets.map(&:name) => ["foo", "bar"]
今回は awspec の実装をそのまま参考にさせて頂いて, 以下のようにスタブが利用されるように実装を行いました.
stub_responses: true
を追加# spec/spec_helper.rb require 'rspec' require 'sample_cli' Aws.config.update(stub_responses: true) ...
list_buckets
や list_object_v2
メソッドのスタブデータ)# lib/stub/s3.rb Aws.config[:s3] = { stub_responses: { list_buckets: { buckets: [ { name: 'foo' }, { name: 'bar' } ] }, list_objects_v2: { contents: [ { key: 'foo' }, { key: 'bar' }, { key: 'baz/key' } ] } } }
# lib/stub.rb module SampleCli class Stub def self.load(type) require File.dirname(__FILE__) + '/stub/' + type end end end
require 'spec_helper' SampleCli::Stub.load 's3' describe 'sample_cli check subcommand buckets' do it 'have bucket names' do expect(SampleCli::S3.new.buckets).to eq(%w(foo bar)) end it 'have bucket names by cli' do output = capture(:stdout) { SampleCli::CLI.start(%w{buckets}) } expect(output).to match('foo\nbar\n') end end describe 'sample_cli check subcommand objects' do it 'have object keys' do expect(SampleCli::S3.new.objects('foo')).to match(%w(foo bar baz/key)) end it 'have object keys by cli' do output = capture(:stdout) { SampleCli::CLI.start(%w{objects --bucket=foo}) } expect(output).to match('foo\nbar\nbaz/key\n') end end
上記の通り, テストはサブコマンドを実行した場合 (have bucket names by cli
や have object keys by cli
) と, サブコマンドから呼ばれるメソッド (have bucket names
や have object keys
) に対してテストを行っています. これを以下のように実行してテストします.
$ bundle exec rake spec:s3 ... sample_cli check subcommand buckets have bucket names have bucket names by cli sample_cli check subcommand objects have object keys have object keys by cli Finished in 0.11505 seconds (files took 3.01 seconds to load) 4 examples, 0 failures
いい感じです.
せっかくなので, rubocop でコーディングスタイルに準拠しているかもチェックしています. 例えば, 以下のようなコードは警察の取締対象となります.
it "have object keys by cli" do output = capture(:stdout) { SampleCli::CLI.start(%w{objects --bucket=foo}) } expect(output).to match("foo\nbar\nbaz/key\n") end
容疑は Prefer single-quoted strings when you don't need string interpolation or special symbols.
です. 実際の取締の様子です.
$ bundle exec rake spec:rubocop Running RuboCop... Inspecting 18 files .....C............ Offenses: spec/s3_spec.rb:20:6: C: Prefer single-quoted strings when you don't need string interpolation or special symbols. it "have object keys by cli" do ^^^^^^^^^^^^^^^^^^^^^^^^^ 18 files inspected, 1 offense detected RuboCop failed!
釈放される為にはダブルクウォートをシングルクォートに書き換えることで釈放されます.
it 'have object keys by cli' do output = capture(:stdout) { SampleCli::CLI.start(%w{objects --bucket=foo}) } expect(output).to match('foo\nbar\nbaz/key\n') end
釈放の様子です.
$ bundle exec rake spec:rubocop Running RuboCop... Inspecting 18 files .................. 18 files inspected, no offenses detected
いい感じですね.
こちらもせっかくなので Travis CI でテストを走らせます. .travis.yml は以下の通りです.
sudo: false language: ruby rvm: - 2.5.1 before_install: gem install bundler -v 1.16.2
テスト結果は以下の通りです.
AWS SDK for Ruby を利用した CLI ツールのサンプル的なものを実装検討してみました. 素人のたわごとになりますので, いろいろとツッコミどころがあるとは思いますが, これをテンプレートとしてより良いオレオレツールが作れるようになると嬉しいなあ. あと, 先人が書かれたコードを読むというのは本当に勉強になりました.
社内で Real World HTTP 輪読会に参加したけど, 今まで軽々しく HTTP を語って申し訳ございませんという感じになった.
— Yohei Kawahara(かっぱ) (@inokara) 2018年11月2日
本当に申し訳ございません...
放送大学教養学部の「アルゴリズムとプログラミング」という授業で使われる「アルゴリズムとプログラミング」という教材書籍を自分なりにまとめたものです.
以下, 冗長なコードの例.
/* code: ex5-1.c */ #include <stdio.h> int main() { int i; for (i = 0; i < 10; i++) printf ("%d", i); printf ("\n"); for (i = 0; i < 10; i++) printf ("%d", i); printf ("\n"); for (i = 0; i < 10; i++) printf ("%d", i); printf ("\n"); return 0; }
これをコンパイルして実行してみる.
root@0be431eebb77:/work# gcc ex5-1.c -o ex5-1 -g3 root@0be431eebb77:/work# ./ex5-1 0123456789 0123456789 0123456789
以下は, このコードから 0 〜 9 の数値を表示する print_numbers を関数に切り出した場合のコード.
/* code: ex5-2.c */ #include <stdio.h> void print_numbers (void) { int i; for (i = 0; i < 10; i++) printf ("%d", i); printf ("\n"); } int main () { print_numbers (); print_numbers (); print_numbers (); return 0; }
これをコンパイルして実行してみる.
root@0be431eebb77:/work# gcc ex5-2.c -o ex5-2 -g3 root@0be431eebb77:/work# ./ex5-2 0123456789 0123456789 0123456789
main
も関数の 1 つであり, これはコードの初めに呼ばれる特殊な関数である以下は, main 関数より前に print_numbers 関数の関数プロトタイプが記述されている.
/* code: ex5-3.c */ #include <stdio.h> void print_numbers (void); int main () { print_numbers (); print_numbers (); print_numbers (); return 0; } void print_numbers (void) { int i; for (i = 0; i < 10; i++) printf ("%d", i); printf ("\n"); }
これをコンパイルして実行してみる.
root@0be431eebb77:/work# gcc ex5-3.c -o ex5-3 -g3 root@0be431eebb77:/work# ./ex5-3 0123456789 0123456789 0123456789
例えば, sqrt 関数 (平方根を求める関数) の関数プロトタイプ (double sqrt (double);
) であり, math.h というヘッダファイルに関数プロトタイプの記述があるので, sqrt 関数を利用する場合には, 以下のように math.h をインクルードして利用する.
... #include <stdio.h> #include <math.h> ...
以下のコードでは, 関数 f と関数 g の中で同じ変数名 i
が使われているが, それぞれの関数でスコープが異なる.
/* code: ex5-4.c */ #include <stdio.h> void g (void) { int i; for (i = 0; i < 3; i++) { printf ("a"); } } void f (void) { int i; for (i = 0; i < 5; i++) { g (); } } int main () { f (); return 0; }
これをコンパイルして実行する.
root@0be431eebb77:/work# gcc ex5-4.c -o ex5-4 -g3 root@0be431eebb77:/work# ./ex5-4 aaaaaaaaaaaaaaa
同じ変数名のグローバル変数とローカル変数を宣言することも可能であるが, ローカル変数が宣言されているブロック内では, グローバル変数よりもローカル変数の方が優先される.
以下のコードは三角形の面積 () を求める関数の例.
/* code: ex5-5.c */ #include <stdio.h> float triangle (float base, float height) { float c; c = (base * height) / 2.000F; return c; } int main () { float t; t = triangle (3.00, 4.00); printf ("triangle = %f\n", t); t = triangle (5.00, 6.00); printf ("triangle = %f\n", t); return 0; }
尚, 三角形の面積は以下の公式で求められる.
$$ area = \frac{1}{2} \times base \times height $$
上記のコードにおいて, 底辺 () と高さ () の 2 つの float 型の引数を持ち, この引数に値が渡されると関数 (triangle) が三角形の面積を計算する. この関数の戻り値として float 型の面積の値が関数から返される. 3.00, 4.00 及び 5.00 と 6.00 が実引数であり, 関数で宣言されている base と height が仮引数となる.
一応, このコードをコンパイルして実行してみる.
root@0be431eebb77:/work# gcc ex5-5.c -o ex5-5 -g3 root@0be431eebb77:/work# ./ex5-5 triangle = 6.000000 triangle = 15.000000
値渡し
(pass by value) と 参照渡し
(pass by reference) と呼ばれるものがある値渡し
は変数のコピーが作成されて値が渡される方法で, 関数内で変数の値が変更されても元の変数の値は変わらない参照渡し
は, 変数そのものを渡す為, 関数内で変数の値の変更は, 呼び出した側の変数にも影響して値が変わる以下のコードは 値渡し
と 参照渡し
の場合では, 変数 a の出力する値が異なる例.
/* code: ex5-6.c */ #include <stdio.h> void add_pass_by_value (int i) { i = i + 1; } void add_pass_by_reference (int *i) { *i = *i + 1; } int main() { int a; a = 10; add_pass_by_value (a); printf ("%d\n", a); a = 10; add_pass_by_reference (&a); printf ("%d\n", a); return 0; }
このコードをコンパイルして実行してみる.
root@0be431eebb77:/work# gcc ex5-6.c -o ex5-6 -g3 root@0be431eebb77:/work# ./ex5-6 10 11
C 言語では変数ポインタを取得出来る為, 変数へのポインタを渡すことで 参照渡し
と同じ効果がある アドレス渡し
が可能であるとのこと...ポインタの話がサラッと出てきているので辛い...ポインタについては別で勉強する必要があるな.
再帰呼び出し (recursion call) とは, 関数や手続き等が, 自分自身を呼び出して実行することで, アルゴリズムの中には, 再帰的にプログラムを記述することによって効果的な処理を出来るものがある. 以下, 再帰プログラミングの例として用いられる階乗 (factorial) の計算式で, 1 から までの自然数の総乗である. 階乗は で表現され, 以下のような式で定義される. (尚, と定義されている.)
$$ n!=\prod_{k=1}^n k=n\times\left(n-1\right)\times\left(n-2\right)\cdots\times3\times2\times1 $$
実際の階乗の計算例は以下の通り. $n$ の値が大きくなるに従って急激に数が増加する.
1! = 1 2! = 2 x 1 = 2 3! = 3 x 2 x 1 = 6 4! = 4 x 3 x 2 x 1 = 24 .. .. 10! = 10 x 9 x 8 x 7 x 6 x 5 x 4 x 3 x 2 x 1 = 3,628,800
この階乗を計算をコードに落とし込むと以下のような感じで factorial 関数を再帰で呼び出して計算結果を返す. factorial 関数は, factorial 関数内で という引数で呼び出すようになっており, が になった時点で 1 を返し, その時点で factorial 関数の呼び出しを止める.
/* code: ex5-7.c */ #include <stdio.h> int factorial (int n) { if (n == 0) { return 1; } else { return n * factorial (n - 1); } } int main() { int i; i = 5; printf ("%d! = %d\n", i, factorial (i)); return 0; }
これをコンパイルして実行する.
root@0be431eebb77:/work# gcc ex5-7.c -o ex5-7 -g3 root@0be431eebb77:/work# ./ex5-7 5! = 120
これを gdb でステップ実行してみると以下のような感じになる.
(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /work/ex5-7 Breakpoint 1, main () at ex5-7.c:16 16 i = 5; (gdb) s 17 printf ("%d! = %d\n", i, factorial (i)); (gdb) s factorial (n=5) at ex5-7.c:6 6 if (n == 0) { (gdb) s 9 return n * factorial (n -1); (gdb) s factorial (n=4) at ex5-7.c:6 6 if (n == 0) { (gdb) s 9 return n * factorial (n -1); (gdb) s factorial (n=3) at ex5-7.c:6 6 if (n == 0) { (gdb) s 9 return n * factorial (n -1); (gdb) s factorial (n=2) at ex5-7.c:6 6 if (n == 0) { (gdb) s 9 return n * factorial (n -1); (gdb) s factorial (n=1) at ex5-7.c:6 6 if (n == 0) { (gdb) s 9 return n * factorial (n -1); (gdb) s factorial (n=0) at ex5-7.c:6 6 if (n == 0) { (gdb) s 7 return 1;
return n * factorial (n -1);
が 5 回繰り返された後に return 1;
が返っていることが解る.
本教材ではポインタがサラッとしか触れられていないのが辛い. とりあえず, 本教材に書かれている内容を抜粋する.
*
間接演算子 (indirection operator) は, ポンタを介して値に間接的に参照し, ポインタが指し示すアドレスに格納されている値を参照する演算子である&
アドレス演算子 (address-of operator) は, オペランドのアドレスを与える, 変数のアドレスを取得する演算子であるこれだけではピンと来ないので...自分で調べるしかない.
以下のコードは, どのような値を出力するか答えなさい.
/* code: q5-1.c */ #include <stdio.h> float trapezoid (float a, float b, float h) { float c; c = ((a + b) * h) / 2.000F; return c; } int main () { float t; t = trapezoid (3.00, 4.00, 5.00); printf ("trapezoid = %f\n", t); t = trapezoid (5.00, 6.00, 7.00); printf ("trapezoid = %f\n", t); return 0; }
これをコンパイルして実行する.
root@0be431eebb77:/work# gcc q5-1.c -o q5-1 -g3 root@0be431eebb77:/work# ./q5-1 trapezoid = 17.500000 trapezoid = 38.500000
これは台形の面積を求めるコードだった. 尚, C 言語では, 関数に設定出来る引数の数の上限はコンパイラに依存している. C90 規格では 31 個, C99 規格では 127 個まで利用出来る.
以下のコードは, どのような値を出力するか答えなさい.
/* code: q5-2.c */ #include <stdio.h> struct student { int id; char grade; float average; }; typedef struct student STUDENT_TYPE; STUDENT_TYPE initialize_student_record (STUDENT_TYPE s) { s. id++; s. grade = 'x'; s. average = 0.0; return s; } int main () { STUDENT_TYPE student; student. id = 20; student. grade = 'a'; student. average = 300.000; printf ("%d %c %f\n", student. id, student. grade, student. average); student = initialize_student_record (student); printf ("%d %c %f\n", student. id, student. grade, student. average); return 0; }
これをコンパイルして実行してみる.
root@0be431eebb77:/work# gcc q5-2.c -o q5-2 -g3 root@0be431eebb77:/work# ./q5-2 20 a 300.000000 21 x 0.000000
以下の再帰関数のコードは, どのような値を出力するか答えなさい.
/* code: q5-3.c */ #include <stdio.h> int fibonacci (int n) { if (n == 0) { return 0; } else if (n == 1) { return 1; } else { return (fibonacci (n - 1) + fibonacci (n - 2)); } } int main () { int i; i = 10; printf ("fibonacci (%d) = %d\n", i, fibonacci (i)); }
これをコンパイルして実行してみる.
root@0be431eebb77:/work# gcc q5-3.c -o q5-3 -g3 root@0be431eebb77:/work# ./q5-3 fibonacci (10) = 55
関数名から判るとおり, これはフィボナッチ数を計算するプログラムである. このプログラムをちょっと弄って 0 〜 11 まで数のフィボナッチ数列を計算させてみる.
/* code: q5-3a.c */ #include <stdio.h> int fibonacci (int n) { if (n == 0) { return 0; } else if (n == 1) { return 1; } else { return (fibonacci (n - 1) + fibonacci (n - 2)); } } int main () { int i; for (i = 0; i < 11; i++) { printf ("fibonacci (%d) = %d\n", i, fibonacci (i)); } }
コンパイルして実行する.
root@0be431eebb77:/work# gcc q5-3a.c -o q5-3a -g3 root@0be431eebb77:/work# ./q5-3a fibonacci (0) = 0 fibonacci (1) = 1 fibonacci (2) = 1 fibonacci (3) = 2 fibonacci (4) = 3 fibonacci (5) = 5 fibonacci (6) = 8 fibonacci (7) = 13 fibonacci (8) = 21 fibonacci (9) = 34 fibonacci (10) = 55
以下の再帰関数のコードは, どのような値を出力するか答えなさい.
/* code: q5-4.c */ #include <stdio.h> void foo (int n) { if (n < 15) { foo (n + 1); printf ("%d ", n); } } int main () { foo (0); return 0; }
コンパイルして実行する.
root@0be431eebb77:/work# gcc q5-4.c -o q5-4 -g3 root@0be431eebb77:/work# ./q5-4 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
上記のように 14 から 0 の数値が出力される.
コード ex5-7.c の階乗を計算する再帰関数のコードで を変更し, として負の整数を引数にして関数を呼び出した場合, どのような問題があるか考えなさい.
以下, ex5-7c のコード.
/* code: ex5-7.c */ #include <stdio.h> int factorial (int n) { if (n == 0) { return 1; } else { return n * factorial (n - 1); } } int main() { int i; i = 5; printf ("%d! = %d\n", i, factorial (i)); return 0; }
とりあえず, ex5-7.c の実行結果.
root@0be431eebb77:/work# ./ex5-7 5! = 120
上記のコードを以下のように書き換えてみる.
/* code: q5-5.c */ #include <stdio.h> int factorial (int n) { if (n == 0) { return 1; } else { return n * factorial (n - 1); } } int main() { int i; i = -1; printf ("%d! = %d\n", i, factorial (i)); return 0; }
これを実行する.
root@0be431eebb77:/work# ./q5-5
Segmentation fault
あちゃー. 一応, gdb で確認.
(gdb) run The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /work/q5-5 Breakpoint 1, main () at q5-5.c:16 16 i = -1; (gdb) s 17 printf ("%d! = %d\n", i, factorial (i)); (gdb) s factorial (n=-1) at q5-5.c:6 6 if (n == 0) { (gdb) s 9 return n * factorial (n - 1); (gdb) s factorial (n=-2) at q5-5.c:6 6 if (n == 0) { (gdb) s 9 return n * factorial (n - 1); (gdb) s factorial (n=-3) at q5-5.c:6 6 if (n == 0) { (gdb) s 9 return n * factorial (n - 1); (gdb) s factorial (n=-4) at q5-5.c:6 6 if (n == 0) { (gdb)
上記のように factorial (n=-1)
, factorial (n=-2)
, factorial (n=-3)
となる為, n == 0
の条件に合致しなくなり, 無限ループが発生して最終的には Segmentation fault
となる.
トゥデイは Cognito の User Pool を弄って終わりました. User Pool にユーザーを登録して, パスワードを変更する流れの雰囲気は掴めた気がします... 修行の日々は続きます. #jawsugfuk
— Yohei Kawahara(かっぱ) (@inokara) 2018年11月1日
久しぶりに美人居酒屋とんとん. 鶏のタタキと湯豆腐を食べた.
気づいたら来週の日曜日 (11 日) は福岡マラソン desuyone
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月31日
あっという間に福岡マラソンだ.
鹿児島マラソン当選のお知らせ. きばっど〜.
— Yohei Kawahara(かっぱ) (@inokara) 2018年10月31日
ちょっと忘れそうになっていたけど, 鹿児島マラソンにも当選した. 入金は 11/15 まで. 忘れずに.