@kwnstantin

Как работает эта функция по повороту таблицы PivotTable?

Функция PivotTable (поворот, пивот таблицы) позволяет хитрым образом перегруппировывать данные, получать HTML-таблицы из массивов записей. Например, можно получить таблицу форм древнегреческих глаголов, как в учебниках грамматики.

Код функции:

/*
     Create table HTML: only pass data (no labels). 
     Pass column indexes/keys for:
     - the column with the values for the cells, 
     - the row headers, and 
     - the column headers
     Optionally provide callback for aggregating data that ends up in same cell
    */


    function pivotTable(data, valueColumn, groupColumns, pivotColumns, aggregator = x=>x.length ? x[0] : "") {
        // Helper function
        function span(...args) {
            return [" rowspan", " colspan"].map((dim, i) =>
                args[i] > 1 ? dim + '="' + args[i] + '"' : ""
            ).join``;
        }

        // Structure dimensions:
        const [groups, pivots] = [groupColumns, pivotColumns].map(columns => {
            const colMap = {};
            for (const row of data) {
                columns.reduce((acc, colNo) => (acc[row[colNo]] = acc[row[colNo]] || {}), colMap);  
            }
            const headers = columns.map(col => []);
            const tree = (function process(colMap, y=0, x=0) {
                return Object.entries(colMap).reduce((acc, [title, obj]) => {
                    const result = process(obj, y+1, x);
                    const size = result.size || 1;
                    result.title = title;
                    acc.children[title] = result;
                    acc.size += size;
                    headers[y].push(result);
                    x += size;
                    return acc;
                }, { title: null, x, size: 0, children: {} });
            })(colMap);
            tree.headers = headers;
            return tree;
        });
        
        // Collect data in matrix, per dimensions:
        const matrix = Array.from({length:groups.size}, row => Array.from({length:pivots.size}, cell => []));
        for (const row of data) {
            const y = groupColumns.reduce((acc, colNo) => acc.children[row[colNo]], groups).x;
            const x = pivotColumns.reduce((acc, colNo) => acc.children[row[colNo]], pivots).x;
            matrix[y][x].push(row[valueColumn]);
        }
        matrix.forEach(row => row.forEach((values,i) => row[i] = aggregator(values)));
        
        // Produce HTML for column headers:
        let html = `<tr><th${span(groupColumns.length, pivotColumns.length)}></th>${
            pivots.headers.map(header => header.map(o => `<th${span(1, o.size)}>${o.title}</th>`).join``).join("</tr>\n<tr>")
        }</tr>`;

        // Produce HTML for the row headers and cells:
        let startRow = true;
        let y = 0;
        (function loop(children, depth=0) {
            Object.values(children).map(group => {
                if (startRow) html += "<tr>";
                startRow = false;
                html += `<th${span(group.size, 1)}>${group.title}</th>`;
                loop(group.children, depth+1);
            });
            if (depth >= groupColumns.length) {
                html += `${matrix[y].map(value => `<td>${value}</td>`).join``}</tr>\n`;
                startRow = true;
                y++;
            }
        })(groups.children);
        
        return html;
    }


Пример. Имеются данные в таком виде:
// Demo where data is structured as array of arrays:
const data = [["lemma","form","tense","mood","voice","person","number"],
["πιστεύω","πεπίστευκα","perf","ind","act","1st","sg"],
["πιστεύω","πεπιστεύκαμεν","perf","ind","act","1st","pl"],
["πιστεύω","πεπίστευκας","perf","ind","act","2nd","sg"],
["πιστεύω","πεπιστεύκατε","perf","ind","act","2nd","pl"],
["πιστεύω","πεπιστεύκειν","perf","inf","act","",""],
["πιστεύω","πεπιστεύκειν","plup","ind","act","1st","sg"],
["πιστεύω","πεπιστεύκεισαν","plup","ind","act","3rd","pl"],
["πιστεύω","πεπίστευκεν","perf","ind","act","3rd","sg"],
["πιστεύω","πεπίστευκεν","perf","inf","act","",""],
["πιστεύω","πεπίστευκεν","perf","inf","act","3rd","sg"],
["πιστεύω","πεπίστευκεν","plup","ind","act","3rd","pl"],
["πιστεύω","πεπιστευκέναι","perf","inf","act","",""],
["πιστεύω","πεπίστευμαι","perf","ind","mp","1st","sg"],
["πιστεύω","πεπίστευμαι","perf","ind","pass","1st","sg"],
["πιστεύω","πεπίστευνται","perf","ind","mid","3rd","pl"],
["πιστεύω","πεπίστευνται","perf","ind","mp","3rd","pl"],
["πιστεύω","ἐπεπίστευντο","plup","ind","mp","3rd","pl"],
["πιστεύω","πεπίστευται","perf","ind","mid","3rd","sg"],
["πιστεύω","πεπίστευται","perf","ind","mp","3rd","sg"],
["πιστεύω","ἐπεπίστευτο","plup","ind","mp","3rd","sg"],
["πιστεύω","πιστεῦσαι","aor","imperat","mid","2nd","sg"],
["πιστεύω","πιστεῦσαι","aor","inf","act","",""],
["πιστεύω","πιστεῦσαι","aor","inf","act","3rd","sg"]]

//вызов будет такой:
 const html = pivotTable(data.slice(1), 1, [0, 6, 5], [2, 3, 4]);
    document.querySelector("table").innerHTML = html;


