重複円順列の個数をJuliaで解決!
はじめに
2023年5月7日にJuliaのバージョンが1.9.0となりました!こういう時には何かやってみたくなりますね。
現在,中学3年生の数学を教えています。分野や高校数学Aの「場合の数・確率」です。
Twitterなどでは定期的に円順列の話題が挙がってきます。その中で @ysmemoirsさんのツイートで次のようなものがありました。
これは,やらねば!と思い,まず手計算してツイートしました。
なかなか検討が進まないので,Juliaで確認することにしました。
同じものを含む円順列
以前,サイトの方で同じものを含む円順列については,まとめました。
コード1
同じものの個数がわかっていれば,その円順列の個数を求めることができます。
using Combinatorics #multinomial(a)多項係数を求める
using Primes #totient(i) オイラーのトーシェント関数
function divisors(n) #約数のリストを求める関数
X=[]
for i=1:n
if n % i==0
X =push!(X,i)
end
end
X
end
function enkan(a)
l=gcd(a) #リスト(配列)aの最大公約数を求める。
N=sum(a) #aの総和
A=divisors(l)
p=0
for k in A
q=map(x -> x÷k,a)
p +=totient(k)*multinomial(q...)
end
p÷N
end
例:白玉4個,黒玉6個を円形に並べる総数
enkan([4,6])
22
と求めることができます。
コード2
上記のコード1とは別に全ての順列のリストを求めることもできます。
# seq(同じものでもOK)のリストからk個選んで円順列を作り,そのリストを返す。
function circperm(seq, k)
p = union(permutations(seq, k))
n = length(p)
d = []
for i = 1:n-1, j = i+1:n, t = 1:k-1
if p[i] == circshift(p[j], t)
push!(d, j)
end
end
deleteat!(p, sort!(union!(d)))
end
例:白玉4個,黒玉6個を円形に並べる順列のリスト
circperm([1,1,1,1,2,2,2,2,2,2],10)
22-element Vector{Vector{Int64}}:
[1, 1, 1, 1, 2, 2, 2, 2, 2, 2]
[1, 1, 1, 2, 1, 2, 2, 2, 2, 2]
[1, 1, 1, 2, 2, 1, 2, 2, 2, 2]
[1, 1, 1, 2, 2, 2, 1, 2, 2, 2]
[1, 1, 1, 2, 2, 2, 2, 1, 2, 2]
[1, 1, 1, 2, 2, 2, 2, 2, 1, 2]
[1, 1, 2, 1, 1, 2, 2, 2, 2, 2]
[1, 1, 2, 1, 2, 1, 2, 2, 2, 2]
[1, 1, 2, 1, 2, 2, 1, 2, 2, 2]
[1, 1, 2, 1, 2, 2, 2, 1, 2, 2]
⋮
[1, 1, 2, 2, 1, 2, 2, 1, 2, 2]
[1, 1, 2, 2, 1, 2, 2, 2, 1, 2]
[1, 1, 2, 2, 2, 1, 1, 2, 2, 2]
[1, 1, 2, 2, 2, 1, 2, 1, 2, 2]
[1, 1, 2, 2, 2, 1, 2, 2, 1, 2]
[1, 1, 2, 2, 2, 2, 1, 2, 1, 2]
[1, 2, 1, 2, 1, 2, 1, 2, 2, 2]
[1, 2, 1, 2, 1, 2, 2, 1, 2, 2]
[1, 2, 1, 2, 2, 1, 2, 1, 2, 2]
重複円順列
10個の数字から重複を許して4個選び,円形に並べる
まず,Twitterであった,0〜9の10個の数字から重複を許して4個選び,円形に並べることを考えましす。前セクションのコード2を使えば,とりあえず全てのリストが得られるので,そちらを試しました。
k = [0,1,2,3,4,5,6,7,8,9,
0,1,2,3,4,5,6,7,8,9,
0,1,2,3,4,5,6,7,8,9,
0,1,2,3,4,5,6,7,8,9]
circperm(k,4)
2530-element Vector{Vector{Int64}}:
[0, 1, 2, 3]
[0, 1, 2, 4]
[0, 1, 2, 5]
[0, 1, 2, 6]
[0, 1, 2, 7]
[0, 1, 2, 8]
[0, 1, 2, 9]
[0, 1, 2, 0]
[0, 1, 2, 1]
[0, 1, 2, 2]
⋮
[7, 9, 9, 7]
[7, 9, 9, 9]
[7, 7, 7, 7]
[8, 9, 8, 9]
[8, 9, 8, 8]
[8, 9, 9, 8]
[8, 9, 9, 9]
[8, 8, 8, 8]
[9, 9, 9, 9]
2530通りのようです。
10個の数字から重複を許して6個選び,円形に並べる
@ysmemoirsさんのツイートの続きを見ると,「授業の流れ上必要になったのは,実はこれの6桁版」とあったので,さらにやってみることにしました。
最初は手計算でしたものをツイートしました。
さすがに,これは自信がないので,「誰かやってくれないかな。。。」と思っていたのですが,あまり反応もなかったので,Juliaで検算することにしました。しかし,残念ながら,コード2のcircperm
では時間がかかりすぎるので,コード1のenkan
を活かすことにしました。分割の様子がわかれば,こちらが利用できそうです。
手計算で行った時のまとめ
まずは,10個の数字から重複を許して4個選び,円形に並べる場合の手計算で分類した時のものを分割を網羅する形でまとめてみました。
4の分割数を考える。
(4) 文字の選び方は10通り,そのそれぞれに対して並べ方は1通り。
(1-3) 文字の選び方は
(2-2) 文字の選び方は
(1-1-2) 文字の選び方は
(1-1-1-1) 文字の選び方は
次に5個選ぶ場合も考えてみました。
5の分割数を考える。
(5) 文字の選び方は10通り,そのそれぞれに対して並べ方は1通り。
(1-4) 文字の選び方は
(2-3) 文字の選び方は
(1-1-3) 文字の選び方は
(1-2-2) 文字の選び方は
(1-1-1-2) 文字の選び方は
(1-1-1-1-1) 文字の選び方は
分割の中の数の最大公約数が5のときは1で割る。最大公約数が1のときは5で割る。
ツイートでの考え方は下記の方法になります。簡単に述べると,分割の中の数の最大公約数が1のときは円形に並べる方法は1列に並べる総数を個数で割れば求まります。最大公約数が1ではないときは色々調べる必要があります。
6の分割数を考える。
(6)文字の選び方は
(1-5) 文字の選び方は
(2-4) 文字の選び方はenkan([2,4])
(3-3) 文字の選び方はenkan([3,3])
(1-1-4) 文字の選び方はenkan([1,1,4])
(1-2-3) 文字の選び方はenkan([1,2,3])
(2-2-2) 文字の選び方はenkan([2,2,2])
(1-1-1-3) 文字の選び方はenkan([1,1,1,3])
(1-1-2-2) 文字の選び方はenkan([1,1,2,2])
(1-1-1-1-2) 文字の選び方はenkan([1,1,1,1,2])
(1-1-1-1-1-1) 文字の選び方はenkan([1,1,1,1,1,1])
分割の中の数の最大公約数に着目する。
私のツイートでは分割の中の数の最大公約数が1のときは円形に並べる方法は1列に並べる総数を個数6で割れば求まります。最大公約数が1ではないものは(6),(2-4),(3-3),(2-2-2)の4種類あります。その中で「6個で1つ」でないようなものを除きます。数が多くなると大変です。
コード化まとめ
分割数について
分割数はCombinatorics.jl
のpartitions
を使いました。分割数だけでなく,どのような分割なのかも与えられます。便利です。
using Combinatorics
collect(partitions(6))
11-element Vector{Vector{Int64}}:
[6]
[5, 1]
[4, 2]
[4, 1, 1]
[3, 3]
[3, 2, 1]
[3, 1, 1, 1]
[2, 2, 2]
[2, 2, 1, 1]
[2, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1]
分割の様子から数字の選び方を定める
手計算で行うときに前半この総数が必要になるのですけど,初歩的に「配列の中から数字をそれぞれカウントする」ということが最初「どうしよう???」と思ったのですけれど,DataStructures.jl
のcounter
で実現できることがわかりました。
using DataStructures
c = counter([1,2,3,3,4,4,4,6])
Accumulator{Int64, Int64} with 5 entries:
4 => 3
6 => 1
2 => 1
3 => 2
1 => 1
keys(c)
KeySet for a Dict{Int64, Int64} with 5 entries. Keys:
4
6
2
3
1
values(c)
ValueIterator for a Dict{Int64, Int64} with 5 entries. Values:
3
1
1
2
1
iro
,circperm3
)
関数の作成(関数を作成してチェックです。
function iro(k,n)
p = factorial(n)÷factorial(n-length(k))
c = counter(k)
for i in values(c)
p = p÷factorial(i)
end
p
end
function circperm3(m,n)
k = collect(partitions(m))
p = 0
q = n^m
for i in k
p += enkan(i)*iro(i,n)
end
p
end
circperm3(6,10)
166870
バッチリです。
そのほかも確認です。
for i=1:10
println("0〜9の10個の数字から重複を許して$i 個選び円形に並べる方法は",circperm3(i,10),"通りです。")
end
0〜9の10個の数字から重複を許して1 個選び円形に並べる方法は10通りです。
0〜9の10個の数字から重複を許して2 個選び円形に並べる方法は55通りです。
0〜9の10個の数字から重複を許して3 個選び円形に並べる方法は340通りです。
0〜9の10個の数字から重複を許して4 個選び円形に並べる方法は2530通りです。
0〜9の10個の数字から重複を許して5 個選び円形に並べる方法は20008通りです。
0〜9の10個の数字から重複を許して6 個選び円形に並べる方法は166870通りです。
0〜9の10個の数字から重複を許して7 個選び円形に並べる方法は1428580通りです。
0〜9の10個の数字から重複を許して8 個選び円形に並べる方法は12501280通りです。
0〜9の10個の数字から重複を許して9 個選び円形に並べる方法は111111340通りです。
0〜9の10個の数字から重複を許して10 個選び円形に並べる方法は1000010044通りです。
Discussion