# 問題解決力を鍛える!アルゴリズムとデータ構造
## 日程
2022/12/23
## 第4.1~4.3章
- 任意参加
- ファシリテーター: Chen
### 話す・書く順番決めルーレット
in Ruby
```ruby
['hsu', 'chen', 'teruya', 'kanazawa', 'okura', 'tsujita'].shuffle.each { |v| puts ">[name=#{v}]\n\n" }
```
in JavaScript
```javascript
const l = ['hsu', 'chen', 'teruya', 'kanazawa', 'okura', 'tsujita']
l[Math.floor(Math.random()*l.length)]
```
### ファシリテーター決め方
- `%w(hsu chen teruya kanazawa okura kanno)`の順番でやっていく
### 参加者がやること
- 事前に決められた章を読む
- 学んだことやわからなかったことを書き込む(任意)
当日
- 最初に感想や質問を書く時間を設ける(10分)
- 時間枠 60分
- 延長枠 30分
### 勉強会時にやること
各自学びや気付き、疑問を発表していく
ファシリテーターがよしなに議論を進める
終了前に勉強会を振り返る?(KPTなどで)← 60分のときに仮締め
## 各自が書き込む場所
>[name=tsujita]
> 4.1
本のサンプルコードをrubyで書いてみました
```ruby=
def func(n)
if (n == 0)
return 0
else
return n + func(n - 1)
end
end
```
- 再起させる時はベースケースに対する処理を必ず行う
- 再起呼び出しを行った時の引数が、ベースケースに近づくようにすること
- いつも再起させたくてうまくできない・・となっていたので学びだった
> ユークリッドの互除法
手を動かすとなるほど!となるけど使い所は正直謎でした
> フィボナッチ数列
```ruby=
def fibo(n)
if n == 0
return 0
elsif n == 1
return 1
else
return fibo(n - 1) + fibo(n - 2)
end
end
```
>[name=okura]
### 4.1 再帰とは
Catalのコードベースで再帰を使っていた箇所があったような気がするが、思い出せない…
プログラマは再帰が好きで、再帰的頭字語が有名。Yaml Ain't Markup Languageとか、PHP: Hypertext preprocessorとか。
再帰は「ゆきてかえりし物語」になっている。これは『ホビットの冒険』の副題で、"There and back again"
### 4.2 再帰の例(1):ユークリッドの互除法
このコードが高速なのは後述される繰り返しがないからなのかな。
整数論、昔大学受験のためにちょこっとだけ勉強したし、大学時代にも整数論の本を読んだことがあった気がするけど、今勉強したらまた違って見えるのだろうなあ。
https://inupri.web.fc2.com/ichihashi.html
### 4.3 再帰の例(2):フィボナッチ数列
フィボナッチ数列はあまりにも有名すぎて、むしろ再帰以外の方法で書くほうが難しい気がする。
```ruby=
def fibo(n)
if n == 0
return 0
elsif n == 1
return 1
else
define_method("fibo#{n}") { |i| method(:fibo).call(i) }
return send("fibo#{n}", n - 1) + send("fibo#{n}", n - 1)
end
end
p fibo(10)
```
>[name=teruya]
> p.95 再帰関数のテンプレート
この考え方が身についてから、だいぶ理解がしやすくなりました!!!
(再帰関数の先頭で、returnするifのケースを記述する)
DFS(深さ優先探索)もこんな感じ
> p.96 ユークリッドの互除法
1番わかりやすい例(再帰が1本道なので)
RubyにはInteger#gcdがあります
https://docs.ruby-lang.org/ja/latest/method/Integer/i/gcd.html
lcm(最小公倍数)もあります
https://docs.ruby-lang.org/ja/latest/method/Integer/i/lcm.html
> p.98 フィボナッチ数列
4.4 に出てくるfor文ループの方がわかりやすい&自然と動的計画法になる気がする(人間が考える時の手順と同じになる)
1つのArrayで↓を表現する感じ
0, 1
0, 1, 1
0, 1, 1, 2
0, 1, 1, 2, 3,
0, 1, 1, 2, 3, 5 ....
4.1 トリボナッチ数列
```ruby=
def trib(num)
return 0 if num == 0
return 0 if num == 1
return 1 if num == 2
return trib(num - 1) + trib(num - 2) + trib(num - 3)
end
puts trib(gets.to_i)
```
4.2 ↑のメモ化
```ruby=
def memo_trib(num)
return 0 if num == 0
return 0 if num == 1
return 1 if num == 2
return $memo[num] if $memo[num]
return $memo[num] = memo_trib(num - 1) + memo_trib(num - 2) + memo_trib(num - 3)
end
num = gets.to_i
$memo = Array.new(num + 1)
# $memo[0] = 0
# $memo[1] = 0
# $memo[2] = 1
puts memo_trib(num)
p $memo
```
> [name=chen]
### 再帰:自分自身を呼び出すこと
> - 「スタック領域」:この「ベースケースに対する処理」がとても大切です。ベースケースの処理を行わないと、再帰呼び出しを無限に繰り返すことになります。
再帰は難しいと思いますが、その「スタック領域」の概念は非常重要だと思います。ベースケースが分かるどこから着手するのも分かるから。
#### 再帰の例 (1):ユークリッドの互除法
> - 2つの整数 m,n の最大公約数GCD(m, n)を求めるアルゴリズムです
> - ユークリッドの互除法の計算量は、m>=n>0 として、O(logn)となります
ここまで理解できる、再帰の簡単な例と思います。
#### 再帰の例(2):フィボナッチ数列
- ex. 0,1,1,2,3,5,8,13,21,34...
- 章末問題4.1(トリボナッチ数列):
https://jsfiddle.net/cgm1jkaf/5/
```javascript=
function tribo(n){
// 再帰関数を呼び出す
console.log(`tribo(${n})を呼び出しました`)
if(n <= 1) return 0
if(n == 2) return 1
const result = tribo(n-1) + tribo(n-2) + tribo(n-3)
// 答えを示す
console.log('')
console.log(`${n} 項目 = ${result}`)
return result
}
// 0,0,1,1,2,4,7,13,24
const test = tribo(8)
console.log(test)
/* ===Output===
"tribo(8)を呼び出しました"
"tribo(7)を呼び出しました"
"tribo(6)を呼び出しました"
"tribo(5)を呼び出しました"
"tribo(4)を呼び出しました"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
""
"4 項目 = 2"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
""
"5 項目 = 4"
"tribo(4)を呼び出しました"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
""
"4 項目 = 2"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
""
"6 項目 = 7"
"tribo(5)を呼び出しました"
"tribo(4)を呼び出しました"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
""
"4 項目 = 2"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
""
"5 項目 = 4"
"tribo(4)を呼び出しました"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
""
"4 項目 = 2"
""
"7 項目 = 13"
"tribo(6)を呼び出しました"
"tribo(5)を呼び出しました"
"tribo(4)を呼び出しました"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
""
"4 項目 = 2"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
""
"5 項目 = 4"
"tribo(4)を呼び出しました"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
""
"4 項目 = 2"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
""
"6 項目 = 7"
"tribo(5)を呼び出しました"
"tribo(4)を呼び出しました"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
""
"4 項目 = 2"
"tribo(3)を呼び出しました"
"tribo(2)を呼び出しました"
"tribo(1)を呼び出しました"
"tribo(0)を呼び出しました"
""
"3 項目 = 1"
"tribo(2)を呼び出しました"
""
"5 項目 = 4"
""
"8 項目 = 24"
*/
```
```ruby=
$memo = Array.new(num + 1) do |i|
case i
when 0, 1 then 0
when 2 then 1
else nil
end
end
```
## 振り返り
> [name=tsujita]
再起何処かで使いたい・・
> [name=okura]
https://ja.wikipedia.org/wiki/%E5%86%8D%E5%B8%B0%E7%9A%84%E9%A0%AD%E5%AD%97%E8%AA%9E
> [name=teruya]
再帰まじでむずかしい(キューを使ったBFS(幅優先探索)の方がわかりやすい)
> [name=chen]
$memoを使ってる方法、勉強になりました!!なるほど〜〜〜
## 終了後ファシリテーターがやること
- 次回のファシリテーターを決める(2023/01/06)
- teruya
- 読む範囲を決める
- 第4章最後まで