英文:
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时,我需要检查它们之间是否存在循环关系。
例如:
- 产品A,id: 1,{ sub_products: [B] }
- 产品B,id: 2,{ sub_products: [C] }
- 用户尝试更新产品C,id: 3,{ sub_products: [A] } // 应该阻止这种情况
我考虑的是:
- 通过递归查询数据库来构建图形,以获取下一个子节点。其中顶点是产品,边表示父关系(从:父节点,到:子节点)。
- 运行来自 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/>
- Product A with id: 1 { sub_products: [B] }
- Product B with id: 2 { sub_products: [C] }
- User tries to update Product C with id: 3 with { sub_products: [A] } // should prevent this
<br/>
What I have in mind is:
- 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)
- 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
得分: 1
解决方法如下:
- 构建未访问节点的顶点/边。
- 运行问题中提到的算法,稍作调整 https://stackoverflow.com/a/53995651/13142405
export interface Edge<T> {
from: T;
to: T;
}
export class Graph<T> {
adjLists: Map<T, Set<T>> = new Map<T, Set<T>>();
copy(): Graph<T> {
const g = new Graph<T>();
g.adjLists = new Map([...this.adjLists]);
return g;
}
// immutable
merge(other: Graph<T>): Graph<T> {
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<T>): Graph<T> {
if (!this.adjLists.has(from)) this.adjLists.set(from, new Set<T>());
this.adjLists.get(from).add(to);
return this;
}
getCycles(): Edge<T>[] {
const discovered: Set<T> = new Set();
const finished: Set<T> = new Set();
let cycles: Edge<T>[] = [];
for (const u of [...this.adjLists.keys()]) {
if (!discovered.has(u) && !finished.has(u)) {
for (const cycle of getCyclesHelper<T>({
G: this,
u,
discovered,
finished,
})) {
cycles.push(cycle);
}
}
}
return cycles;
}
}
function getCyclesHelper<T>({
G,
u,
discovered,
finished,
}: {
G: Graph<T>;
u: T;
discovered: Set<T>;
finished: Set<T>;
}): Edge<T>[] {
discovered.add(u);
let cycles: Edge<T>[] = [];
for (const v of G.adjLists.get(u) ?? []) {
if (discovered.has(v)) {
const cycle: Edge<T> = {
from: u,
to: v,
};
cycles.push(cycle);
break;
}
if (!finished.has(v)) {
for (const cycle of getCyclesHelper<T>({
G,
u: v,
discovered,
finished,
})) {
cycles.push(cycle);
}
}
}
discovered.delete(u);
finished.add(u);
return cycles;
}
英文:
Solved this by:
- Constructing the vertex/edges of unvisited nodes.
- running the algorithm as referred to in the question with a slight tweak https://stackoverflow.com/a/53995651/13142405
export interface Edge<T> {
from: T;
to: T;
}
export class Graph<T> {
adjLists: Map<T, Set<T>> = new Map<T, Set<T>>();
copy(): Graph<T> {
const g = new Graph<T>();
g.adjLists = new Map([...this.adjLists]);
return g;
}
// immutable
merge(other: Graph<T>): Graph<T> {
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<T>): Graph<T> {
if (!this.adjLists.has(from)) this.adjLists.set(from, new Set<T>());
this.adjLists.get(from).add(to);
return this;
}
getCycles(): Edge<T>[] {
const discovered: Set<T> = new Set();
const finished: Set<T> = new Set();
let cycles: Edge<T>[] = [];
for (const u of [...this.adjLists.keys()]) {
if (!discovered.has(u) && !finished.has(u)) {
for (const cycle of getCyclesHelper<T>({
G: this,
u,
discovered,
finished,
})) {
cycles.push(cycle);
}
}
}
return cycles;
}
}
function getCyclesHelper<T>({
G,
u,
discovered,
finished,
}: {
G: Graph<T>;
u: T;
discovered: Set<T>;
finished: Set<T>;
}): Edge<T>[] {
discovered.add(u);
let cycles: Edge<T>[] = [];
for (const v of G.adjLists.get(u) ?? []) {
if (discovered.has(v)) {
const cycle: Edge<T> = {
from: u,
to: v,
};
cycles.push(cycle);
break;
}
if (!finished.has(v)) {
for (const cycle of getCyclesHelper<T>({
G,
u: v,
discovered,
finished,
})) {
cycles.push(cycle);
}
}
}
discovered.delete(u);
finished.add(u);
return cycles;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论