「permutation」「combination」メソッドによる順列/組み合わせの数え上げ

f:id:ryoutaku_jo:20190708223534p:plain

【結論】

permutation メソッドは、配列から引数n個の要素を選んだときの順列を数え上げる。

combinationメソッドは、配列から引数n個の要素を選んだときの組合せを数え上げる。

・要素の重複を許可する場合はrepeated_combination メソッド(repeated_permutationメソッド )を利用する。

【目次】

【本題】

順列と組み合わせの違い

Rubyには、配列から順列・組み合わせと数え上げるメソッドが存在します。

それらの解説の前に、それぞれの違いについて説明します。

「順列」「組み合わせ」は、以下の様に定義されています。

・順列 n個の中から、r個を順番に取り出して並べる総数。

・組み合わせ n個の集合の中からm個を取り出す組合せ

これだけだと分かりづらいですが、簡単に言うと「順序違いによる重複を許可するか?しないか?」の違いです。

例えば「1・2・3・4・5」の5枚のカードがあるとします。

この5枚のカードについて、2種類の問題を出します。

1:「このカードから2枚を選び、2ケタの数字を作る場合、作れる数字は何通りあるか?」 2:「このカードから2枚を選ぶ場合、選び方は何通りあるか?」

それぞれ同じ問題に見えますが、答えが異なります。

1の答えが「20通り」です。 2の答えが「10通り」です。

1の場合、「1・2」と「2・1」という順番が違うが組み合わせが同じ場合でも、数にカウントされます。 2の場合、「1・2」と「2・1」は、同じ組み合わせ(重複)と見なされて、片方が数にカウントされません。

その為、この様な違いが生まれています。

permutationメソッド

permutationメソッドは、配列から引数n個の要素を選んだときの順列を数え上げます。

numbers = [1, 2, 3, 4, 5]
numbers.permutation(2) {|a, b| printf("(%d, %d) ", a, b) }
=> (1, 2) (1, 3) (1, 4) (1, 5) (2, 1) (2, 3) (2, 4) (2, 5) (3, 1) (3, 2) (3, 4) (3, 5) (4, 1) (4, 2) (4, 3) (4, 5) (5, 1) (5, 2) (5, 3) (5, 4)

combinationメソッド

combinationメソッドは、配列から引数n個の要素を選んだときの組合せを数え上げる。

numbers = [1, 2, 3, 4, 5]
numbers.combination(2) {|a, b| printf("(%d, %d) ", a, b) }
=> (1, 2) (1, 3) (1, 4) (1, 5) (2, 3) (2, 4) (2, 5) (3, 4) (3, 5) (4, 5)

要素の重複

なお先ほどのメソッドだと、同じ要素の重複を許可していません。

要素の重複を許可する場合はrepeated_combination メソッド(repeated_permutationメソッド )を利用します。

numbers = [1, 2, 3, 4, 5]
numbers.repeated_permutation(2) {|a, b| printf("(%d, %d) ", a, b) }
=> (1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (2, 1) (2, 2) (2, 3) (2, 4) (2, 5) (3, 1) (3, 2) (3, 3) (3, 4) (3, 5) (4, 1) (4, 2) (4, 3) (4, 4) (4, 5) (5, 1) (5, 2) (5, 3) (5, 4) (5, 5)
numbers = [1, 2, 3, 4, 5]
numbers.repeated_combination(2) {|a, b| printf("(%d, %d) ", a, b) }
=> (1, 1) (1, 2) (1, 3) (1, 4) (1, 5) (2, 2) (2, 3) (2, 4) (2, 5) (3, 3) (3, 4) (3, 5) (4, 4) (4, 5) (5, 5)

参考情報

permutation (Array) - Rubyリファレンス

combination (Array) - Rubyリファレンス

repeated_combination (Array) - Rubyリファレンス

repeated_combination (Array) - Rubyリファレンス

《今日の学習進捗(3年以内に10000時間に向けて)》

本日、CTOとの1on1にて、最近タスクが遅延してしまっている事を相談したが、そこで「システムだけでなく運用と併せて要件を満たせれば良い」というアドバイスを頂き、それが非常に参考になった。

具体例として、実際にタスクが遅延した「ユーザー削除機能」をあげる。

「ユーザー削除」の実装においては、誤って削除したり、退会したユーザーの投稿内容を確認したい場合に備えて、ユーザーの復元が行える様に「論理削除」での実装を試みた。 論理削除は開発の難易度が高く、実装できてもパフォーマンスや拡張性に難が出る事が予想された為、それらの問題に対処する方法の調査に時間を割く結果となった。 しかし、要件を洗い直すと、UIを工夫するなどすれば、物理削除で十分にニーズを満たせる(むしろ論理削除では満たせない)ということが判明し、論理削除に割いた工数が無駄となってしまった。

こうなってしまった原因は、システムだけで要件を満たそうとした事にあった。 本来ソフトウェアの要件を定める際は、システムで対処できない事象は、運用でカバーするという様な、システムと運用の両方で要件を満たすものである。 今回で言えば、誤って削除する恐れがあるのであれば、ボタン操作だけで削除出来ない様に入力操作を要求したり、削除確認ページを設けるといった事で要件を満たすことが出来た。 にも関わらず、システムだけで解決する事に拘った結果、論理削除の実装に執着してしまった。

更に言えば、「誤って削除したり、退会したユーザーの投稿内容を確認したい場合」というのは仮定の話で、現実に発生している問題では無いので、発生する頻度が高いと見込まれない問題に、多くのコストを割くべきでは無かった。

今後機能実装を行う際は、その機能が求められている理由/背景を掘り下げて、本当に満たすべき要件を定義し、システム・運用の両面で実装方法を検討し、最も費用対効果の高い実装方法を定めてから、開発に着手する様にしたい。

また、こういった運用の工夫については、C向け・B向け問わず、様々なサービスを実際に利用してみて、そこからノウハウを得るのが最も効果的だともアドバイス頂いた。 なので、様々なサービスに触れる機会を増やすとともに、それらが運用面でどういった工夫を行っているか?という観点で利用する様にしていきたい。

それと、昨日が七夕だったと、さっき気づいた。 神頼みが出来なくなったので、自力で自社サービスの成功に向けて開発に努める。

学習開始からの期間 :213日
今日までの合計時間:2060h
一日あたりの平均学習時間:9.7h
今日までに到達すべき目標時間:1945h
目標との解離:115h
「10,000時間」まで、

残り・・・「7940時間!」