~nch/gamelog

ref: 75432213d3972f26c03ef636da68ed482f0ac908 gamelog/df.lua -rw-r--r-- 4.0 KiB View raw
75432213 — nc refactoring debug code out a bit 5 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
local fun = require 'fun'

function ref_table(parent)
    return setmetatable({}, {__index = parent})
end

local variable_mt = {
    __tostring = function(t)
        return '<' .. t.name .. '>'
    end,
    __eq = function(a, b)
        return a.name == b.name
    end
}
function v(name)
    return setmetatable({ name = name }, variable_mt)
end

function is_var(x)
    return getmetatable(x) == variable_mt
end

function chase(env, v)
    if is_var(v) then
        if env[v.name] ~= nil then
            return chase(env, env[v.name])
        end
    end
    return v
end

function unify(a, b, env) --> env | nil
    env = (env or {}) -- make sure env isn't nil
    if (type(a) == 'table' and not is_var(a)) and (type(b) == 'table' and not is_var(b)) then
        if (#a ~= #b) then return nil end
        local r = {}
        for _, x, y in fun.zip(a, b) do
            local u = unify(x, y, env)
            if u == nil then
                return nil
            else
                for k, v in pairs(u) do
                    r[k] = v
                end
                -- ehh... not my finest work
                --env = u
            end
        end
        return r
    else
        local t1 = chase(env, a)
        local t2 = chase(env, b)
        if is_var(t1) and not is_var(t2) then
            env[t1.name] = t2
            return env
        elseif is_var(t2) and not is_var(t1) then
            env[t2.name] = t1
            return env
        elseif t1 == t2 then
            return env
        else
            return nil
        end
    end
    return env
end

local function test_unify()
    assert(unify(v'a', 1, {a = 2}) == nil)
    assert(unify(1, 1) ~= nil)
    assert(unify(v'a', 1)['a'] == 1)
    assert(unify(v'a', 1, {a = 2}) == nil)
    local u1 = unify({v'a', {v'b', 2}}, {1, {2, 2}})
    assert(u1['a'] == 1)
    assert(u1['b'] == 2)
    assert(unify({v'a', {v'a'}, 3}, {'x', {'x'}, 3})['a'] == 'x')
    assert(unify({v'a', {v'a'}, 3}, {'x', {v'a'}, 3})['a'] == 'x')
    assert(unify({v'a', {v'a'}, 3}, {1, {2}, 3}) == nil)
    assert(unify({v'a', {v'a'}, 3}, {1, {1}, v'a'}) == nil)
end

function make_df(data_tuples)
    local df = {c = {}}
    local col_names = {}

    df.nrows = #data_tuples
    df.row = function(self, i)
        return fun.map(function(col) return self.c[col][i] end, col_names):totable()
    end

    df.rows = function(self)
        return fun.map(function(row_i)
            return self:row(row_i)
        end, fun.range(self.nrows))
    end

    df.insert = function(self, t)
        for _, c in pairs(col_names) do
            table.insert(self.c[c], t[c])
        end
    end

    if #data_tuples == 0 then
        return df
    end

    for c, _ in pairs(data_tuples[1]) do
        -- save column names
        table.insert(col_names, c)
        -- and initialize column in df
        df.c[c] = {}
    end

    fun.each(function(t) df:insert(t) end, data_tuples)
    return df
end

local function test_df()
    local t = make_df({{'a', 'b', 'c'}, {'d', 'e', 'f'}})
    assert(#t:row(1) == 3)
    assert(t:row(1)[1] == 'a')
    for _, r in t:rows() do
        assert(#r == 3)
    end
    assert(#t:rows():totable() == 2)
end

function make_db()
    return {
        tables = {},
        query = function(self, name, ...)
            local ts = {}
            for _, r in self.tables[name]:rows() do
                if unify(r, {...}) then
                    table.insert(ts, r)
                end
            end
            if #ts == 0 then
                return nil
            end
            return make_df(ts)
        end,
        insert = function(self, name, ...)
            if self.tables[name] == nil then
                self.tables[name] = make_df({{...}})
            else
                self.tables[name].insert({...})
            end
        end
    }
end

local function test_db()
    local db = make_db()
    db:insert('a', 7, 3)
    db:insert('b', 9, 1)
    assert(db:query('a', v'x') == nil)
    assert(db:query('a', v'x', v'y'):row(1)[1] == 7)
end

-- TODO implement joining similarly to datalog