Или в таком виде:
const data=[
        {lemma: "πιστεύω", form: "πεπίστευκα", tense: "perf", mood: "ind", voice: "act", person: "1st", number "sg"},
       {lemma: "πιστεύω", form: "πεπιστεύκαμεν", tense: "perf", mood: "ind", voice: "act", person: "1st", number: "pl"},
      {lemma:"πιστεύω", form:"πιστεῦσαι", tense:"aor",mood:"inf",voice:"act"]
        /* ... */
    ];

   //В таком случае вызывается так:
    const html = pivotTable(data, "form", ["lemma", "number", "person"], ["tense", "mood", "voice"]);


Функция выдаёт таблицу:
<table><tbody><tr><th rowspan="3" colspan="3"></th><th colspan="5">perf</th><th colspan="2">plup</th><th colspan="10">aor</th><th colspan="7">pres</th><th>imperf</th><th colspan="3">fut</th></tr>
<tr><th colspan="4">ind</th><th>inf</th><th colspan="2">ind</th><th colspan="2">imperat</th><th>inf</th><th>opt</th><th colspan="3">ind</th><th colspan="3">subj</th><th colspan="2">ind</th><th colspan="2">subj</th><th>imperat</th><th colspan="2">inf</th><th>ind</th><th colspan="2">ind</th><th>inf</th></tr>
<tr><th>act</th><th>mp</th><th>pass</th><th>mid</th><th>act</th><th>act</th><th>mp</th><th>mid</th><th>act</th><th>act</th><th>act</th><th>act</th><th>pass</th><th>mid</th><th>pass</th><th>act</th><th>mid</th><th>act</th><th>mp</th><th>act</th><th>mp</th><th>act</th><th>act</th><th>mp</th><th>act</th><th>mid</th><th>act</th><th>act</th></tr><tr><th rowspan="7">πιστεύω</th><th rowspan="3">sg</th><th>1st</th><td>πεπίστευκα</td><td>πεπίστευμαι</td><td>πεπίστευμαι</td><td></td><td></td><td>πεπιστεύκειν</td><td></td><td></td><td></td><td></td><td></td><td>ἐπίστευσα</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>πιστεύω</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
<tr><th>2nd</th><td>πεπίστευκας</td><td></td><td></td><td></td><td></td><td></td><td></td><td>πιστεῦσαι</td><td>πίστευσον</td><td></td><td></td><td>πιστεύσας</td><td></td><td>πιστεύσω</td><td></td><td>πιστεύσεις</td><td>πιστεύσῃ</td><td>πιστεύεις</td><td>πιστεύῃ</td><td>πιστεύῃς</td><td>πιστεύῃ</td><td>πίστευε</td><td></td><td></td><td></td><td>πιστεύσῃ</td><td>πιστεύσεις</td><td></td></tr>
<tr><th>3rd</th><td>πεπίστευκεν</td><td>πεπίστευται</td><td></td><td>πεπίστευται</td><td>πεπίστευκεν</td><td></td><td>ἐπεπίστευτο</td><td></td><td></td><td>πιστεῦσαι</td><td>πιστεῦσαι</td><td>ἐπίστευσε</td><td>ἐπιστεύθη</td><td></td><td>πιστευθῇ</td><td>πιστεύσῃ</td><td></td><td>πιστεύῃ</td><td>πιστεύεται</td><td>πιστεύῃ</td><td></td><td>πιστευέτω</td><td></td><td></td><td>πίστευε</td><td></td><td>πιστεύσει</td><td></td></tr>
<tr><th rowspan="3">pl</th><th>1st</th><td>πεπιστεύκαμεν</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>ἐπιστεύσαμεν</td><td></td><td></td><td></td><td>πιστεύσομεν</td><td></td><td></td><td></td><td>πιστεύωμεν</td><td></td><td></td><td></td><td></td><td></td><td></td><td>πιστεύσομεν</td><td></td></tr>
<tr><th>2nd</th><td>πεπιστεύκατε</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>πιστεύσατε</td><td></td><td></td><td>ἐπιστεύσατε</td><td></td><td></td><td></td><td>πιστεύσετε</td><td></td><td>πιστεύετε</td><td></td><td>πιστεύητε</td><td></td><td>πιστεύετε</td><td></td><td></td><td>πιστεύετε</td><td></td><td>πιστεύσετε</td><td></td></tr>
<tr><th>3rd</th><td></td><td>πεπίστευνται</td><td></td><td>πεπίστευνται</td><td></td><td>πεπιστεύκεισαν</td><td>ἐπεπίστευντο</td><td></td><td>πιστευσάντων</td><td></td><td></td><td>πιστεῦσαν</td><td></td><td></td><td></td><td>πιστεύσουσιν</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>πιστεύσουσι</td><td></td></tr>
<tr><th></th><th></th><td></td><td></td><td></td><td></td><td>πεπιστεύκειν</td><td></td><td></td><td></td><td></td><td>πιστεῦσαι</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td>πιστεύειν</td><td>πιστεύεσθαι</td><td></td><td></td><td></td><td>πιστεύσειν</td></tr>
</tbody></table>


Но как функция достигает такой результат - непонятно, она как черный ящик. Отсюда вопрос: не могли бы вы дать словесное пошаговое объяснение алгоритма этой функции?
Спасибо.
  • Вопрос задан
  • 49 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы
Artezio Нижний Новгород
от 130 000 до 180 000 ₽
Artezio Москва
от 160 000 до 220 000 ₽
Artezio Могилев
от 2 800 до 3 300 $