BIツールなどに見られるTopN件のデータ取得をTypescriptで実現するやり方
こんにちわ。nap5です。
BIツールなどに見られるTopN件のデータ取得をTypescriptで実現するやり方の紹介です。
定義側になります。
import { Path } from "dot-path-value";
export type TRow = Record<string, unknown>;
export type TRowWithNumber<T extends TRow> = T & Record<string, number>;
export const compare = <T extends TRow>(fields: Path<T>[], a: T, b: T): number => {
for (const field of fields) {
if (a[field] < b[field]) return -1;
if (a[field] > b[field]) return 1;
}
return 0;
};
export const partitionBy = <T extends TRow>(data: T[], fields: Path<T>[]): T[][] =>
data.reduce((acc: T[][], row: T) => {
if (
acc.length > 0 &&
compare(
fields,
acc[acc.length - 1][acc[acc.length - 1].length - 1],
row
) === 0
) {
// 現在の行と直前のパーティションの最後の行が指定されたフィールドすべてで同じ場合に
// 直前のパーティションに現在の行を追加する
acc[acc.length - 1].push(row);
} else {
// 新しいパーティションを作成し、現在の行を追加する
acc.push([row]);
}
return acc;
}, []);
export const rowNumber = <T extends TRow>(
data: T[][],
alias: string = "rowNumber"
): TRowWithNumber<T>[][] =>
data.map((d) =>
d.map((row, index) => ({
...row,
[alias]: index + 1,
}))
);
export const topN = <T extends TRowWithNumber<TRow>>(
data: T[][],
n: number,
alias: string = "rowNumber"
): T[][] => data.map((d) => d.filter((row) => row[alias] <= n));
使用側になります。
import { test, expect, describe } from "vitest";
import { partitionBy, rowNumber, topN } from ".";
import { pipe } from "fp-ts/lib/function";
import { bind, bindTo, map } from "fp-ts/lib/Identity";
import { arrange, asc, desc, tidy } from "@tidyjs/tidy";
describe("TopN件のデモ", () => {
test("カテゴリ別のTop3件", () => {
type Demo = {
id: number;
name: string;
category: string;
score: number;
};
const data: Demo[] = [
{ id: 1, name: "Alice", category: "A", score: 100 },
{ id: 2, name: "Bob", category: "A", score: 105 },
{ id: 3, name: "Charlie", category: "A", score: 98 },
{ id: 4, name: "Derek", category: "A", score: 102 },
{ id: 5, name: "Eva", category: "A", score: 101 },
{ id: 6, name: "Frank", category: "A", score: 96 },
{ id: 7, name: "George", category: "B", score: 110 },
{ id: 8, name: "Helen", category: "B", score: 112 },
{ id: 9, name: "Ian", category: "B", score: 108 },
{ id: 10, name: "Jane", category: "B", score: 111 },
{ id: 11, name: "Kyle", category: "B", score: 107 },
];
const result = pipe(
data,
bindTo("input"),
bind("ordered", ({ input }) =>
tidy(input, arrange([asc((d) => d.category), desc((d) => d.score)]))
),
bind("partitioned", ({ ordered }) => partitionBy(ordered, ["category"])),
bind("ranked", ({ partitioned }) => rowNumber(partitioned, "rank")),
bind("output", ({ ranked }) => topN(ranked, 3, "rank")),
map(({ output }) => output)
);
expect(result).toStrictEqual([
[
{ id: 2, name: "Bob", category: "A", score: 105, rank: 1 },
{ id: 4, name: "Derek", category: "A", score: 102, rank: 2 },
{ id: 5, name: "Eva", category: "A", score: 101, rank: 3 },
],
[
{ id: 8, name: "Helen", category: "B", score: 112, rank: 1 },
{ id: 10, name: "Jane", category: "B", score: 111, rank: 2 },
{ id: 7, name: "George", category: "B", score: 110, rank: 3 },
],
]);
});
});
demo code.
簡単ですが、以上です。
この記事が気に入ったらサポートをしてみませんか?