Upgrade to Pro — share decks privately, control downloads, hide ads and more …

AI時代を乗り切る実装力をつけよう / Get avility of implementation beyond AI era

Naoki Kishida
February 15, 2024

AI時代を乗り切る実装力をつけよう / Get avility of implementation beyond AI era

2024/2/15-16に開催されたDeveloper Summit 2024での登壇資料です。
https://event.shoeisha.jp/devsumi/20240215/session/4778

Naoki Kishida

February 15, 2024
Tweet

More Decks by Naoki Kishida

Other Decks in Programming

Transcript

  1. 2024/02/15 2 自己紹介 • きしだ なおき • LINEヤフー • X(twitter):

    @kis • blog: きしだのHatena • (nowokay.hatenablog.com) • 「プロになるJava」というJavaの本を書いてます
  2. 2024/02/15 3 プロになるJava • Javaを使ったプログラミング入門書 • 単にJavaの文法解説書にならないように工夫 • 多くの入門書は単に文法解説になっている •

    文法を「覚えた」からといってプログラムが書ける わけではない • 入門書を読んだけどプログラムが書けない問題 • プログラムを書くには「プログラミング」という 能力が必要 • なるべく慣れや経験だけに頼らないで再現可能にする必要もある
  3. わかりますか? • x = (x = x + 1) +

    x • 「=」は変数に値を割り当てると共に、割り当てた値を返す演算子 • ここではどのように追えばいいかわかればOK
  4. わからないときの差は? • x = (x = x + 1) +

    x では「=」を式として使っている • しかしそれを知っただけでわかるわけではない • ではなぜそこでわからなくなる? • var y = x = 1は「=」を式として使っているけど理解しやすい
  5. x = x + 1の難しさ • プログラムには通常の式や文章と違うことが表されている • 演算、逐次実行、状態遷移が含まれている •

    プログラムの処理の本質 • 計算機も論理回路、クロック、フリップフロップでできている • x := x + 1や x + 1 → xのように表記を変えても解決になりにくい x = x + 1 演算 状態遷移 状態遷移 逐次実行
  6. 逐次実行をちゃんとやろう • 逐次実行はプログラムに特有 • 演算は算数でのトレーニングが活かせる • ここでの状態遷移は複雑ではない • 変数自体はわかる人が多い •

    変数、ループがわからないのは逐次実行がわかっていない • そこまでごまかせていたものが表出するだけ • x = (x = x + 1) + x がわからないのも
  7. 同じ処理を無限に繰り返す • 同じことを無限に繰り返すのはループの中で一番簡単 void main() { for (;;) { println("Hello");

    } } ※ Java 21ではクラスやメインメソッド引数が省略できるようになっています ※ 仕様機能なので--enable-previewが必要 ※ System.outの省略はまだ。今回は仮コード
  8. ループ変数の値を使う • 毎回やることが少し変わる void main() { for (int i =

    0; i < 5; i++) { println(STR."Hello \{i}"); } } ※ Java 21では文字列に式が埋め込めるようになっています ※ --enable-previewが必要
  9. ループによる値の集約 • ループを使って合計 void main() { int[] data ={23, 76,

    43, 9, 17, 32, 59}; var result = 0; for (int n : data) { result += n; } println(result); }
  10. 条件を全てが満たすかどうか • ループで実装 void main() { int[] data ={23, 76,

    43, 9, 17, 32, 59}; var result = true; for (int n : data) { result &= n % 2 == 1; } println(result); }
  11. 条件を満たす値のリスト • ループで実装 • 数値配列を動的に構築するときはIntStream.builder import java.util.stream.IntStream; void main() {

    int[] data ={23, 76, 43, 9, 17, 32, 59}; var result = IntStream.builder(); for (int n : data) { if (n % 2 == 1) continue; result.add(n); } var nums = result.build().toArray(); println(nums); }
  12. 集約の基本構造 • 共通の構造がある • 仕組み化できる • Stream var result =

    初期値 for (繰り返し) { 処理 resultへの追加処理 }
  13. Streamによる値の集約 • streamによる合計 • pythonではmap-filter-reduce • haskellではmap-filter-fold import java.util.stream.IntStream; void

    main() { int[] data ={23, 76, 43, 9, 17, 32, 59}; int result = IntStream.of(data) .sum(); println(result); }
  14. 条件を全てが満たすかどうか • streamによる判定 import java.util.stream.IntStream; void main() { int[] data

    ={23, 76, 43, 9, 17, 32, 59}; var result = IntStream.of(data) .allMatch(n -> n % 2 == 1); println(result); }
  15. 条件を満たす値のリスト • streamによるリスト生成 import java.util.stream.IntStream; void main() { int[] data

    ={23, 76, 43, 9, 17, 32, 59}; var result = IntStream.of(data) .filter(n -> n % 2 == 1) .toArray(); println(result); }
  16. 単位元 • 初期値は単位元になっている • 単位元 • a = e a

    = a e ★ ★ となる値a • 足し算の場合0 • 掛け算の場合1 • リストの結合の場合、空リスト 終端処理 初期値 更新処理 sum 0 + allMatch true & anyMatch false | toList new ArrayList() add
  17. Streamの利点 • for文が命令的なのに対してStreamは関数的 • 関数的≒ 宣言的 • 処理を追わなくてよい • ニンゲンは処理の記述を追うのが苦手

    • つまり、処理を追わずにループが読める! for (int n : data) { if (n % 2 == 1) continue; result.add(n); } var result = IntStream.of(data) .filter(n -> n % 2 == 1) .toArray();
  18. 移動平均 • 他のデータを参照する • 複数のデータで平均をとる • 平均をとる範囲が移動していく • なめらかにする •

    ローカットフィルタ • 例: 日次売上を7日で移動平均 • 曜日の影響を排除できる import java.util.Arrays; import java.util.stream.IntStream; void main() { int[] data ={23, 76, 43, 9, 17, 32, 59}; var builder = IntStream.builder(); for (int i = 0; i < data.length - 2; i++) { int sum = Arrays.stream(data, i, i + 3).sum(); builder.add(sum / 3); } var result = builder.build().toArray(); println(Arrays.toString(result)); }
  19. 隠れた状態さえ見つければ実装は簡単 • 割と素直 • コードから難しさが わかりにくい boolean check(String data) {

    int count = 0; for (var ch : data.toCharArray()) { switch (ch) { case '(' -> count++; case ')' -> { count--; if (count < 0) { return false; } } } } return count == 0; }
  20. 状態遷移のコード • 状態遷移図の実装 • 状態遷移図さえ書ければ コードは簡単 • 状態遷移図がなければ 読み解くのは難しい boolean

    check(String s) { enum State { START, ZERO, INT } var state = State.START; for (char ch : s.toCharArray()) { switch (state) { case START -> { if (ch == '0') state = State.ZERO; else if (ch >= '1' && ch <= '9') state = State.INT; else return false; } case ZERO -> { return false; } case INT -> { if (ch < '0' || ch > '9') return false; } } } return state != State.START; }
  21. クロックで実行処理を決める • プログラムカウンタを使う int counter = 0; void clock() {

    counter = (counter + 1) % 2; switch (counter) { case 0 -> println("hello"); case 1 -> println("world"); } } void main() { for (;;) clock(); }
  22. 回数の決まった再帰 • 条件をいれて止める void loop(int i) { if (i ->

    5) { return; } println(STR."Hello \{i}"); loop(i + 1); } void main() { loop(0); }
  23. 入れ子になった状態遷移 • 複数のカッコの対応を判定 • 再帰を使うと書きやすい • スタックの隠蔽 void check(String str)

    { idx = 0; println(STR."\{str} \{check(str, 0)}"); } int idx; boolean check(String str, int paren) { for (; idx < str.length(); idx++) { char ch = str.charAt(idx); switch (ch) { case '(', '{', '[' -> { idx++; if (!check(str,ch)) return false; } case ')', '}', ']' -> { return ch == paren + (paren == '(' ? 1 : 2); } } } return paren == 0; }
  24. 初期化のある再帰のパターン • 初期化がある場合 戻り値 処理(引数) { 初期化 処理impl(引数, 追加のデータ) }

    戻り値 処理impl(引数, 追加のデータ) { if (終了条件) { return 再帰を行わない値 } return 処理impl(加工した引数, 加工した追加のデータ) }
  25. 深さの変わるループ • 多重ループ • n重ループを実装するには? import static java.util.FormatProcessor.FMT; void main()

    { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { print(FMT."\{i}*\{j}=%2d\{i*j} "); } println(); } }
  26. 再帰による可変ループ • 再帰によって可変にする void loop(int n) { loopImpl(n, 0); }

    void loopImpl(int n, int c) { if (n == c) { println("なにか処理"); return; } for (int i = 0; i < n; ++i) { loopImpl(n, c + 1); } }
  27. AI時代にも価値を出せるように • ライブラリの使い方は「AI」がわかる • 単純な知識の蓄積に意味がなくなる • 検索で暗記の必要はなくなっていた • 手に覚えさせることも不要になりつつある •

    プログラムの処理が発想できること把握できることが大切 • 正しく表現できる、読み取れることが大切 • 発想したあとのコードは「AI」が書いてくれる