辞書順(lexicographical order)に並べて何番目か、あるいはn番目のものは何か。
要素に重複がないとして考える。数列 (1, 2, .... , n) の並べ替えとして解こう。
数列 (a1, .. , an) は辞書順で何番目か。
def number_in_lex_order(ary) n = ary.size return 1 if n <= 1 idx = ary.sort.bsearch_index { _1 >= ary[0] } (1..n - 1).inject(:*) * idx + number_in_lex_order(ary[1..-1]) end number_in_lex_order([2, 1, 5, 3, 4]) #=>29
ここでは、数列の左端の数字が、数列を昇順にソートした中で何番目に来るかがわからなければならない。それを i (=idx+1) とすると、そこまでで (n - 1)! * (i - 1) だけ既に(一桁少ない部分)数列が並んでいることになる(ただし n は ary の桁数)。
あとは左端の一桁を落として同じことを(再帰で)求め、すべての和をとってやれば求まる。
文字列などならば、ハッシュで数列に変換するテーブルを作ってやればよい。
str = "MATH" h = "AHMT".each_char.with_index(1).to_h number_in_lex_order(str.each_char.map { h[_1] }) #=>14
もっとも、文字列ならば Ruby の柔軟性(?)のおかげで、
number_in_lex_order("MATH".chars) #=>14
で求まってしまうのだが。
これの逆、つまり辞書順でn番目の数列はどうなるか。この場合、数列の長さ(size)を与えることが必要である。
def nth_sequence_in_lex_order(n, size) i = 1 table = (1..size - 1).map { i *= _1 } order = (1..size).to_a ans = [] n -= 1 size.pred.times do factorial = table.pop i = n / factorial n -= i * factorial ans << (a = order[i]) order.delete(a) break if n <= 0 end ans.concat(order) end
やってみる。
nth_sequence_in_lex_order(29, 5) #=>[2, 1, 5, 3, 4]
よさそうだ。
上と同様に、文字列でもやってみる。これも、ハッシュでテーブルを作り、Hash#invert する。
h = "AHMT".each_char.with_index(1).to_h.invert nth_sequence_in_lex_order(14, 4).map { h[_1] }.join #=>"MATH"
これもよさそうだ。
なお、いずれも不正入力には対応していない。