使用TypeORM/SQL/NestJS查找循环产品关系。

huangapple go评论62阅读模式
英文:

Finding cyclic product relations with TypeORM/SQL/NestJS

问题

Here is the translated content without the code:

我目前有一个像这样的Product类:

@Entity()
export class Product {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToMany(() => Product, (product) => product.id, {
    onDelete: 'RESTRICT',
    onUpdate: 'CASCADE',
  })
  @JoinTable({ joinColumn: { name: 'product_id_1' } })
  sub_products: Product[];

  ...
}

当用户想要创建/更新一个Product时,我需要检查它们之间是否存在循环关系。

例如:

  1. 产品A,id: 1,{ sub_products: [B] }
  2. 产品B,id: 2,{ sub_products: [C] }
  3. 用户尝试更新产品C,id: 3,{ sub_products: [A] } // 应该阻止这种情况

我考虑的是:

  1. 通过递归查询数据库来构建图形,以获取下一个子节点。其中顶点是产品,边表示父关系(从:父节点,到:子节点)。
  2. 运行来自 https://stackoverflow.com/a/53995651/13142405 的DFS循环检测算法。

我的问题是:

  • 为了完成(1),如果我只构建未访问节点的顶点/边,是否足够?也就是说,我会保持已访问产品的集合,如果我看到的下一个产品在这个集合中,我可以跳过其顶点/边的构建吗?
  • 是否足以通过以下测试来证明其正确性?
英文:

Currently I have a Product class like so:

@Entity()
export class Product {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToMany(() => Product, (product) => product.id, {
    onDelete: 'RESTRICT',
    onUpdate: 'CASCADE',
  })
  @JoinTable({ joinColumn: { name: 'product_id_1' } })
  sub_products: Product[];

  ...
}

When a user wants to create/update a Product, I'll need to check if there's a cyclic relation between them.

For example: <br/>

  1. Product A with id: 1 { sub_products: [B] }
  2. Product B with id: 2 { sub_products: [C] }
  3. User tries to update Product C with id: 3 with { sub_products: [A] } // should prevent this
    <br/>

What I have in mind is:

  1. Construct a graph recursively by querying the database to fetch the next children. Where vertices are products, and edges represent a parent relation (from: parent, to: child)
  2. Run DFS cycle detection algorithm from https://stackoverflow.com/a/53995651/13142405
    <br/>

My question is:

  • In order to do (1), would it suffice if I only construct the vertex/edges of unvisited nodes before? Meaning to say I'll keep a set of visited products, and if the next product I see is in this set, I can skip construction of its vertex/edge.
  • Would it suffice to prove its correctness with the following tests?
    <br/>

