ソフトウェアは、多くのユーザに対して同等の体験を提供するという役目があります。n次元では、「ユーザ体験は繰り返し」と言ったりします。
ソフトウェアのソースコードを見ていると、複数のユーザやデータを取ってきて、ループ(繰り返し)させるようなコードがよく出てきます。まさにユーザ体験は繰り返しなのです。
なので、プログラミングでは、複数の要素を集合的に扱うことが必要になってきます。集合的な要素を扱うことは、プログラマのスキルの中でも必須項目なのです。
この機会にきちーっと勉強していきましょう。
集合ってなんだったっけ?
集合というと高校時代、数学で出てきてアレルギーになっている人も多いかもしれません。集合は、あまり受験でも重視されていないようで、「集合が出てきたら捨て問題」のように言われたりしてました。本当は数学の最も根幹となるような概念なのに残念です。
しかし、理解すべきことは実は非常に簡単です。集合というのは、扱う世界の広さを制限します。集合は世界の広さ・狭さだと考えてみてください。
ただ、集合は、広ければ広いほど良いというものではありません。
郡(ぐん・Group)、環(かん・Ring)、体(たい・Field)の様に集合を拡張することがありますが、この拡張は、より優れたものにするということを意味するわけではありません。集合の広さは、制限することで扱いやすくなったり、より面白く世界を開拓していけるものなのです。
例えば、同じ足を使うスポーツでも、サッカーとフットサルがあります。フットサルは、サッカーよりも狭いコートで構成されるスポーツですが、フットサルの方が面白くないとはならないでしょう。
狭いには狭いなりの面白さがあります。人数が少なく、コートが狭ければ一人ひとりの働きがより重要になったり、初心者でもボールに触れやすかったりするメリットもあります。
郡、環、体とある中で一番狭い集合を扱う、郡は、群論(ぐんろん)という数学の分野になって研究されています。また、体の中でも有限集合のみを話題を制限して有限体(ゆうげんたい、ガロア体、Galois Field)とし、こちらも非常に研究されています。
集合は、数学の定義の中の一部で、ルールなるものです。ルールを縛るからその中で楽しむことができる。集合という概念がなければ、数学はそもそもなかったでしょう。
桃太郎で学ぶ「集合」
僕と皆さんで桃太郎の演劇をやるとします。そのときに、まず考えるのが登場人物の整理ですね。桃太郎に出てくる登場人物は、次の6人(匹)です。面倒なので、今後、動物も単位を「人」とします。A = { 桃太郎, おじいさん, おばあさん, さる, キジ, 犬, オニ }
ここから、オニ退治に出る人を取り出すと、
B = { 桃太郎, さる, キジ, 犬 }
となります。これを部分集合 (Subset) と呼びます。B ⊂ A と書きます。人や動物の全体集合を U とすると、 A ⊂ U, B ⊂ U となります。
ここで、重要なのが、A において、オニは何人かということはどうでもよいということです。実際は、オニは10人いるかもしれませんが、とりあえず、A では登場人物の種類のみに注目したということです。
ここで、数も一緒に考えた方が便利じゃないかと思う人もいるかもしれません。素晴らしい想像力ですね。もちろん、それは可能です。
A' = { { 桃太郎, 1 }, { おじいさん, 1 }, { おばあさん, 1 }, { さる, 1 }, { キジ, 1 }, { 犬, 1 }, { オニ. 10 } }
という形で管理すれば、なんとか人数とペアで、登場人物を管理できるようになりました。一見、こっちの方が便利な気がします。しかし、本当でしょうか。
もし、僕らの作った演劇のクライマックスがあまりにもしょぼかったら、さらにオニを追加したくなるでしょう。では、 A' に { オニ, 5 } を追加してみましょう。
A'' = A' ∪ { オニ, 5 }
= { { 桃太郎, 1 }, { おじいさん, 1 }, { おばあさん, 1 }, { さる, 1 }, { キジ, 1 }, { 犬, 1 }, { オニ. 10 }, { オニ. 5 } }
あれ、求める結果となんだか違うような気がします。これは、{ オニ. 10 } と { オニ. 5 } が異なる要素なので、別々の登場人物として認識されてしまったからです。
ここで、理解したいのは、今回の登場人物と人数の管理の仕方が間違っていたということです。前半は、登場人物の種類が把握できればよかったので、集合はすごく上手く機能してくれました。素晴らしいです。
しかし、登場人物と人数の管理に関しては、別の方法があったのではないでしょうか。経験の浅いプログラマは、概してこのような間違った設計を選びがちです。
知っている道具が少ないので、より適切なものがあっても気付かず、つい使い慣れたもので作業してしまうのです。解決方法は簡単ですね。道具の数を増やせば良いのです。
では、順番にその道具を見ていきましょう。
集合 (Set)
先ほどやりましたね。プログラミング言語として集合を扱う場合、同じものが入らないことを保証します。A = { 1, 2, 4 }
としたときに、
A に 5 を追加すると、
A = { 1, 2, 4, 5 }
となります。
再度、 A に 5 を追加しようとしても、
A = { 1, 2, 4, 5 }
のままです。複数のユーザに対して、1回ずつ繰り返し体験を届けたいときに使いやすいです。ユーザIDを集合に入れていくことで、同じユーザIDは、集合に2つ以上入れることができないため、重複を防ぐことができます。
集合は非常に便利ですが、プログラミング言語によっては、使える言語は限られています。
ペア (Pair)
次に、ペアです。ペアは簡単で、2つしか要素を入れることができません。
(ysawa, ブロマガ担当)
とか
(Shun, 動画担当)
のように、2つを組み合わせてなにか情報を整理したいときに使います。
シンプル過ぎて、使う場面がないまたは使いにくそうな印象を受けますが、Lisp や Scheme などの関数型言語では、非常に重要な型になります。
Lisp や、Scheme では、基本的にペアを組み合わせて、次の節で述べるリストを構成します。ペアは、集合的な要素を考える基礎となっているわけです。
Lisp や Scheme では、ペアは、以下のように作ります。
(cons 1 2)
=> (1 . 2)
1 と 2をペアにしたわけです。それぞれの要素を取り出すときはこうです。
(car (cons 1 2))
=> 1
(cdr (cons 1 2))
=> 2
となります。car (カー) という関数、cdr (クドゥアー) という関数を使って取り出すわけです。
ペアと、次に述べるリストや配列を組み合わせると連想配列ができあがります。Haskell ではよくこの型を使います。
(Haskell ではペアという用語はなく、 タプル (Tuple) というような有限個の要素を代わりに使います。)