Pythonで「成分解析」(ハッシュ値を乱数シードに使う)
最近流行っているらしい成分解析。「こういう適当な結果を返すのに乱数を使いたいが、普通に乱数を使ってしまうと解析のたびに違う結果が帰ってきてしまう」「どうやればこういうプログラムを作れるのだろう」という話をどこかで小耳に挟んだので、サンプルを作ってみました。
一言で言ってしまうと、タイトルに入れた「ハッシュ値を乱数シードに使う」で済んでしまいます。もっと詳しく言うと、「与えられた名前などの情報を適当な関数で整数に変換し、その値を用いて乱数の初期化を行う」とでもなるでしょうか。「適当な関数」は、同じ文字列を与えると同じ整数値が返ってくるような関数を使います。たとえば「最初の1文字の文字コードを返す」でも構いません。ただ、その場合、最初の文字が同じ人は同じ結果になります。もちろんたまたま全然似ていない名前の人が同じ結果になる場合もあります。これらの問題を避けるためには「文字列全体によって値が決まり」「なるべく広い範囲の値にまんべんなく散らばる」という特徴が欲しいところです。
さて、実はそういう特徴を持った関数はすでにハッシュ(連想配列、辞書)というデータ構造の実現のために多くの言語で実装されており、Pythonに至ってはhashという組み込みの関数まであります。これを使って乱数を初期化してやればいいわけです。実はPythonの乱数モジュールは初期化関数に整数でないものが渡されると、勝手にhashでハッシュ値を求めて使うようになっています。つまり、やるべきことは非常に少なく、
import random name = "西尾泰和" random.seed(name)
これで終わり。生年月日などがある場合はタプルにくくって("西尾泰和", 1981, 7, 23)と行った形でseedに与えることができます。Pythonのタプルはハッシュ化可能なので。
さて、駆け足で解説を書いてきましたが、ソースコードはこちら。50行程度で
# -*- coding: cp932 -*-
#
# 成分表示の簡単な実装
import random
seibunStr = """
ビタミン タンパク質 脂肪
愛 希望
けだるさ 思いつき
"""
seibunList = seibunStr.strip().split()
name = "西尾泰和"
random.seed(name)
# いくつの成分からなるか (3~5こ)
numSeibun = random.randint(3, 5)
# 成分を選んで、割合も決める
result = []
sumValue = 0
for i in range(numSeibun):
s = random.choice(seibunList)
seibunList.remove(s)
v = random.random()
sumValue += v
result.append((v, s))
result.sort()
result.reverse()
# 値の合計が100%になるように調整
sumPercent = 0
for (i, (v, s)) in enumerate(result):
percent = int(100 * v / sumValue)
sumPercent += percent
result[i] = (s, percent)
result[0] = (result[0][0], result[0][1] + 100 - sumPercent) # 丸め誤差をトップに足す
# 表示
print name, "は"
for (s, p) in result:
print "%d%%の%s" % (p, s)
print "で出来ています。"