Tests:

  1. No Cycle <br/>
    使用TypeORM/SQL/NestJS查找循环产品关系。

  2. Trivial Cycle <br/>
    使用TypeORM/SQL/NestJS查找循环产品关系。

  3. Tree (Notice how 4 is visited twice but there's no cycle) <br/>
    使用TypeORM/SQL/NestJS查找循环产品关系。

  4. Non-Trivial Cycle <br/>
    使用TypeORM/SQL/NestJS查找循环产品关系。

答案1

得分: 1

解决方法如下:

  1. 构建未访问节点的顶点/边。
  2. 运行问题中提到的算法,稍作调整 https://stackoverflow.com/a/53995651/13142405
export interface Edge&lt;T&gt; {
  from: T;
  to: T;
}

export class Graph&lt;T&gt; {
  adjLists: Map&lt;T, Set&lt;T&gt;&gt; = new Map&lt;T, Set&lt;T&gt;&gt;();

  copy(): Graph&lt;T&gt; {
    const g = new Graph&lt;T&gt;();
    g.adjLists = new Map([...this.adjLists]);
    return g;
  }

  // immutable
  merge(other: Graph&lt;T&gt;): Graph&lt;T&gt; {
    const copy = this.copy();
    for (const [key, value] of other.adjLists) {
      if (!copy.adjLists.has(key)) {
        copy.adjLists.set(key, value);
      } else {
        // 处理冲突
        const cAdjList = copy.adjLists.get(key);
        copy.adjLists.set(key, new Set([...cAdjList, ...value]));
      }
    }
    return copy;
  }

  addEdge({ from, to }: Edge&lt;T&gt;): Graph&lt;T&gt; {
    if (!this.adjLists.has(from)) this.adjLists.set(from, new Set&lt;T&gt;());
    this.adjLists.get(from).add(to);
    return this;
  }

  getCycles(): Edge&lt;T&gt;[] {
    const discovered: Set&lt;T&gt; = new Set();
    const finished: Set&lt;T&gt; = new Set();
    let cycles: Edge&lt;T&gt;[] = [];
    for (const u of [...this.adjLists.keys()]) {
      if (!discovered.has(u) &amp;&amp; !finished.has(u)) {
        for (const cycle of getCyclesHelper&lt;T&gt;({
          G: this,
          u,
          discovered,
          finished,
        })) {
          cycles.push(cycle);
        }
      }
    }

    return cycles;
  }
}

function getCyclesHelper&lt;T&gt;({
  G,
  u,
  discovered,
  finished,
}: {
  G: Graph&lt;T&gt;;
  u: T;
  discovered: Set&lt;T&gt;;
  finished: Set&lt;T&gt;;
}): Edge&lt;T&gt;[] {
  discovered.add(u);
  let cycles: Edge&lt;T&gt;[] = [];
  for (const v of G.adjLists.get(u) ?? []) {
    if (discovered.has(v)) {
      const cycle: Edge&lt;T&gt; = {
        from: u,
        to: v,
      };
      cycles.push(cycle);
      break;
    }

    if (!finished.has(v)) {
      for (const cycle of getCyclesHelper&lt;T&gt;({
        G,
        u: v,
        discovered,
        finished,
      })) {
        cycles.push(cycle);
      }
    }
  }

  discovered.delete(u);
  finished.add(u);
  return cycles;
}
英文:

Solved this by:

  1. Constructing the vertex/edges of unvisited nodes.
  2. running the algorithm as referred to in the question with a slight tweak https://stackoverflow.com/a/53995651/13142405
export interface Edge&lt;T&gt; {
  from: T;
  to: T;
}

export class Graph&lt;T&gt; {
  adjLists: Map&lt;T, Set&lt;T&gt;&gt; = new Map&lt;T, Set&lt;T&gt;&gt;();

  copy(): Graph&lt;T&gt; {
    const g = new Graph&lt;T&gt;();
    g.adjLists = new Map([...this.adjLists]);
    return g;
  }

  // immutable
  merge(other: Graph&lt;T&gt;): Graph&lt;T&gt; {
    const copy = this.copy();
    for (const [key, value] of other.adjLists) {
      if (!copy.adjLists.has(key)) {
        copy.adjLists.set(key, value);
      } else {
        // handle collision
        const cAdjList = copy.adjLists.get(key);
        copy.adjLists.set(key, new Set([...cAdjList, ...value]));
      }
    }
    return copy;
  }

  addEdge({ from, to }: Edge&lt;T&gt;): Graph&lt;T&gt; {
    if (!this.adjLists.has(from)) this.adjLists.set(from, new Set&lt;T&gt;());
    this.adjLists.get(from).add(to);
    return this;
  }

  getCycles(): Edge&lt;T&gt;[] {
    const discovered: Set&lt;T&gt; = new Set();
    const finished: Set&lt;T&gt; = new Set();
    let cycles: Edge&lt;T&gt;[] = [];
    for (const u of [...this.adjLists.keys()]) {
      if (!discovered.has(u) &amp;&amp; !finished.has(u)) {
        for (const cycle of getCyclesHelper&lt;T&gt;({
          G: this,
          u,
          discovered,
          finished,
        })) {
          cycles.push(cycle);
        }
      }
    }

    return cycles;
  }
}

function getCyclesHelper&lt;T&gt;({
  G,
  u,
  discovered,
  finished,
}: {
  G: Graph&lt;T&gt;;
  u: T;
  discovered: Set&lt;T&gt;;
  finished: Set&lt;T&gt;;
}): Edge&lt;T&gt;[] {
  discovered.add(u);
  let cycles: Edge&lt;T&gt;[] = [];
  for (const v of G.adjLists.get(u) ?? []) {
    if (discovered.has(v)) {
      const cycle: Edge&lt;T&gt; = {
        from: u,
        to: v,
      };
      cycles.push(cycle);
      break;
    }

    if (!finished.has(v)) {
      for (const cycle of getCyclesHelper&lt;T&gt;({
        G,
        u: v,
        discovered,
        finished,
      })) {
        cycles.push(cycle);
      }
    }
  }

  discovered.delete(u);
  finished.add(u);
  return cycles;
}

huangapple
  • 本文由 发表于 2023年4月19日 15:43:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76051901.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定