素数関係

NScripter&Luaで素数 - 永字八法の続き。
今のLua理解でもう一度発明をしてみる。

ライブラリ

これをはりつけると、一つのテーブルと二つの関数が追加される。

math.prime_list
素数列のテーブル。prime_list[10]とすると、10番目の素数(=31)が戻ってくる。
math.is_prime(num)
関数。数値を与えると、それが素数かどうかをbooleanで返す。
math.is_not_prime(num)
関数。math.is_prime(num)の逆の結果になる。
do
	-- 素数列の配列。1には2が、2には3が、3には5が……入っている。
	prime_list = setmetatable({2, 3}, {__index=function(prime_list, key)
		if type(key)~="number" then return nil end -- キーは数字でなければ!
		key = math.floor(key) -- 整数でないと!

		local temp = prime_list[key-1] + 2 -- 初期設定は、一つ前+2
		local is_not_prime = false -- 素数かどうかの判定フラグ
		local copy
		while true do
			for i, prime in ipairs(prime_list) do -- 小さな素数から順番に試していく。
				if prime > 2 then -- 素数2では試さない。
					if prime * prime > temp then break end -- 素数の平方を超えたら終了。
					copy = temp -- 試す数値をコピー
					while copy > 0 do copy = copy - prime end -- ずっと減らしていく。
					is_not_prime = (copy==0) -- copyの値が0なら割り切れたことになり、これは素数ではない。
					if is_not_prime then break end -- 素数でないことがわかったなら、ループ終了。
				end
			end
			if is_not_prime then -- 素数でないならば、試す数値を2増やして再トライ
				temp = temp + 2
			else -- 素数ならば、ループを終了
				break
			end
		end

		rawset(prime_list, key, temp) -- セットする。
		return rawget(prime_list, key) -- セットした物を返す。
	end})
	math.prime_list = prime_list

	-- その数値が素数かどうかを調べる。
	local is_prime_memo = {} -- 調べた結果を格納しておく。
	local func = function(num)
		if num < 2 then return false end
		if type(is_prime_memo[tostring(num)]) ~= "nil" then return is_prime_memo[tostring(num)] end -- メモがあればそれを使う。
		local cursor = #prime_list -- 調べる場所
		local res = false
		local search_list = {}
		while true do
			if search_list[cursor] then break end -- 一度調べたところなら、パスする。
			search_list[cursor] = true
			if prime_list[cursor]==num then -- 素数列内に同値があったならば、それは素数である。
				res = true
				break
			end
			if prime_list[cursor]<num then -- カーソル位置の素数よりも、試す数値が高いならば、
				cursor = cursor + 1 -- カーソルを一つ増やす。
			else -- 逆に低いならば
				cursor = cursor - 1 -- カーソルを一つ減らす。
			end
		end
		is_prime_memo[tostring(num)] = res -- 結果をメモに保存。
		return res -- 結果を返す。
	end
	math.is_prime = func
	math.is_not_prime = function(num)
		if func(num) then return false else return true end
	end
end

おまけ

以下のスクリプトを使うと、NScirpter側に二つの命令が追加される。

-- prime命令。
-- prime %0,%1
-- 素数列の最初を1として、%1番目の素数を%0に格納する。
NSExec("luasub prime")
function NSCOM_prime()
	local ref = NSPopIntRef()
	NSPopComma()
	NSSetIntValue(ref, math.prime_list[NSPopInt()])
end

-- is_prime命令
-- is_prime %0,%1
-- %1が素数かどうか判定して、結果を%0に入れる。素数なら1、そうでなければ0が入る。
NSExec("luasub is_prime")
function NSCOM_is_prime()
	local res = 0
	local ref = NSPopIntRef()
	NSPopComma()
	if is_prime(NSPopInt()) then res = 1 end
	NSSetIntValue(ref, res)
end

まとめ

なんかすっきりしたけど、コメントのせいかファイルサイズ自体は増えてるんだよなあ。