Moduuli:Mallinetyokalut

Wikisanakirjasta
Tässä ohjeessa kuvataan toiminnallisuutta jonka kehitys on vielä kesken. Sivu on tarkoitettu lähinnä kehityksen apuvälineeksi, ei yleiseen käyttöön.

Hyödyllisiä apufunktiota mallineiden parametrien käsittelyyn.

Funktiot[muokkaa]

ensimmainen_ei_tyhja[muokkaa]

ensimmainen_ei_tyhja(luettelo)

Palauttaa ensimmäisessä parametrissa annetusta luettelosta ensimmäisen arvon joka ei ole nil tai tyhjä merkkijono. Funktiota voi käyttää tutkittaessa mitä parametreja on annettu, kun tyhjän parametrin halutaan vastaavan antamatonta arvoa. Jos parametria ei ole annettu ollenkaan, on sen arvo nil. Jos sen kenttä on jätetty tyhjäksi, on sen arvo "".

Parametrit:

  1. luettelo arvoista

Esim.

ensimmainen_ei_tyhja{"", nil, "arvo1", "arvo2"}  -- palauttaa "arvo1"
ensimmainen_ei_tyhja{""}  -- palauttaa nil

numeroidut_parametrit[muokkaa]

numeroidut_parametrit(args, nimet)

Palauttaa annettua taulukkoa args vastaavan taulukon, jossa numeroidut parametrit on muutettu taulukon alkioiksi.

Parametrit:

  1. frame-olion args-kenttä
  2. Parametrit, joista tehdään luettelo vaikka niitä ei olisi numeroitu. Huom. Funktio tekee luettelon kaikista numerollisista parametreista, mutta koodin ymmärrettävyyden kannalta on tässä parametrissa hyvä luetella kaikki parametrinimet, joita halutaan myöhemmin käyttää.

Esim.

numeroidut_parametrit({ mon1 = "eka", mon2 = "toka" }, { "mon" })  -- palauttaa { mon = { "eka", "toka" } }

2. parametri on hyödyllinen, kun arvo voidaan antaa joko numerolla tai ilman (jolloin numeroton arvo vastaa 1:tä).

numeroidut_parametrit({ mon = "eka", mon2 = "toka" }, { "mon" })  -- palauttaa { mon = { "eka", "toka" } }

Jos tasoja on useampia, ne erotetaan pisteellä:

