๋ฐ˜์‘ํ˜•

์˜ˆ์‹œ ๋ฐ์ดํ„ฐ


Hello ์ธ๋ฑ์Šค: 0~1 / World ์ธ๋ฑ์Šค: 1~2 / ์ฝค๋งˆ ์ธ๋ฑ์Šค: 2~3, Inchala: 3~4

๋‹จ์–ด ํ˜น์€ ๋ฌธ์žฅ๋ถ€ํ˜ธ(' ์•„ํฌ์ŠคํŠธ๋กœํ”ผ ์ œ์™ธ)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋ถ„๋ฆฌํ•œ ํ† ํฐ ์ธ๋ฑ์Šค ์ •๋ณด๊ฐ€ ๋‹ด๊ธฐ๋Š” Part ๊ฐ์ฒด๊ฐ€ ์žˆ๋‹ค. ์ด Part ๊ฐ์ฒด๋Š” child ๋ฐฐ์—ด์„ ๊ฐ€์ง€๋ฉฐ, child ๋ฐฐ์—ด์˜ ๊ฐ ์š”์†Œ์ธ Part๋Š” ๋ถ€๋ชจ Part์˜ ํ† ํฐ ์ธ๋ฑ์Šค ๋ฒ”์œ„ ๋‚ด์˜ ์ธ๋ฑ์Šค๋ฅผ ๊ฐ€์ง„๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด ๋ถ€๋ชจ Part์˜ ํ† ํฐ ์ธ๋ฑ์Šค ๋ฒ”์œ„๊ฐ€ 2~13์ด๋ผ๋ฉด ์ž์‹ Part์˜ ํ† ํฐ ์ธ๋ฑ์Šค๋Š” 3~13(3~13, 4~13, ...) ํ˜น์€ 2~12(2~12, 2~11, ...) ๋ฒ”์œ„๋ฅผ ๊ฐ€์ง„๋‹ค.

[
  // parts ๋ฐฐ์—ด
  {
    id: 0, // part
    begin: 2,
    end: 13,
    kc: [{ id: 'kc-1', /* ... */ }],
    child: [ // parts ๋ฐฐ์—ด
      {
        id: 1, // part
        begin: 4,
        end: 13,
        kc: [{ id: 'kc-2', /* ... */ }],
        child: [{ /* ... */ }],
      },
    ],
  },
  {
    // ...
  },
];

 

์œ„์™€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์—์„œ ๊ฐ Part์˜ kc ๋ฐฐ์—ด์— ์žˆ๋Š” ํŠน์ • ์š”์†Œ๋ฅผ ์ถ”๊ฐ€/์‚ญ์ œ ํ•˜๋Š”๊ฒŒ ๋ชฉ์ . ์ฐธ๊ณ ๋กœ kc ๋ฐฐ์—ด์€ begin~end ๋ฒ”์œ„์˜ ๋ฌธ๋ฒ•์  ํŠน์„ฑ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

 

KC ์‚ญ์ œ | BFS


๋ชฉํ‘œ

BFS ํƒ์ƒ‰ ์ˆœ์„œ

ํ˜„์žฌ ๋ ˆ๋ฒจ์— ์žˆ๋Š” ๋ชจ๋“  part์˜ kc ๋ฐฐ์—ด์„ ํƒ์ƒ‰ํ•˜๋ฉด์„œ ์ž…๋ ฅ๋ฐ›์€ kcId๋ฅผ ์ฐพ์•„ ์‚ญ์ œํ•œ๋‹ค. ํ˜„์žฌ ๋ ˆ๋ฒจ ๊นŠ์ด์—์„œ kcId๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋ฉด ๋‹ค์Œ ๋ ˆ๋ฒจ ๊นŠ์ด์˜ part ์š”์†Œ๋“ค์„ ๊ฒ€์‚ฌํ•˜๋Š” ๋„ˆ๋น„ ์šฐ์„  ํƒ์ƒ‰ ๋ฐฉ์‹ ์‚ฌ์šฉ.

 

๊ตฌํ˜„

์žฌ๊ท€ํ•จ์ˆ˜ ๋Œ€์‹  queue๋กœ ๊ตฌํ˜„. while ๋ฌธ์„ ์ˆœํšŒํ•  ๋•Œ๋งˆ๋‹ค queue ๊ฐ€์žฅ ์•ž์ชฝ ์š”์†Œ๋ฅผ ์ถ”์ถœํ•œ ํ›„ ๊ฒ€์‚ฌ. part.kc ๋ฐฐ์—ด ๋‚ด์—์„œ ์ž…๋ ฅ๋ฐ›์€ kcId๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋ฉด ํ˜„์žฌ part์˜ ๋ชจ๋“  child ์š”์†Œ๋ฅผ queue์— ์ถ”๊ฐ€ํ•˜๊ณ  ํƒ์ƒ‰ ๋ฐ˜๋ณต.

const removeKCById = (parts: Array<Part>, kcId: KC['id']) => {
  const newParts: Array<Part> = structuredClone(parts); // ๊นŠ์€ ๋ณต์‚ฌ
  const queue = [...newParts]; // ํƒ์ƒ‰ํ•  parts
  let found = false;

  while (queue.length > 0) {
    const currentPart = queue.shift();

    if (currentPart?.kc.length) {
      // filter ํ›„์˜ part.kc ๋ฐฐ์—ด ๊ธธ์ด์™€ ๋น„๊ตํ•˜๊ธฐ ์œ„ํ•ด ์›๋ณธ kc ๋ฐฐ์—ด ๊ธธ์ด ์ž„์‹œ ์ €์žฅ
      const originalLength = currentPart.kc.length;

      // ์ž…๋ ฅ๋ฐ›์€ kc.id๋ฅผ ์ฐพ์œผ๋ฉด kc ๋ฐฐ์—ด์—์„œ ์ œ์™ธ
      currentPart.kc = currentPart.kc.filter(({ id }) => id !== kcId);

      // originalLength ๊ธธ์ด๊ฐ€ ๋” ํฌ๋ฉด kc.id๋ฅผ ์ฐพ์•„ ์‚ญ์ œํ–ˆ์œผ๋ฏ€๋กœ ํ•จ์ˆ˜ ์ข…๋ฃŒ
      if (originalLength > currentPart.kc.length) {
        found = true;
        break;
      }
    }

    // ํ˜„์žฌ part.kc ๋ฐฐ์—ด์—์„œ ์ž…๋ ฅ๋ฐ›์€ kc.id๋ฅผ ์ฐพ์ง€ ๋ชปํ•˜๋ฉด queue์— ์ถ”๊ฐ€ํ•œ ํ›„ ์œ„ ๊ณผ์ • ๋ฐ˜๋ณต
    if (currentPart?.child?.length) queue.push(...currentPart.child);
  }

  if (!found) console.warn(`No KC with id ${kcId} was found.`);

  return newParts;
};

 

KC ์ถ”๊ฐ€ | DFS


 

๋ชฉํ‘œ

๋ฌธ์ž์—ด์˜ ํ† ํฐ ์ธ๋ฑ์Šค๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” begin/end์™€ ๋ฌธ๋ฒ•์  ํŠน์„ฑ์„ ๋‚˜ํƒ€๋‚ด๋Š” kc๋ฅผ ๋ฐ›์•„, ํ•ด๋‹น ์ธ๋ฑ์Šค ๋ฒ”์œ„์™€ ์ผ์น˜ํ•˜๋Š” part๋ฅผ ์ฐพ์•„ kc ๋ฐฐ์—ด์— ์ถ”๊ฐ€ํ•œ๋‹ค. ์ผ์น˜ํ•˜๋Š” part๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ๋‹ค๋ฉด ์ƒˆ๋กœ์šด part๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

begin/end ๋ฒ”์œ„๋ฅผ ํฌํ•จํ•˜์ง€ ์•Š๋Š” part์˜ ์ž์‹์€ ํƒ์ƒ‰ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

 

์˜ˆ๋ฅผ๋“ค์–ด begin 8, end 9๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•˜์„ ๋•Œ ํ•ด๋‹น ์ธ๋ฑ์Šค ๋ฒ”์œ„๋ฅผ ํฌํ•จํ•˜๋Š”, begin ๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™๊ณ  end ๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™์€ part๋ฅผ ๊นŠ์ด ์šฐ์„  ๋ฐฉ์‹์œผ๋กœ ํƒ์ƒ‰ํ•ด ๋‚˜๊ฐ€์•ผ ํ•œ๋‹ค(์œ„ ์ด๋ฏธ์ง€ ์™ผ์ชฝ ์ผ€์ด์Šค ์ฐธ๊ณ ).

 

๊ตฌํ˜„

const addKCToPart = (parts: Part[], begin: number, end: number, kc: KC) => {
  const newParts: Array<Part> = structuredClone(parts); // ๊นŠ์€ ๋ณต์‚ฌ

  for (const part of newParts) {
    // begin, end ๋ฒ”์œ„์™€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•œ๋‹ค๋ฉด part.kc ๋ฐฐ์—ด์— ์ถ”๊ฐ€ํ•œ ํ›„ ํ•จ์ˆ˜ ์ข…๋ฃŒ
    if (part.begin === begin && part.end === end) {
      part.kc.push(kc);
      return newParts;
    }

    // begin ๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™๊ณ , end๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™์œผ๋ฉด ๋ฒ”์œ„๋‚ด์— ์žˆ์œผ๋ฏ€๋กœ ์ž์‹ part ํƒ์ƒ‰
    if (part.begin <= begin && part.end >= end) {
      // ์ž์‹ part ์žฌ๊ท€์ ์œผ๋กœ ํƒ์ƒ‰
      part.child = addKCToPart(part.child, begin, end, kc);
      return newParts;
    }
  }

  /** ๋งค์นญํ•˜๋Š” part ์—†์œผ๋ฉด ์ƒˆ๋กœ์šด part ์ƒ์„ฑ */
  newParts.push({
    id: uuidv4(),
    begin,
    end,
    kc: [kc],
    child: [],
  });

  return newParts;
};
๋”๋ณด๊ธฐ

์ƒˆ๋กœ์šด ๋…ธ๋“œ(Part)๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ํ•ด๋‹น ๋ฒ”์œ„๋‚ด ๋‹ค๋ฅธ ๋…ธ๋“œ๋ฅผ ์ž์‹์œผ๋กœ ์˜ฎ๊ธฐ๋Š” ์‹ฌํ™” ๋ฒ„์ „ โ–ผ

const cloneParts = (parts: Part[]): Part[] => structuredClone(parts);

const moveToPart = (parts: Part[], begin: number, end: number): Part[] => {
  // flatMap์€ 1depth๊นŒ์ง€ ๋ฐฐ์—ด์„ ํŽผ์น˜๊ณ , ๋นˆ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๊ฒฐ๊ณผ์— ํฌํ•จํ•˜์ง€ ์•Š์Œ
  return parts.flatMap((part) => {
    if (
      (part.begin > begin && part.end <= end) ||
      (part.begin >= begin && part.end < end)
    )
      return [part];
    else return moveToPart(part.child, begin, end);
  });
};

export const addKCToPart = (
  parts: Part[],
  begin: number,
  end: number,
  kc: KC,
): Part[] => {
  const newParts = cloneParts(parts);

  for (const part of newParts) {
    if (part.begin === begin && part.end === end) {
      part.kc.push(kc);
      return newParts;
    }

    if (part.begin <= begin && part.end >= end) {
      part.child = addKCToPart(part.child, begin, end, kc, onInvalidCallback);
      return newParts;
    }
  }

  if (newParts.length === 0) return [getNewPart(begin, end, [kc])];

  const left = getNewPart(begin, end, [kc]);
  const right = getNewPart(end, newParts[newParts.length - 1].end);

  left.child = moveToPart(newParts, left.begin, left.end);
  right.child = moveToPart(newParts, right.begin, right.end);

  return [left, right];
};

 

Empty Part ์‚ญ์ œ | DFS


DFS ํƒ์ƒ‰ ์ˆœ์„œ

part.kc์™€ part.child ๊ธธ์ด๊ฐ€ ๋ชจ๋‘ 0์ด๋ฉด ๋”์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” part์ด๋ฏ€๋กœ ์‚ญ์ œํ•ด์•ผ ํ•œ๋‹ค. ๋ชจ๋“  ๋…ธ๋“œ๋ฅผ ๋ฐฉ๋ฌธํ•ด์„œ ํ™•์ธํ•ด์•ผ ํ•˜๋ฏ€๋กœ DFS, BFS ์–ด๋–ค ๊ฒƒ์„ ์‚ฌ์šฉํ•ด๋„ ๋ฌด๋ฐฉํ•˜๋‹ค. part.child ๊ธธ์ด๊ฐ€ 1์ด์ƒ์ด๋ฉด ์‚ฌ์šฉํ•˜๋Š” part๋กœ ํŒ๋‹จํ•˜๋ฏ€๋กœ, ๊ฐ€์žฅ ๊นŠ์€ ๋…ธ๋“œ๊ฐ€ ๋น„์–ด์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋งŽ๋‹ค. ๋”ฐ๋ผ์„œ ๊นŠ์ด ์šฐ์„  ํƒ์ƒ‰์œผ๋กœ ๊ตฌํ˜„ํ•œ๋‹ค.

const removeEmptyKCParts = (parts: Part[]) => {
  return parts.reduce((acc: Part[], part: Part) => {
    // ๊นŠ์ด ์šฐ์„  ํƒ์ƒ‰์„ ์œ„ํ•ด child๊ฐ€ ์žˆ๋‹ค๋ฉด child ์žฌ๊ท€ํ˜ธ์ถœ
    if (part.child.length) part.child = removeEmptyKCParts(part.child);

    // ํ˜„์žฌ part์˜ ์‚ฌ์šฉ ์—ฌ๋ถ€์— ๋Œ€ํ•œ ์กฐ๊ฑด
    if (part.kc.length || part.child.length) {
      acc.push(part);
    }

    return acc;
  }, []);
};

 

Parts ์ •๋ ฌ | DFS


DFS ํƒ์ƒ‰ ์ˆœ์„œ

parts ๋ฐฐ์—ด์—์„œ ๊ฐ€์žฅ ๋„“์€ ๋ฒ”์œ„์˜ ์ธ๋ฑ์Šค๋ฅผ ๊ฐ€์ง€๋Š” part๊ฐ€ ๋ฐฐ์—ด ์•ž์ชฝ์— ์˜ค๋„๋ก ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌํ•œ๋‹ค. begin + end ํ•ฉ์ด ํด ์ˆ˜๋ก ๋” ๋„“์€ ๋ฒ”์œ„๋ฅผ ๊ฐ€์ง„๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ •๋ ฌ ํ›„์—” ๊ฐ part๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ child ๋ฐฐ์—ด(parts) ๊ธธ์ด๊ฐ€ 1 ์ด์ƒ์ธ์ง€ ๊ฒ€์‚ฌํ•˜๊ณ , ์ฐธ์ด๋ฉด ์žฌ๊ท€ํ˜ธ์ถœํ•ด์„œ ์ž์‹ parts ๋ฐฐ์—ด๋„ ์ •๋ ฌํ•˜๋Š” ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•œ๋‹ค.

const sortParts = (parts: Part[]) => {
  return parts
    .sort((a, b) => b.begin + b.end - (a.begin + a.end))
    .map((part) => {
      if (part.child.length) part.child = sortParts(part.child);
      return part;
    });
};
๋ฐ˜์‘ํ˜•