mike-neckのブログ

Java or Groovy or Swift or Golang

婚姻成立数のシミュレーションなんていらんかったという話

はてブ見てたら、こんなのあった。

juunnnnnnnn.hatenablog.com

この手の無駄な計算は好きなので、プログラムを少し書いてみたところで、これ単なる組み合わせの問題じゃね?とおもったところ、すでにこんなツイートが…

で、もう少し考えれば、

婚姻成立する組み合わせの数はNチームが参加する総当り戦の試合数

であると一般化できます。

1 2 3 N
1 × × × × ×
2 × × × ×
3 × × ×
N ×

つまり、

N * (N - 1) / 2

が成立する組み合わせの数。

これに対してN * Nが組み合わせの総数。

よってその確率は

N * (N - 1) / (N * N * 2) = (N - 1) / (2 * N)

なお、生成されるであろうカップルの数はNなので、件の期待値は(N - 1) / 2

N -> ∞の極限を取れば、0.5なので、なんかそれほど難しいシミュレーションではなかった。

元のブログにあるように

やはり女性には、男の性格も見てほしいものです。

を考慮して、出題はこうするべきでしたね。

各N人の男女にそれぞれ1~Nの年収があり、恋愛は男の告白から始まると仮定、競争力の高い男10から1に向かって順におっぱいの大きさ(ランダム)で告白、男がブサメンの場合:男の年収>女の年収 * 3、男がフツメンの場合:男の年収>女の年収、男がイケメンの場合:男の年収>女の年収 / 3となった場合に婚姻が成立して婚活から離脱していくとした場合の婚姻成立数の期待値

イケメン:フツメン:ブサメンは1:3:2の数で分布するものとする

(なお、ここでのフツメン、ブサメン、イケメンは性格上のイケメン度とする)

おわり


【2015/04/11 10:48 追記】

上記の問題を若干修正して、年収からの倍率を婚姻成立条件にしました。

  • 男がブサメン:男の年収>女の年収 * 3
  • 男がフツメン:男の年収>女の年収
  • 男がイケメン:男の年収>女の年収 / 3

で、その問題をシミュレートするプログラムは次の通り。

ArrayList.metaClass.define {
    randomQueue = {
        def s = delegate.size()
        def col = delegate.collect{it}.sort{new Random().nextInt(s)}
        new ArrayDeque(col)
    }
}

enum Type {
    IKEMEN{
        @Override double acceptableIncome(int income) {income / 2 as double}
    }, FUTSUMEN{
        @Override double acceptableIncome(int income) {income}
    }, BUSAMEN {
        @Override double acceptableIncome(int income) {income * 3}
    }
    abstract double acceptableIncome(int income)
    static Type random() {
        def r = new Random().nextInt(6)
        r < 3? FUTSUMEN: r < 5? BUSAMEN: IKEMEN
    }
}

abstract class BusinessPerson {
    final int income
    BusinessPerson(int income) {
        this.income = income
    }
}

class Man extends BusinessPerson {
    final Type type
    Man(int income) {
        super(income)
        type = Type.random()
    }
    @Override String toString() {
        "Man($type : $income)"
    }
}

class Woman extends BusinessPerson {
    Woman(int income) {super(income)}
    boolean accept(Man man) {
        man.income > man.type.acceptableIncome(this.income)
    }
    @Override String toString() {
        "Woman($income)"
    }
}

class Pair {
    Woman woman
    Man man
    Pair(Woman woman, Man man) {
        this.woman = woman
        this.man = man
    }
    boolean complete() {
        woman.accept(man)
    }
    @Override String toString() {
        "$woman - $man : ${complete()}"
    }
}

(1..100000).collect {
    def women = (1..10).collect{new Woman(it)}.randomQueue()
    (1..10).collect{new Man(it)}.sort{-it.income}.collect {
        new Pair(women.poll(), it)
    }.findAll{it.complete()}.size()
}.sum()/100000

なお、実行結果は上記の100,000回試行で0.4くらいに収束します。

多分、高校数学で簡単に解けると思われるけど、確率統計が苦手なのでコンピューターに計算させた(´・ω・`)


【追記 2015/04/12 8:08】

ついでだから、イケメン、フツメン、ブサメンごとの成立数/不成立数を調べてみた。

最後の計算部分の少し前に次のようなクラスを追加

@EqualsAndHashCode
class ResultKey implements Comparable<ResultKey> {
    final Type type
    final boolean completed
    ResultKey(Type type, boolean completed) {
        this.type = type
        this.completed = completed
    }
    @Override String toString() {
        "${String.format('%-9s', type)} [${completed ? ' 成立 ' : '不成立'}]"
    }
    @Override int compareTo(ResultKey that) {
        if (this.type.ordinal() != that.type.ordinal()) {
            return Integer.compare(this.type.ordinal(), that.type.ordinal())
        } else {
            - Boolean.compare(this.completed, that.completed)
        }
    }
}

class Result implements Comparable<Result> {
    final ResultKey key
    final int count
    Result(ResultKey key, int count) {
        this.key = key
        this.count = count
    }
    @Override String toString() {
        "[key: [$key], count: $count]"
    }
    @Override int compareTo(Result that) {
        this.key.compareTo(that.key)
    }
}

計算部分を次のように修正

(1..12000).collect {
    def women = (1..10).collect{new Woman(it)}.randomQueue()
    (1..10).collect{new Man(it)}.sort{-it.income}.collect {
        new Pair(women.poll(), it)
    }.groupBy{new ResultKey(it.man.type, it.complete())}.sort{it.key.type.ordinal()}.collect {
        new Result(it.key, it.value.size())
    }
}.flatten().groupBy{it.key}.collect{
    new Result(it.key, it.value.collect{it.count}.sum())
}.sort().each {
    println "${it.key} -> ${String.format('%6d[%5.2f%s]', it.count, 100*it.count/120000 as double, '%')}"
}

実行結果

IKEMEN    [ 成立 ] ->  14665[12.22%]
IKEMEN    [不成立] ->   5430[ 4.53%]
FUTSUMEN  [ 成立 ] ->  27283[22.74%]
FUTSUMEN  [不成立] ->  32647[27.21%]
BUSAMEN   [ 成立 ] ->   6216[ 5.18%]
BUSAMEN   [不成立] ->  33759[28.13%]

イケメンでも金がなければ結婚できないし、ブサメンでも札束で殴れば結婚できるということですね