アルゴリズムに関する世界観です。データ構造も参照のこと。
アルゴリズムとは、コードブロック(プログラム)の全体の構造や流れのこと。
特に、ソートや探索など、ある目的のために順序立てて作られた、データの整理のプログラムのことを「アルゴリズム」と言うことが多い。
ソートや探索では、一時的な記憶領域(変数)にデータを格納し、繰り返し(反復して)データを移動・変更・書き換えることで、データ全体を順番に整理したり(ソート)、目的のデータを探索したり(探索)する。
場合と条件によって反復的にデータを書き換え、全体の処理が終わった時にきちんとソートができるか、といったところを数学的に考えてコードを作る必要がある。
ソートや探索に限らず、ある目的のためのコードブロックの流れや順番に沿った構造のことをアルゴリズムと呼ぶ。プログラミングは、このアルゴリズムを中心に行う。多くの場合一時的なデータを保管し、反復や条件分岐によって場合や条件を変えて書き換える。
アルゴリズムを自分で設計・実装する場合、条件分岐や反復構造の中で処理の流れを考える、「フローチャート」と呼ばれる図を描くことが、特に初心者にとっても、また上級者にとっても有効です。
実際のところ、数学的なアルゴリズムを自分で開発するのは、とても難しいことです。いきなりコードを描いてできることではありません。
フローチャートを描くことで、いつどのような条件で処理を実行し、実行内容をどのような手順(それも単純ではなく複雑な手順)で実行するのかを考えることができます。
ソートや探索だけではなく、文字列検索やメモリやデータベースなどのリソース管理などに必要なアルゴリズムも、フローチャートを描くことで書きやすくなるでしょう。
アルゴリズムに多いのが、ルーチンワークです。これは、「与える変数や参照・取得する変数だけを変えて同じルーチンを実行する」というものです。
与える変数とは、インクリメントされるカウント変数や、関数やブロックに引数などとして渡す、「一回一回変わるデータの参照先」です。
また、参照・取得する変数とは、データを参照・格納したり、場合によってはソートしたり探索したりする、「保持するデータの格納先」です。
ルーチンを書く時には、こうした変数をどのように捕捉するかを考えながら、どんな場合においても共通の「手順」を書きます。たとえば、ファイルの文字列を一行ごとに検索し、正規表現でマッチするパターンがあればカウントを増やす、あるいは何らかの処理を実行する(マッチした部分と回数のプリント出力など)といったことが記述できます。
そして、条件式やジャンプ命令などで、「この場合に終了する」とか、「この場合は処理を行わず次の処理を行う」などといった「中止命令」を内部に挿入します。場合によっては最後まで処理すれば停止するだけではなく、途中で何らかの文字列が含まれていれば即座にアボートする、などといったことが考えられます。
「アルゴリズム」とは、このようなルーチンの設計を指して言わることが多いです。
最近は、クラスライブラリのようなモジュールが既に用意されていたり、専用のSQLなどのデータベースサーバーがオープンソースで入手できたりすることから、本当にアルゴリズムを自分で書かなければならない場面というのは少なくなっています。
それでも、純粋な数学的記述を行ったり、自分で独自の処理を柔軟に行うために、画像処理やテキスト処理などを自分で独自に実装したり、あるいはハードウェアやバックエンドに近いカーネルやファイルシステムやデータベースなどのデータ構造を作ったり、GUIに近いところではHTMLレンダリングエンジンなどのコアのコンポーネントを作ったりするためには、自分でアルゴリズムを設計しなければなりません。
アルゴリズムの設計としてもっとも単純なのは、分岐命令や繰り返し命令を図にし、その図の中にプリントなどの処理や変数の操作を書く、フローチャートです。
また、オブジェクト指向モデリングやUML図においては、クラス図やシーケンス図を描くことで、行われる処理と「どのようなデータ構造を使うのか」を明確にし、クラス間の関係性や関連性を考えられます。
オブジェクト指向でアルゴリズムを設計するためには、デザインパターンも有効です。デザインパターンは多くの設計に共通する「デザイン上のよくあるパターン」を集めたもので、JavaだけではなくC#やPythonなどでアルゴリズムを書く時も、デザインパターンが参考になります。特にBuilderパターンはよく使われます。
また、アルゴリズムだけではなく、データ構造も重要です。ファイルシステムを実装する場合も、アルゴリズムだけではなく、iノードやダーティビットなどを作る上で、双方向リストなどのようなデータ構造を使ってどのようにアルゴリズムを書くかはとても重要です。スケジューリングやメモリ管理についても、たくさんのアルゴリズムがあると同時に、それと関連するひとつひとつのデータ構造があります。
僕は全然データ構造のこともアルゴリズムのことも詳しくありませんが、「バックエンドの開発でもフロントエンドの開発でもアルゴリズムとデータ構造の知識は必須」であると考えた方が良いでしょう。ライブラリやモジュールに頼るだけのプログラミングから、自分でライブラリやモジュールを書けるプログラミングへとレベルアップできるでしょう。
アルゴリズムというと、「抽象的で数学的な方法や手順である」とされることが多いですが、実際のプログラミングにおいては、ハードウェアにとても近いところでもアルゴリズムが多くみられます。
たとえば、Linuxカーネルのメモリ管理のアルゴリズムや、CPUスケジューリングのアルゴリズムなどが代表的です。
具体例をひとつ挙げると、CPUスケジューリング・アルゴリズムとして、「ラウンドロビン」と呼ばれるアルゴリズムがあります。これは、複数のプロセスにひとつひとつ順番に交代してCPUのリソース資源を割り当てるアルゴリズムです。また、これ以外にもスケジューリングのアルゴリズムはあります。以下を参照してください。
このように、アルゴリズムは必ずしも数学的ではなく、極めてハードウェアに近い部分でも多く見られます。OSカーネルやバックエンドを学ぶ上では、こうしたアルゴリズムの知識が欠かせません。
データベースのようなバックエンドは、アルゴリズムが分かっていると、独自に設計して開発することができます。
これは、たとえばスケーラビリティが必要な場合(クラスタ・スーパーコンピュータからモバイルやゲームまでスケールが大きい・小さい場合に独自に実装する)とか、扱うプラットフォームやシステム構成が特殊だったりする場合や、独自の機能が必要な場合(ネットワークやセキュリティや並列性など)、あるいは信頼性や性能で妥協を許さない場合などに有効だと思います。
実際は僕もやったことがないので分かりませんが、MySQLやPostgreSQLでは対応できない状況というのはあるでしょう。
こうした場合にデータベースのインデックスや検索などのアルゴリズムを知っておくと、自社でデータベースに必要な機能を取捨選択することができます。
あるいは、自社でデータセンターを運用したい場合などに、自社でデータベース管理システムを「その決まりや作法に従ったスタイルで」構築したい場合もあると思います。リレーショナルデータベースだけがデータモデルではありません。Amazonなども、AWSのデータベース管理システムをOracleから独自実装に切り替えたと話題になりました。今どうなっているかは知りません。
RDBMSも参照のこと。
なぜ、Linuxカーネルの効率が高いのか、それはメモリやディスク、ネットワークといったリソースを効率的に使っているからです。
そして、プログラムの効率を高めるためにはどうすればいいか、それはボトルネックを解消することです。
プログラムのソースコードの中で、本当に時間のかかる部分というのは、全体ではなく、ほんの一部分であることがあります。全体が遅く動いているのではなく、一部分だけが遅くなって、全体の速度の多くを遅くしているから、そのプログラムは非効率的なのです。これを「ボトルネック」と言います。
プログラムのボトルネックを解消するためには、まずは、重要な部分、特にアルゴリズム部分を見ることです。全体をPythonからCで書き直しても、確かに速度は向上するかもしれません。しかしながら、全体を書き直すことだけでは、「アルゴリズム部分のボトルネック」を解消することができません。アルゴリズムで無駄があった場合、そこがボトルネックになっている場合、低水準言語で書き直すことよりも、まずはアルゴリズム部分に注目して、どのように変えるか、たとえばキャッシュを使ったり並列処理を行ったりすることで効率的にできるか、といったことが肝心になります。
しかしながら、並列処理を行う場合でも、並列処理にできない順序が変えられない処理がもしあったとして、それが全体のボトルネックになっている場合、並列処理だけでは解決しません。キャッシュで効率的になるのは同じデータに繰り返しアクセスする時だけであり、データベースにインデックスをつけるのと同様、間違えれば逆効果になることもあるでしょう。
プログラムを効率的にするためには、まずはアルゴリズムを考えましょう。二分探索木を使うなどのデータ構造的な設計や実装を変えたりすることでも高速化することはあります。
ソフトウェアを高速化する際には、ボトルネックとなっている部分を探します。
ボトルネックとは「瓶の首」の意味で、その部分が全体のほとんどの速度を制限しているような部分のことです。
たとえば、以下にRubyの作者まつもとゆきひろによるRuby三倍高速化についてのインタビュー記事があり、参考になります。
また、プログラムの実行時間の50%はコードブロックの4%以下の部分に集中しているとする、クヌース先生の論文は有名である。
クヌース先生は「早すぎる最適化は諸悪の根源」(最適化は時期を見て行うこと)などとも言っていて、勉強になる。
文字をひとつひとつ比較するなど、なんて陰気臭いことをやっているのだろうと、思われるかもしれません。
ですが、コンピュータは、どんなに細かいことであっても、文句を言わず正確にやってくれます。
また、たくさんのデータがあった場合も、すべてのデータに対して正確に処理を行ってくれます。
それから、再利用性があります。一度きちんと動くロジックを書いてしまえば、二度同じことを書く必要はありません。ひとつに対する正しいロジックを書けば、それを再利用してすべてに対するサブルーチンとすることができます。
文字をひとつひとつ比較するロジックであったとしても、それを正確に、間違えることなく、そして大量に行ってくれると考えれば、きちんと書く価値はあります。
以下の書籍が参考になります。
僕がプログラミングができないのは、アルゴリズムが書けないからです。
プログラミング言語は、仕様を学ぶためにあるのではなく、アルゴリズムを書くためにあります。
変数を定義して代入したり加算したり、if文やfor文で制御機構を作ったり条件式を比較したりするのは、それ自体の意味を学ぶためではなく、アルゴリズムを書くためにあります。
そして、僕はそうしたアルゴリズムを書くことができないせいで、プログラミングのマスターから遠ざかっています。
皆さんは、僕のように、学ぶだけでアルゴリズムを書くことができない人間には決してならないようにしてください。
僕は、今から、アルゴリズムを書く術を学びます。技術とは名前の通り「技」と「術」であり、決して勉強して分かるだけがパソコンの学習の目的ではありません。皆さんは、僕のようにならず、アルゴリズムを書く術をきちんと身に着けるようにしてください。
2023.09.27
結局、コンピュータはただの機械です。
コンピュータは、アルゴリズムを計算する機械であり、そのアルゴリズムの計算は、普通のアナログな機械が動くのと何も変わりません。
アルゴリズムは、機械的に、条件が満たされるまで同じ計算を繰り返す、ということを行うだけです。
物質的な機械をハードウェア、プログラムをソフトウェアと呼ぶのは、ソフトウェアはハードウェアと違って具体的な物質的な装置を持ちませんが、やっていることはソフトウェアもハードウェアも変わらないからです。
機械的なモーターや歯車を使ったハードウェアが動くのと、電子頭脳的なソフトウェアが動くのは、何も変わりません。両者ともに同じ機械だから、「ハードウェア」「ソフトウェア」という似たような名称を付けただけです。
アルゴリズムは、それを理解するのが難しいだけで、一度理解してしまえば、その理解に基づいて、正しく論理的に記述するだけできちんと動きます。
アルゴリズムの基本は「状態を変更する」ということです。そのプログラムの中で状態を保持し、その状態を目的の結果になるまで変更処理をし続ける、これがコンピュータという言葉の意味する「計算」なのです。
2023.09.30
アルゴリズムとデータ構造は、実際の高度なプログラミングの基本を掴むために、学んでおくと良いでしょう。
アルゴリズムとデータ構造。