Module:Sandbox/Erutuon/Climate/stats
Appearance
local fun = require "Module:fun"
local map = fun.map
local count = fun.count
local function errorf(level, ...)
if type(level) == number then
return error(string.format(...), level + 1)
else -- level is actually the format string.
return error(string.format(level, ...), 2)
end
end
local function mean_of_highs_and_lows(highs, lows)
local high_count, low_count = #highs, #lows
-- for now, no annual average accepted
if not (high_count == 12 and low_count == 12) then
errorf("Wrong number of highs or lows (%d, %d): expected 12 each",
high_count, low_count)
elseif high_count ~= low_count then
errorf("Number of highs (%d) is not equal to number of lows (%d)",
high_count, low_count)
end
local temperatures = {}
for i = 1, high_count do
if highs[i] <= lows[i] then
errorf("High #%d (%d) is not greater than low #%d (%d)",
i, highs[i], i, lows[i])
end
temperatures[i] = mean(highs[i], lows[i])
end
return temperatures
end
local varargs_or_array_mt = {
__index = {
arr_func = function (self, arr, i, j)
i = i or 1
j = j or #arr
assert(i > 0 and j > 0)
if i == j then
return arr[i]
elseif i < j then -- |---i+++j---|
return self.varargs_func(unpack(arr, i, j))
else -- |+++j---i+++|
return self.varargs_func(self.varargs_func(unpack(arr, 1, j)),
self.varargs_func(unpack(arr, i)))
end
end,
},
__call = function (self, ...)
if type(...) == 'table' then -- if first argument is table
if select(2, ...) then -- if second argument
return self:arr_func(...)
else
return self.varargs_func(unpack((...)))
end
else
return self.varargs_func(...)
end
end,
}
-- Create a function that accepts two argument formats: a list of numbers, or
-- an array containing numbers followed by optional start and end indices
-- (similar to the arguments to 'unpack').
local function make_arr_and_varargs_func(varargs_func)
return setmetatable({ varargs_func = varargs_func }, varargs_or_array_mt)
end
local function sum_varargs (...)
local result = 0
for i = 1, select('#', ...) do
result = result + select(i, ...)
end
return result
end
local ops = {
sum = make_arr_and_varargs_func(sum_varargs),
mean = make_arr_and_varargs_func(
function (...)
return sum_varargs(...) / select('#', ...)
end),
min = make_arr_and_varargs_func(
function (...)
local min = math.huge
local min_index
for i = 1, select('#', ...) do
local val = select(i, ...)
if type(val) == "table" then
val, i = val.value, val.index
end
if val < min then
min = val
min_index = i
end
end
return { value = min, index = min_index }
end),
max = make_arr_and_varargs_func(
function (...)
local max = -math.huge
local max_index
for i = 1, select('#', ...) do
local val = select(i, ...)
if type(val) == "table" then
val, i = val.value, val.index
end
if val > max then
max = val
max_index = i
end
end
return { value = max, index = max_index }
end),
}
local stats_mt = {}
function stats_mt:__index(key)
if type(key) ~= "string" then
return nil
end
local op, amount = key:match("(%a+)_(%d+)")
if amount then
amount = tonumber(amount)
local count =
op == "above" and count(function(val) return val > amount end, self)
or op == "below" and count(function(val) return val < amount end, self)
or errorf("Unrecognized operation %s", op)
self[key] = count
return count
end
local season
season, op = key:match("^(%a-)_?(%a+)$")
if not (season == "" or season == "summer" or season == "winter") then
errorf("Unrecognized season %s", season)
elseif not ops[op] then
errorf("Unrecognized operation %s", op)
end
local result
if season == "" then
result = ops[op](self)
else
result = ops[op](self, unpack(self[season .. "_months"]))
end
self[key] = result
return result
end
local seasons = { summer = { south = { 10, 3 }, north = { 4, 9 } } }
seasons.winter = {}
seasons.winter.south = { seasons.summer.south[2] + 1, seasons.summer.south[1] - 1 }
seasons.winter.north = { seasons.summer.north[2] + 1, seasons.summer.north[1] - 1 }
setmetatable(stats_mt, {
__call = function (self, val, Southern_Hemisphere) -- constructor function
val = val or {}
local hemisphere = Southern_Hemisphere and "south" or "north"
val.summer_months, val.winter_months =
seasons.summer[hemisphere], seasons.winter[hemisphere]
return setmetatable(val, self)
end
})
return stats_mt