LoginSignup
2
1

More than 3 years have passed since last update.

nCrとnPrを求める

Last updated at Posted at 2019-09-24

はじめに

以前書いた記事「論理パズルをプログラムで解く」で、組み合わせ(nCr)を求める記事を書きました。ここから派生させて順列(nPr)を求める方法を書きます。
Swiftを使用しています。

1 やりかた

やりかたはきっと色々あるのでしょうが、組み合わせを求めてその結果をもとに順列を求めます。

2 組み合わせ(nCr)

※以前書いた記事「論理パズルをプログラムで解く」のコピペです。
「集合n」から「r人」取り出すので、この2つを引数として「集合の集合」を戻り値とする関数とします。
①引数nの要素数の数繰り返す関数をr回再帰させます。${}_4C_2$であれば、$4^2$回の計算を行い、それぞれの集合を作っていきます。
②出来上がった集合のうち、要素数がrのものだけ、戻り値の集合に入れます。
③戻り値の集合は、重複を排除するので、最終的にはユニークな集合のみが残ります。

nCr.swift
import UIKit

func nCr<T>(n:Set<T>,r:Int)->Set<Set<T>>{                  //引数nは集合、rは整数、戻り値は集合の集合(ジェネリクス)
    var results:Set<Set<T>> = []                           //戻り値用の変数(results)
    func rec(itr:Int,result:Set<T>){                       //再帰関数。引数は再帰回数itrと作成中の集合result
        guard itr<r else{                                  //他の言語のwhileみたいなの。rがitrと同じになれば再帰終了。
            result.count == r ? results.insert(result):nil //出来上がった集合resultの要素数がrであれば、resultsに代入
            return
        }
        for num:T in n{                                    //関数nCrの要素をひとつずつ取り出す。
            var myResult:Set<T> = result                   //次の再帰関数の引数用の集合myResultを用意
            myResult.insert(num)                           //myResultに集合nから取り出した要素numを追加
            rec(itr: itr + 1, result: myResult)            //再帰させる
        }
    }
    rec(itr: 0, result: [])                                //再帰関数の開始
    return results
}

ためしに[1,2,5,10]から2つ取り出す組み合わせを作ってみましょう。${}_4C_2$=6種類の集合ができるはずです。

print(nCr(n: [1,2,5,10], r: 2))
//[Set([1, 10]), Set([10, 2]), Set([10, 5]), Set([2, 1]), Set([5, 1]), Set([2, 5])]

うまくできました。
ちなみに、汎用性を高めるために戻り値はジェネリクスにしていますので、「6人のクラスの中から3人選ぶ」みたいな組み合わせもこんな感じでできます。

print(nCr(n: ["佐藤","鈴木","吉田","高橋","山本","斎藤"],r: 3))
//[Set(["山本", "高橋", "吉田"]), Set(["鈴木", "佐藤", "吉田"]), Set(["鈴木", "高橋", "吉田"]), Set(["吉田", "佐藤", "高橋"]), Set(["吉田", "斎藤", "高橋"]), Set(["鈴木", "山本", "高橋"]), Set(["鈴木", "佐藤", "高橋"]), Set(["山本", "佐藤", "高橋"]), Set(["鈴木", "斎藤", "吉田"]), Set(["山本", "佐藤", "斎藤"]), Set(["山本", "斎藤", "吉田"]), Set(["鈴木", "佐藤", "斎藤"]), Set(["鈴木", "山本", "佐藤"]), Set(["吉田", "佐藤", "斎藤"]), Set(["鈴木", "山本", "吉田"]), Set(["鈴木", "斎藤", "高橋"]), Set(["山本", "斎藤", "高橋"]), Set(["鈴木", "山本", "斎藤"]), Set(["高橋", "佐藤", "斎藤"]), Set(["山本", "佐藤", "吉田"])]

${}_6C_3$=20種類の集合ができました。

3 順列(nPr)

順列は、組み合わせで作成した集合に更に順番を考えればいいだけです。よって、関数nCrの答えをそのまま受け取って、要素数に応じた順番を作成する関数を作ればよいことになります。

nPr.swift
import UIKit
func nCr<T>(n:Set<T>,r:Int)->Set<Set<T>>{                  //引数nは集合、rは整数、戻り値は集合の集合(ジェネリクス)
    var results:Set<Set<T>> = []                           //戻り値用の変数(results)
    func rec(itr:Int,result:Set<T>){                       //再帰関数。引数は再帰回数itrと作成中の集合result
        guard itr<r else{                                  //他の言語のwhileみたいなの。rがitrと同じになれば再帰終了。
            result.count == r ? results.insert(result):nil //出来上がった集合resultの要素数がrであれば、resultsに代入
            return
        }
        for num:T in n{                                    //関数nCrの要素をひとつずつ取り出す。
            var myResult:Set<T> = result                   //次の再帰関数の引数用の集合myResultを用意
            myResult.insert(num)                           //myResultに集合nから取り出した要素numを追加
            rec(itr: itr + 1, result: myResult)            //再帰させる
        }
    }
    rec(itr: 0, result: [])                                //再帰関数の開始
    return results
}

func nPr<T>(n:Set<T>,r:Int)->Set<[T]>{//nCrはSet<Set<T>>だけど、nPrは、Set<[T]>が戻り値
    var results:Set<[T]> = []                           //戻り値用の変数(results)
    func rec(myArray:[T],result:[T]){//再帰関数
        guard myArray.count != 0 else{
            results.insert(result)
            return
        }
        for i in 0..<myArray.count{//与えられた配列から要素を取り出して
            var myNewArray = myArray//変数にして処理
            myNewArray.remove(at: i)
            var myResult = result
            myResult.append(myArray[i])
            rec(myArray: myNewArray, result: myResult)//再帰させる
        }
    }

    for mySet:Set<T> in nCr(n: n, r: r){//関数nCrで集合の集合を作って分解
        var myArray:[T] = mySet.map({(myValue) in myValue})//mapを使って、集合を配列にする
        rec(myArray: myArray, result: [])//再帰開始
    }
    return results
}

ためしてみます。

print(nPr(n: ["佐藤","鈴木","吉田","高橋","山本","斎藤"],r: 3)

スクリーンショット_2019_09_24_19_27.jpg

${}_6P_3$=120種類の集合ができました。

2
1
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
2
1