見出し画像

TypeScriptを活用した階層構造データのランダム生成テクニック

こんにちわ。nap5です。

TypeScriptを活用した階層構造データのランダム生成テクニックの紹介です。

世代データだけに限らず、いろいろ応用が利きそうです。

実装です。

type Item = {
  id: number;
  name: string;
};

type TreeNode = {
  id: number;
  name: string;
  children: TreeNode[];
};

type Visited = Set<number>;

const itemFactory = (
  items: Item[],
  visited: Visited,
): (() => [Item, Visited]) => {
  return (): [Item, Visited] => {
    const availableItems = items.filter((item) => !visited.has(item.id));
    if (availableItems.length === 0) {
      throw new Error("No more available items to choose from.");
    }
    const randomIndex = Math.floor(Math.random() * availableItems.length);
    const selectedItem = availableItems[randomIndex];
    visited.add(selectedItem.id);
    return [selectedItem, visited];
  };
};

const clamp = (value: number, min: number, max: number): number => {
  return Math.min(Math.max(value, min), max);
};

const makeData = (seriesItems: Item[][], ...lens: number[]): TreeNode[] => {
  for (let i = 0; i < lens.length; i++) {
    if (lens[i] > seriesItems[i].length) {
      throw new Error(
        `The number of items selected (${lens[i]}) cannot be greater than the number of items in the seed list (${seriesItems[i].length}).`,
      );
    }
  }

  const makeDataLevel = (
    depth: number = 0,
    visited: Visited = new Set(),
  ): TreeNode[] => {
    const availableItems = seriesItems[depth].filter(
      (item) => !visited.has(item.id),
    );
    const actualLen = clamp(lens[depth], 0, availableItems.length); // Use the length of availableItems here
    const getItem = itemFactory(seriesItems[depth], visited);

    return Array.from({ length: actualLen }).map((): TreeNode => {
      const [item, newVisitedForItem] = getItem();
      // Pass the 'newVisitedForItem' set to the recursive call instead of creating a new set
      const children = lens[depth + 1]
        ? makeDataLevel(depth + 1, newVisitedForItem)
        : [];
      children.forEach((child) => {
        newVisitedForItem.add(child.id); // Add the ids of the children to the visited set
      });
      return {
        ...item,
        children,
      };
    });
  };

  return makeDataLevel();
};

// 上の世代には少なめのバリエーション、下の世代には多めのバリエーションを設ける
// こうすることで、Populatedされたデモデータが作られやすくなります
const generation1: Item[] = [
  { id: 1, name: "Adam" },
  { id: 2, name: "Eve" },
  { id: 3, name: "Lilith" },
  { id: 4, name: "Lucas" },
];

const generation2: Item[] = [
  { id: 5, name: "Cain" },
  { id: 6, name: "Abel" },
  { id: 7, name: "Seth" },
  { id: 8, name: "Nina" },
  { id: 9, name: "Ian" },
  { id: 10, name: "Aria" },
];

const generation3: Item[] = [
  { id: 11, name: "Enos" },
  { id: 12, name: "Kenan" },
  { id: 13, name: "Mahalalel" },
  { id: 14, name: "Eliana" },
  { id: 15, name: "Nolan" },
  { id: 16, name: "Dara" },
];

export const generateDemoData = (): TreeNode[] => {
  return makeData([generation1, generation2, generation3], 1, 2, 3);
};


demo code.



簡単ですが、以上です。


いいなと思ったら応援しよう!