LoginSignup
9
7

More than 3 years have passed since last update.

組み合わせの列挙

Last updated at Posted at 2016-12-20

組み合わせを列挙しようとして手が止まりました。基本的なアルゴリズムのはずなのに、いざ実装しようとすると思いのほか難しいです。検索してみると同じように感じた方が多いようで、色々な実装が出て来ました。屋上屋を架すことにはなりますが、試みた実装を説明します。

【追記 2020.01.05】実装を大幅に変更しました。

方針

手作業で組み合わせを列挙する場合、次のように考えます。

【例】 {1,2,3,4} から2個を取り出した組み合わせ

組み合わせ.png

これをなるべく素直に実装してみようと考えました。

特徴

挙動を観察すると、次のような特徴が分かります。

  • 左の数字<右の数字(組み合わせでは順番を区別しないため、便宜的な取り決め)
  • {1,2,3}のそれぞれに、それより大きい数字を組み合わせます。{4}はそれより大きい数字がないため左には来ません。

実装

1つ小さい数字までの組み合わせ combination (n - 1) (k - 1) の末尾に、それより大きい数字を追加します。

let rec combination n = function
| k when k < 1 -> []
| 1 -> [ for i in 1..n -> [i] ]
| k -> [ for c in combination (n - 1) (k - 1) do
         for i in (List.max c) + 1 .. n -> List.append c [i] ]

printfn "%A" (combination 4 2)
実行結果
[[1; 2]; [1; 3]; [1; 4]; [2; 3]; [2; 4]; [3; 4]]

メモ化

nk が大きくなると構築に時間が掛かるようになります。プログラム中で何度も使う場合、結果をキャッシュしておくと効果的です。これをメモ化と呼びます。

F#
let memoize (f: 'k -> 'v) =
    let cache = System.Collections.Generic.Dictionary<'k, 'v>()
    fun a ->
        let b, v = cache.TryGetValue a
        if b then v else
        let ret = f a
        cache.[a] <- ret
        ret

let   curry f = fun a  b  -> f(a, b)
let uncurry f = fun(a, b) -> f a  b

let combination =
    let rec f n = function
    | k when k < 1 -> []
    | 1 -> [ for i in 1..n -> [i] ]
    | k -> [ for c in g (n - 1) (k - 1) do
             for i in (List.max c) + 1 .. n -> List.append c [i] ]
    and g = f |> uncurry |> memoize |> curry
    g

再帰の結果もキャッシュするため相互再帰しています。

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7