local args = { 
    ["p1"] = "何‎", 
    ["plat1.1"] = "eka", 
    ["plat1.2"] = "toka", 
    ["p2"] = "مايو", 
    ["plat2.1"] = "kolkki" 
}
numeroidut_parametrit(args, { "p", "plat" })  
-- Tulos: { p = { "何", "مايو" }, plat = { { "eka", "toka" }, { "kolkki" } }

local p = {}


--- TODO: POIS ei liity mallineisiin ---->

--- Kutsuu funktiota `func` kaikille taulukon `arr` arvoille ja palauttaa tulokset taulukkona.
-- 
-- @param func: funktio muotoa f(p)
-- @param arr:  taulukko
-- @return:     taulukko
function p.map(func, arr)
    local out = {}
    for i, v in ipairs(arr) do
        out[i] = func(v)
    end
    return out
end

function p.map2(func, arr1, arr2)
    local out = {}
    local v1, v2
    
    for i = 1, math.max(#arr1, #arr2) do
        v1 = arr1[i]
        v2 = arr2[i]
        out[i] = func(v1, v2)
    end
    
    return out
end

--- Palauttaa uuden taulukon, jossa annettujen taulukoiden arvot on paritettu.
--
-- Käyttö: zip(taulukko1, taulukko2, [taulukko3 ...])
-- 
-- Esim. zip({1, 2, 3}, {"a", "b", "c"})
--  --> { {1, "a"}, {2, "b"}, {3, "c"} }
-- 
function p.zip(...)
    local out = {}
    local len = 0

    local args = {...}
    
    for i_arr, arr in ipairs(args) do
        assert(type(arr) == "table", "Argumentti " .. i_arr .. " ei ole taulukko")
        len = math.max(len, #arr)
    end

    for i_item = 1, len do
        out[i_item] = {}
        for i_arr, arr in ipairs(args) do
            out[i_item][i_arr] = arr[i_item]
        end
    end

    return out
end

function p.filter(func, arr)
    local out = {}
    for i, v in ipairs(arr) do
        if func(v) then
            out[i] = v
        end
    end
    return out
end

--- <---- POIS




--- Erottaa ryhmiä annetun taulukon avaimista etuliitteiden perusteella.
--
-- Jos etuliite sisältyy toiseen, menee arvo pisimmän etuliiteen mukaiseen ryhmään.
-- 
-- @param arr:       taulukko, esim. mallineen frame.args-parametrit
-- @param prefiksit: taulukko, jossa avaimet ovat prefiksejä ja arvot ryhmiä, joihin kyseisellä
--                   prefiksillä alkavat arvot laitetaan
-- @return:     taulukko, jossa on kullekin prefiksille oma kohta. Prefiksittömät menevät 0-alkion alle.
-- 
-- Esim. 
--   m.ryhmittele_prefikseittain({
--         ["nom.m"]  = "blörgus",
--         ["nom.f"]  = "blörga",
--         ["nom.n"]  = "blörgum",
--         ["komp.nom.m"] = "blörgämpus",
--         ["komp.nom.f"] = "blörgämpa",
--         ["komp.nom.n"] = "blörgämpum",
--         ["sup.nom.m"]  = "blöginus",
--         ["sup.nom.f"]  = "blörgina",
--         ["sup.nom.n"]  = "blörginum",
--     }, {
--         ["komp."] = "komp",
--         ["sup."] = "sup",
--  })
--  --> {
--             [0] = {
--                 ["nom.m"] = "blörgus",
--                 ["nom.f"]  = "blörga",
--                 ["nom.n"]  = "blörgum"
--             },
--             ["komp"] = {
--                 ["nom.m"] = "blörgämpus",
--                 ["nom.f"] = "blörgämpa",
--                 ["nom.n"] = "blörgämpum"
--             },
--             ["komp.testi"] = {
--                 ["nom.m"]  = "blöginus",
--                 ["nom.f"]  = "blörgina",
--                 ["nom.n"]  = "blörginum"
--             }
--     }
-- 
function p.ryhmittele_prefikseittain(arr, prefiksit)
    local out = { [0] = {} }

    local prefiksit_list = {}
    
    for prefiksi, ryhma in pairs(prefiksit) do
        assert ( type(prefiksi) == "string", "virheellinen prefiksi" )
        assert ( type(ryhma)    == "string", "virheellinen ryhmä (prefiksi: " .. prefiksi .. ")" )
        table.insert(prefiksit_list, prefiksi)
        out[ryhma] = {}
    end

    -- Lajitellaan pisimmästä lyhimpään siltä varalta, että jokin prefiksi on
    -- toisen prefiksin alku.
    table.sort(prefiksit_list, function (a, b) return #b < #a end)

    for key, val in pairs(arr) do
        local lisatty = false
        for i, prefiksi in ipairs(prefiksit_list) do
            local ryhma = prefiksit[prefiksi]
            local alku  = mw.ustring.sub(key, 1, #prefiksi)
            local loppu = mw.ustring.sub(key, #prefiksi + 1)
            
            if alku == prefiksi then
                out[ryhma][loppu] = val
                lisatty = true
                break
            end
        end
        
        if not lisatty then
            out[0][key] = val
        end
    end

    return out
end


--- Palauttaa ensimmäisen ei-tyhjän arvon annetusta luettelosta.
-- 
-- Tyhjäksi arvoksi lasketaan nil ja tyhjä merkkijono.
-- Esim. 
--   p.ensimmainen_ei_tyhja{"", nil, "arvo1", "arvo2"}
-- palauttaa "arvo1".
-- 
function p.ensimmainen_ei_tyhja(params)
    for i = 1, #params do
        local val = params[i]
        if val ~= nil and val ~= "" then
            return val
        end
    end
    
    return nil
end


--- Palauttaa uuden taulukon, jossa on annetusta taulukosta kaikki muut arvot paitsi pelkkiä tyhjiä merkkejä sisältävät.
-- Funktiolla voi poistaa malllineen parametreista anatamattomat arvot silloin kun tyhjä kenttä vastaa antamatonta arvoa.
--
-- @param params: (yksiulotteinen) taulukko
-- @return:       sama taulukko ilman tyhjiä kenttiä
--
-- Esim. poista_tyhjat({
--                        ["y.nom"] = "uugi",
--                        ["y.gen"] = "",
--                        ["y.part"] = "buugi"
-- })
-- palauttaa:
-- {
--                        ["y.nom"] = "uugi",
--                        ["y.part"] = "buugi"
-- }
function p.poista_tyhjat(params)
    local out = {}

    for k,v in pairs(params) do
		if not mw.ustring.match(v, "^%s*$") then
            if tonumber(k) == k then
                table.insert(out, v)
            else
                out[k] = v
            end
		end
    end

    return out
end






--- Erottaa pisteillä erotetut numerot nimen lopusta.
-- 
-- Apufunktio numeroidut_parametrit -funktiolle.
-- 
-- Esim. "mon1" -> "mon", 1;
--       "param2.3" -> "param", 2, 3;
--       "y3muoto1" -> "y3muoto", 1.
local function erota_osat(sana)
    -- Etsitään lopusta alkaen, jotta nimi voi sisältää numeroita.
    if type(sana) ~= "string" then
        return  { sana }
    end
    local rev  = sana:reverse()
    local nums = string.match(rev, "^([%d.]*)"):reverse()
    if nums == "" then
        return { sana }
    end
    local name = string.sub(sana, 1, sana:len() - nums:len())
    local ns   = {}
    
    for num in string.gmatch(nums, "([%d]+)") do
        table.insert(ns, tonumber(num))
    end

    return { name, unpack(ns) }
end

local function key_compare(a, b)
    local a = string.match(a or "", "([0-9]*)$")
    local b = string.match(b or "", "([0-9]*)$")

    return (tonumber(a) or 0) < (tonumber(b) or 0)
end

--- Palauttaa taulukon avaimet numerojärjestyksessä.
--
-- Huom. varmistaa vain, että numeroidut nimet ovat järjestyksessä
-- (esim. "xx1" tulee ennen "xx2"). Ei muuten nimet ovat
-- edelleen satunnaisessa järjestyksessä.
local function ordered_keys(tbl)
    local keyset = {}
    local n = 0

    for k,v in pairs(tbl) do
        n = n + 1
        keyset[n] = k
    end

    table.sort(keyset, key_compare)

    return keyset
end

--- Muuttaa assosiatiivisen taulukon, jossa avaimet on numeroitu, sisäkkäisiksi taulukoiksi.
--
-- Syötetaulukon numerot erotetaan pisteillä (paitsi ensimmäinen). Numeroimattomat ("a") ja
-- ylemmän tason ylimääräiset arvot ("c1") jätetään pois ilman virheilmoitusta.
-- Esim. { ["a"] = "X",
--         ["a2"] = "A",
--         ["b1"] = "B",
--         ["b2"] = "C",
--         ["c1"] = "X",
--         ["c1.1"] = "D" }
-- 
--    -> { ["a"] = { [2] = "A" },
--         ["b"] = { "B",
--                   "C" },
--         ["c"] = { { "D" } } }
-- @param args:  taulukko, jossa on numeroon päättyviä avaimia
-- @param nimet: taulukkoon ainakin tulevat avaimet. Numeroton arvo tulkitaan 1:seksi.
--               Esim. "mon" = "mon1" -> { "mon" = { [1] = ... } }
function p.numeroidut_parametrit(args, nimet)
    local out = {}
    local cur_node
    local path_
    local cur_p

    if nimet then
        for i, v in ipairs(nimet) do
            if args[v] then
                out[v] = {}
                out[v][1] = args[v]
            end
        end
    end

    local keys = ordered_keys(args)
    for _, key in ipairs(keys) do
        local val = args[key]
        path_ = erota_osat(key)
        cur_node = out

        if #path_ > 1 then
            -- Etsitään/luodaan polun päätepiste.
            for i = 1, (#path_ - 1) do
                cur_p = path_[i]
                
                -- Jos type on "string", on annettu kaksi eritasoista samannimistä arvoa. Ohitetaan
                -- äänettömästi, koska monissa mallineissa on esim. "mon" ja "mon2".
                if type(cur_node[cur_p]) ~= "table" then
                    cur_node[cur_p] = {}
                end
                cur_node = cur_node[cur_p]
            end
            
            table.insert(cur_node, val)
        end
    end

    return out
end

--- Yhdistää kaksi taulukkoa uudeksi taulukoksi.
-- 
-- Jos molemmissa on sama avain, tulee uuteen taulukkoon jälkimmäinen.
function p.yhdista(eka, toka)
    local out = {}
    
    for k,v in pairs(eka) do
        out[k] = v
    end
    for k,v in pairs(toka) do
        out[k] = v
    end

    return out
end


--- Luo uuden taulukon taulukon `arr` pohjalta siten, että kukin arvo esiintyy vain kerran.
--
-- Elementtien järjestys noudattaa elementtien ensimmäisiä esiintymiä.
--
-- @param arr:    taulukko
-- @param hash_f: jos taulukon `arr` elementit ovat taulukoita, annetaan tällä parametrilla
--                hajautusarvon laskeva funktio. Funktion tulee olla sellainen, että se ottaa
--                alkion parametriksi ja paluttaa joko merkkijonon tai numeron siten, että
--                kaksi samanlaista alkiota palauttavat saman arvon eikä kaksi erilaista
--                alkiota palauta samaa arvoa.
-- @return:     taulukon arr elementit tuplat poistettuna
-- 
-- Esim. 1. Alkeistyypeillä
--   poista_tuplat({ "a", "b", "c", "a", "a", "d", "b" })
--   --> { "a", "b", "c", "d" }
--
-- Esim. 2. Taulukkotyypeillä
--   m.poista_tuplat({
--             { "burger", "m" }, 
--             { "berger", "f" },
--             { "burger", "m" },
--             { "berger", "m" },
--             { "berger", "f" },
--     }, function (item) return item[1] .. " " .. item[2] end)
--  )
--  --> {
--             { "burger", "m" }, 
--             { "berger", "f" },
--             { "berger", "m" }
--     }
-- 
function p.poista_tuplat(arr, hash_f)
    local out = {}
    local seen = {}
    local key
    
    for i, val in ipairs(arr) do
        if hash_f then
            key = hash_f(val)
        else
            assert ( type(val) ~= "table", "Tarvitaan hajautusfunktio" )
            key = val
        end

        if not seen[key] then
            out[#out + 1] = val
        end
        
        seen[key] = true
    end

    return out
end

function p.sisaltyy(taulukko, arvo)
    for i, val in ipairs(taulukko) do
        if val == arvo then
            return true
        end
    end

    return false
end

return p