作者: 機械伯爵
日時: 2002/10/19(22:25)
 ども、機械です。

 ちょっとした理由があってDOSでも動くPython1.0.1で使えるランダム関数モジュー
ルが必要になりましたので、Pythonの標準モジュールの Wichman-Hill random
number generatorを、1.0.1仮想マシンからインポートして使えるように移植しまし
た。

※しかし、ランダム関数なんて、ANSI-Cの標準ライブラリにさえあるのに、なんで
Pythonの組み込みには無いんだろう?

 で・・・予想以上に苦戦しました。

 実は、欲しいのはrandintだけだったので、それが使えるだけなら簡単だったので
すが、この際だからできるかぎり忠実に機能を移植しようなんて考えて、土壺にはま
りました。

 引数については、1.0.1段階ではデフォルト引数が使えないばかりか、辞書引数さ
え使えないので、位置引数で代用することで何とか様々な引数数?に対応しました
が、引数指定はできません(どうしてもというのであれば、オブジェクトフィールド
を直接変更することで対処することもできないわけではありませんでしたが、書式が
完全に異なってしまう上に、本来隠しオブジェクトであるものを直接いじるのは美し
くないので、やめました)

 とりあえず苦労したおかげで、色々勉強になりました。

※インポートされたモジュールの中からは、直接組み込み関数が「名前としては」呼
び出せないとか、位置引数がタプルで渡されるとか(リストだと思ってました)

 ランダム関数といえば、高度な解析で使う他はフツーはゲームに使う(笑)と思い
ますので(私も勿論そうです)、DOSベースで簡単なPythonスクリプトのゲームを
作って気晴らしとかしたい方は、是非活用してください。

 ちなみに、ディスクから立ち上げたり、古いFM-RやTOWNS、PC-9800などから使うな
ら、16bit Python (16python.exe)が、Pythonをインストールせずに、DOSプロンプト
がコマンドプロンプトが使用禁止状態になってるパソコン(う〜ん、私の学校のパソ
コン以外で、こんな制限がかけてあるパソコンを使わされる羽目になることがあるの
かどうか疑問ですが)で使うなら、QWPythonが便利です。

 コメントは極力オリジナルのものを残しましたが、容量が勿体無いと思われる方は
削ってください☆

--^ whrandom.py
# Wichman-Hill random number generator.

# Wichmann, B. A. & Hill, I. D. (1982)
# Algorithm AS 183:
# An efficient and portable pseudo-random number generator
# Applied Statistics 31 (1982) 188-190

# see also:
#         Correction to Algorithm AS 183
#         Applied Statistics 33 (1984) 123

#         McLeod, A. I. (1985)
#         A remark on Algorithm AS 183
#         Applied Statistics 34 (1985),198-200


# USE:
# whrandom.random()       yields double precision random numbers
#                         uniformly distributed between 0 and 1.

# whrandom.seed(x, y, z)  must be called before whrandom.random()
#                         to seed the generator

# There is also an interface to create multiple independent
# random generators, and to choose from other ranges.



# Multi-threading note: the random number generator used here is not
# thread-safe; it is possible that nearly simultaneous calls in
# different theads return the same random value.  To avoid this, you
# have to use a lock around all calls.  (I didn't want to slow this
# down in the serial case by using a lock here.)

# Translated by Guido van Rossum from C source provided by
# Adrian Baddeley.

# To Python 1.0.1 Format by Count KIKWAI

import __builtin__

def dpara(dlist,ptuple): # Default Parameter
    # lstの要素の数に応じて、足りない分にデフォルト数を与える
    # デフォルト引数書式が使えない1.0.1用代理関数
    plist = []
    for x in ptuple: # tuple -> list
      plist = plist + [x]
    n0 = len(dlist)
    n1 = len(plist)
    if n1 < n0:
        plist = plist + dlist[(len(plist)):]
        return plist
    elif n1 > n0:
        return plist[:(len(dlist))]
    else : #(n1==n0)
        return plist

class whrandom:
    def __init__(self, *lst):
#   def __init__(self, x = 0, y = 0, z = 0):
        # Initialize an instance.
        # Without arguments, initialize from current time.
        # With arguments (x, y, z), initialize from them.
        [x, y, z] = dpara([0, 0, 0], lst)
        self.seed(x, y, z)

    def seed(self, *lst):
#   def seed(self, x = 0, y = 0, z = 0):
        # Set the seed from (x, y, z).
        # These must be integers in the range [0, 256).
        [x, y, z] = dpara([0, 0, 0], lst)
        if not type(x) == type(y) == type(z) == type(0):
            raise TypeError, 'seeds must be integers'
        if not (0 <= x < 256 and 0 <= y < 256 and 0 <= z < 256):
            raise ValueError, 'seeds must be in range(0, 256)'
        if 0 == x == y == z:
            # Initialize from current time
            import time
            t = long(time.time() * 256)
            t = int((t&0xffffff) ^ (t>>24))
            t, x = divmod(t, 256)
            t, y = divmod(t, 256)
            t, z = divmod(t, 256)
        # Zero is a poor seed, so substitute 1
        self._seed = (x or 1, y or 1, z or 1)

    def random(self):
        # Get the next random number in the range [0.0, 1.0).
        # This part is thread-unsafe:
        # BEGIN CRITICAL SECTION
        x, y, z = self._seed

        x = (171 * x) % 30269
        y = (172 * y) % 30307
        z = (170 * z) % 30323

        self._seed = x, y, z
        # END CRITICAL SECTION
        #
        return (x/30269.0 + y/30307.0 + z/30323.0) % 1.0

    def uniform(self, a, b):
        # Get a random number in the range [a, b).
        return a + (b-a) * self.random()

    def randint(self, a, b):
        # Get a random integer in the range [a, b] including
        # both end points.
        # (Deprecated; use randrange below.)
        return self.randrange(a, b+1)

    def choice(self, seq):
        # Choose a random element from a non-empty sequence.
        return seq[int(self.random() * len(seq))]

    def randrange(self, start, *lst):
#   def randrange(self, start, stop=None, step=1, int=int, default=None):
        # Choose a random item from range(start, stop[, step]).
        # This fixes the problem with randint() which includes the
        # endpoint; in Python this is usually not what you want.
        # Do not supply the 'int' and 'default' arguments.
        # This code is a bit messy to make it fast for the
        # common case while still doing adequate error checking
        [stop,step,int,default] = dpara([None, 1, __builtin__.int, None],
lst)
        istart = int(start)
        if istart != start:
            raise ValueError, "non-integer arg 1 for randrange()"
        if stop is default:
            if istart > 0:
                return int(self.random() * istart)
            raise ValueError, "empty range for randrange()"
        istop = int(stop)
        if istop != stop:
            raise ValueError, "non-integer stop for randrange()"
        if step == 1:
            if istart < istop:
                return istart + int(self.random() *
                                   (istop - istart))
            raise ValueError, "empty range for randrange()"
        istep = int(step)
        if istep != step:
            raise ValueError, "non-integer step for randrange()"
        if istep > 0:
            n = (istop - istart + istep - 1) / istep
        elif istep < 0:
            n = (istop - istart + istep + 1) / istep
        else:
            raise ValueError, "zero step for randrange()"

        if n <= 0:
            raise ValueError, "empty range for randrange()"
        return istart + istep*int(self.random() * n)


# Initialize from the current time
_inst = whrandom()
seed = _inst.seed
random = _inst.random
uniform = _inst.uniform
randint = _inst.randint
choice = _inst.choice
randrange = _inst.randrange

--$