Module:Ulam spiral

From MOASSpedia
Jump to navigation Jump to search

Implements {{Ulam spiral}}


-- Generate Ulam spiral based on primes, fibonacci, triangular numbers etc.
-- Gts@el wiki, Sep. 2017

local p = {}

-- determine whether number is prime
-- params: n (number)
-- return: boolean
local function is_prime(n)
    for i = 2, n^(1/2) do
        if (n % i) == 0 then
            return false
        end
    end
    return true
end

-- determine whether number is triangular
-- params: n (number)
-- return: boolean
local function is_triangular(n)
   x = (math.sqrt(8*n + 1) - 1) / 2
   return (x % 1 == 0)
end

-- determine whether number is part of standard fibonacci sequence
-- params: n (number)
-- return: boolean
local function is_fib(n)
    if n == 0 then
      return true
    end

    phi = 0.5 + (0.5 * math.sqrt(5.0))
    a = phi * n
    return math.abs(math.floor(a+0.5) - a) < 1.0 / n
end

-- determine total divisors of a number
-- params: n (number)
-- return: int
local function divisors(n)
    limit = n;
    total = 0;

    if n == 1 then
       return 1
    end

    for i = 1, limit-1 do
        if n % i == 0 then
            limit = n / i
            if limit ~= i then
               total = total + 1
            end

            total = total + 1
        end
    end

    return total
end

-- CSS border for cells in order to recreate spiral
-- params: row (number), column (number), size (number)
-- return string
function border(row, column, size)

   local on = 'solid '
   local top, left, right, bottom = 'none ', 'none ', 'none ', 'none '
   local last = size - 1 

   if column == math.floor(size/2) and row == column then
       bottom = on
   elseif row == math.floor(size/2) and column==row+1 then
       bottom=on
       right=on
   end 

   if row < column and column < last - row then
      bottom = on
   elseif row > column and row < last - column then
      right = on
   elseif row >= column-1 and row < last and row > size/2 then
      bottom = on

      if column == row + 1 and column < last then
        right = on
      end 
   elseif column>row and column < last then
      right = on
   end 

   result = top .. right .. bottom .. left
   result = result:gsub("%s$", "") 

   return result

end


-- convert values to wikitable. The wikitable string is preproccessed in frame.
-- params: data (dictionary), size (int), fontSize (int)
-- return: string object
local function wikitable(data, size, fontSize)
	local fontSize = '1' --em
    if tonumber(size) > 15 and tonumber(size) <=30 then
    	fontSize = '0.8'
    elseif tonumber(size) > 30 and tonumber(size) <= 45 then
    	fontSize = '0.6'
    elseif tonumber(size) > 45 and tonumber(size) <= 60 then
    	fontSize = '0.4'
    elseif tonumber(size) > 60 then   
    	fontSize = '0.2'
    end
	
	local result = '{| style="font-size:' .. fontSize .. 'em;text-align:center;border-spacing:0px"'

    -- background colour of a number corresponding to number of divisors
    divi = {
                 [2] = '#8080ff',
                 [3] = '#80ff80',
                 [4] = '',
                 [5] = '',
                 [6] = '',
                 [7] = '',
                 [8] = '',
                 [9] = '',
               }

    -- create wikitable
	for row=0, size-1 do
		result = result .. '\n|-\n|'
		for column=0, size-1 do
            if column > 0 then
               result = result .. '||'
            end

            number = data[row .. ':' .. column]
            bgcolor =''

			divtotal = divisors(number)
            if divtotal >= 47 then
              bgcolor = '#ff8080'
            else
              bgcolor = tostring(divi[divtotal])
            end
            
            result = result .. 'title="' .. math.floor(number) .. '" style="border-color:#9e9e9e;border-size:1px;border-style:' .. border(row, column, size) ..  ';background-color:'.. bgcolor .. ';color:black"|'
            --result = result .. 'title="' .. math.floor(number) .. '" style="border-radius:50%;background-color:'.. bgcolor .. ';color:black !important"|'

            number = math.floor(data[row .. ':' .. column])
 			--if bgcolor ~= '' and bgcolor ~= 'nil' then
 				result = result .. '[[' ..	number .. ' (number)|' .. number .. ']]'
            --else  		
 			--    result = result .. number
 			--end
        end
	end

	result = result .. '\n|}'

    -- preprocess string in frame and render table
    local frame = mw.getCurrentFrame()
 	result = frame:preprocess(result)

	return result
end

-- calculate ulam spiral value for each x,y tabular point.
-- algorithm concept based on Python version at GPL 3 https://rosettacode.org/wiki/Ulam_spiral_(for_primes)#Python
-- use of bitwise ops was attempted but Mediawiki Scribunto Lua doesn't seem to like them
-- params: n (int), x (int), y (int), start (int)
-- return: int
local function cell(n, x, y, start)
	local d = 0

	local x = x - math.floor((n - 1) /2)
	local y = y - math.floor(n / 2)

	l = 2 * math.max(math.abs(x), math.abs(y))

	if y <= x then
		d = (l*3) + x + y
	else
		d = l - x - y
	end

	return math.pow(l - 1, 2) + d + start - 1
end

-- prepare spiral display for output type (i.e. wikitable, coords only, etc)
-- params: size (int), start (int)
-- return: mixed
local function show_spiral(size, start)
   local result = {}

   for i=0, size-1 do
       for x=0, i do
         for y=0, i do
           result[x .. ':' .. y] = cell(size, x, y, start)
         end
      end
   end

   return wikitable(result, size)
end

-- main
-- params: frame
-- return: mixed
function p.ulam(frame)
	local size = tonumber(frame.args[1])
	local start = tonumber(frame.args[2])
    
    if size > 75 then
       return '<span style="font-family:Roboto;">The maximum allowed size is 75x75</span>'	
    end
    
	return show_spiral(size, start)
end

return p