わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

順列の総数を求める

いきなりですが問題です.

引数(文字列)を一つとり,その文字を並べ替えてできる文字列(引数自身を含む)が何通りあるかを求めるプログラムを作りなさい.

順列の問題ですので,まずは順列 - Wikipediaを見ます.「重複を許す場合」の中に,分母が階乗の積で,分子が一つの階乗になっている式があり,これが使えそうです.例を出すと,もとの文字列が wakayama だったら,w, a, k, y, mの各文字から,aのみ4個,それ以外は1個ずつ使ってできる文字列なので,\frac{8!}{1!4!1!1!1!}となります.
階乗を再帰呼び出しで求めるのは,好きではありません.しかし,whileループ*1というのも泥臭いです.そういえば,Enumerable#injectが使えるかもしれません.そこの例では配列に対して使っていますが,Rangeも,Enumerableをmix-inしているので,いけそうです.もう少し探すと,http://konohaotoshi.blog69.fc2.com/blog-entry-103.htmlというのも見つかりました.
もう一点,考慮すべきことは,答えの後で.

#!/usr/bin/env ruby

# permutation_count.rb

require 'strscan'

def factorial(num)
  (1..num).inject(1) {|result, i| result * i}
end

def permutation_count(str)
  str = str.split(//u).sort.join('')
  t = factorial(str.length)
  ss = StringScanner.new(str)
  while !ss.eos?
    t /= factorial(ss.scan(/(.)\1*/u).length)
  end
  t
end

str = ARGV.shift || 'ABCD'
puts "#{str}: #{permutation_count(str)}"

実行してみます.

$ ruby permutation_count.rb wakayama
wakayama: 1680
$ ruby permutation_count.rb takehiko
takehiko: 20160
$ ruby permutation_count.rb 12345678
12345678: 40320
$ ruby permutation_count.rb takehikom
takehikom: 181440
$ ruby permutation_count.rb wakayama-u
wakayama-u: 151200

さて,「答えの後で」の解説です.wakayama の例で「aのみ4個」と書きましたが,文字列から,各文字の個数をどうやって求めるかです.
1文字ずつ順に求めるのはスマートではないなと思いまして,strscanライブラリを使ってみました.あらかじめ文字をソートしておけば,1文字以上の連続する同じ文字の並びが「/(.)\1*/」という正規表現で求められます.

*1:Rubyなら,uptoかdowntoかな.