- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
在我的项目中,我将给定的移动应用程序表示为有向图。移动应用程序的每个屏幕都充当图形节点和从一个屏幕到下一个屏幕的边链接。边缘存储在每个父屏幕内。还使用深度优先搜索将每条边分类为 TREE、FORWARD、CROSS 或 BACK 边。现在一切正常。
我的问题是我需要将两个图合并为一个图。这两个图可能代表移动应用程序的不同构建。一个版本可能有以下链:
Screen#1 -> Screen#2 -> Screen#3
下一个版本可能有如下链:
Screen#1 -> Screen#X -> Screen#2 -> Screen#3
我需要能够合并这两个图以得到下图:
Screen#1 -> Screen#X -+
| |
+------------> Screen#2 -> Screen#3
我可以测试每个节点与其他每个节点是否相等。但我似乎无法设计出一种算法来将这两个图合并为一个。
我在 Internet 上浏览了很多次,但一直找不到关于如何合并图形的站点、讲座或研究论文。我当前的实现(非常有问题)是这样的:
1. Identify all nodes in graph A that also exist in graph B (S#1, S#2, S#3)
2. Copy all of these nodes in the final tree..
3. For each of these common nodes, recursively explore their children..
4. If the child exists in the resultant tree, add an edge between the parent and the child. Otherwise, add the child and an edge between the parent and child and repeat the algorithm on the child itself.
5. Do this till there are no unvisited nodes left.
有人可以指出我在合并这些图表时应该采用什么方法的正确方向吗?请注意,这两个图可能是循环的。
谢谢,阿西姆
最佳答案
因为您还没有发布 Minimal, Complete, and Verifiable example ,这里的答案可能不完全适合您的用例。希望您无论如何都能利用它:
我建议您将图形表示为一组节点数据,其节点由字符串标签标识,同时还有一组节点标签对来表示边。像这样:
// T represents the data type, K represents the union of node labels
class Graph<K extends string, T> {
nodes: Record<K, T>;
edges: Array<{ start: K, end: K }>;
constructor(nodes: Record<K, T>, edges: Array<{ start: K, end: K }>) {
this.nodes = Object.assign({}, nodes);
this.edges = edges.slice();
}
}
// graphOne is a Graph<"a"|"b"|"c", string>
const graphOne = new Graph(
{
a: "NodeA", b: "NodeB", c: "NodeC"
}, [
{ start: "a", end: "b" },
{ start: "b", end: "c" }
]
);
在上面,graphOne
是一个简单的图,具有三个标记为 "a"
、"b"
和 "的节点c"
,从 "a"
到 "b"
以及从 "b"
到 “c”
。在这种情况下,存储在每个节点中的数据只是一个字符串
。在这里,让我们为 Graph
类创建一个简单的控制台日志记录方法:
print() {
console.log(this.nodes)
console.log(this.edges.map(e => e.start + "➡" + e.end));
}
并使用它:
graphOne.print();
// Object { a: "NodeA", b: "NodeB", c: "NodeC" }
// Array [ "a➡b", "b➡c" ]
在这一点上,您可能会反对这种表示并不能自然地表达您如何使用图形,其中每个节点都有数据和一些子节点,并且您可以通过递归遍历子节点来遍历图形(并且可能陷入循环)等。也就是说,您可能会想到这样的事情:
interface TraversableGraph<T> {
nodes: Array<TraversableNode<T>>;
}
interface TraversableNode<T> {
data: T,
children: Array<TraversableNode<T>>;
}
幸运的是,您可以相当轻松地将 Graph
转换为 TraversableGraph
。这是一种方法。让我们向 Graph
添加一个 asTraversableGraph()
方法:
asTraversableGraph(): TraversableGraph<T> {
return {
nodes: (Object.keys(this.nodes) as K[]).map(
k => this.asTraverableNode(k))
};
}
private asTraverableNode(k: K): TraversableNode<T> {
const graph = this;
return {
get data() {
return graph.nodes[k];
},
get children() {
return graph.edges.filter(e => e.start === k).map(
e => graph.asTraverableNode(e.end));
}
}
}
我选择使用 getters这样可遍历图将(可能,也许)反射(reflect)图的变化(例如,您向 Graph
添加一条新边,并且 TraversableGraph
也将具有该新边如果你穿过它,就进入它的边缘)。但您不必那样做。
让我们确保它的行为合理:
graphOne.asTraversableGraph().nodes[0].data; // "NodeA"
graphOne.asTraversableGraph().nodes[0].children[0].data; // "NodeB"
现在我们终于可以合并了。使用 Graph
表示,这不再是一些奇怪的毛茸茸的/循环的/递归的东西。您只需合并节点并合并边缘,然后回家。这是一种可能的方法,通过向 Graph
添加 merge()
方法:
merge<L extends string, U, V>(
otherGraph: Graph<L, U>,
dataMerge: (x: T | undefined, y: U | undefined) => V
): Graph<K | L, V> {
const thisGraph = this as Graph<K | L, T | undefined>;
const thatGraph = otherGraph as Graph<K | L, U | undefined>;
// merge nodes
const nodes = {} as Record<K | L, V>;
(Object.keys(thisGraph.nodes) as (K | L)[]).forEach(
k => nodes[k] = dataMerge(thisGraph.nodes[k], thatGraph.nodes[k]));
(Object.keys(thatGraph.nodes) as (K | L)[]).filter(k => !(k in nodes)).forEach(
k => nodes[k] = dataMerge(thisGraph.nodes[k], thatGraph.nodes[k]));
// merge edges
const edges: Record<string, { start: K | L, end: K | L }> = {};
thisGraph.edges.forEach(e => edges[JSON.stringify(e)] = e);
thatGraph.edges.forEach(e => edges[JSON.stringify(e)] = e);
return new Graph(nodes, Object.keys(edges).map(k => edges[k]));
}
请注意,merge()
需要两个参数:另一个图和一个 dataMerge()
回调,其工作是合并来自第一个图上的节点的数据(如果它存在)与来自第二个图上相同标记节点的数据(如果它存在)并产生你想在合并图中看到的数据。
通过遍历每个图的节点列表并在每个图的相同标记节点上运行 dataMerge()
来合并节点。通过合并两个边列表并确保没有重复项来合并边(我通过在边上使用 JSON.stringify()
作为唯一键来实现)。
让我们通过引入另一个图看看它是否有效:
// graphTwo is a Graph<"b" | "c" | "d", string>
const graphTwo = new Graph(
{
b: "NodeB", c: "Node C", d: "NodeD"
}, [
{ start: "b", end: "d" },
{ start: "b", end: "c" },
{ start: "d", end: "c" }
]
);
graphTwo.print();
// Object { b: "NodeB", c: "Node C", d: "NodeD" }
// Array(3) [ "b➡d", "b➡c", "d➡c" ]
并合并它,注意 dataMerge()
有点复杂,要处理 graphOne
中存储的字符串数据与 中存储的字符串数据之间可能发生的冲突图二
:
// graphMerged is a Graph<"a" | "b" | "c" | "d", string>
const graphMerged = graphOne.merge(graphTwo,
(x, y) => typeof x === 'undefined' ?
(typeof y === 'undefined' ? "??" : y) :
((x === y || typeof y === 'undefined') ? x : x + "/" + y)
);
graphMerged.print();
// Object { a: "NodeA", b: "NodeB", c: "NodeC/Node C", d: "NodeD" }
// Array(4) [ "a➡b", "b➡c", "b➡d", "d➡c" ]
而且有效!尽管 graphOne
和 graphTwo
具有相同的边,但合并后的图只有一条 b➡c
边。标记为 c
的节点名称在 graphOne
("NodeC"
) 和 graphTwo
( "Node C"
),所以 dataMerge()
加入了它们 ("NodeC/Node C"
)。
这是一个 Playground link以上所有代码。
希望对您有所帮助。祝你好运!
关于typescript - 将两个有向(可能是循环)图合并为一个,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55229917/
我已经写了并且 npm 发布了这个:https://github.com/justin-calleja/pkg-dependents 现在我正在用 Typescript 编写这个包:https://g
我有一个函数,我想在 TypeScript 中模拟它以进行测试。在我的测试中,我只关心 json和 status .但是,当使用 Jest 的 jest.spyOn 时我的模拟函数的类型设置为返回 h
我正在使用一个库 (Axios),它的包中包含 Typescript 声明。 我想声明一个将 AxiosResponse(在库的 .d.ts 文件中声明)作为参数的函数。我有以下内容: functio
我是 Typescript 的新手。我想使用 将一个 Typescript 文件加载到另一个 Typescript 文件中标签。 我做了一些事情,但它不起作用!请帮助我。 first.ts: imp
为什么我会收到下面屏幕截图中显示的错误? Atom 说我的 tsconfig.json“项目文件包含无效选项”用于 allowJs、buildOnSave 和 compileOnSave。 但是应该允
所以我正在创建一个 TypeScript 库,我可以轻松地将所有生成的 JS 文件编译成一个文件。有没有办法将所有 .ts 和 .d.ts 编译成一个 .ts 文件? 除了支持 JS 的版本(较少的智
Microsoft Research 提供了一种名为Safer TypeScript 的新 TypeScript 编译器变体: http://research.microsoft.com/en-us/
我需要这个来在单个文件中分发 TypeScript 中的库。有没有办法将多个 typescript 文件合并到(一个js文件+一个 typescript 定义)文件中? 最佳答案 要创建一个库,您可以
用例:我想知道一个函数在 typescript 中执行需要多少时间。我想为此目的使用装饰器。我希望装饰器应该返回时间以便(我可以进一步使用它),而不仅仅是打印它。 例如: export functio
我想检查一个类型是否可以为 null,以及它是否具有值的条件类型。 我尝试实现 type IsNullable = T extends null ? true : false; 但是好像不行 type
我的问题是基于这个 question and answer 假设我们有下一个代码: const myFn = (p: { a: (n: number) => T, b: (o: T) => v
我知道双重否定前缀,我知道 TypeScript 的单后缀(非空断言)。 但是这个双后缀感叹号是什么? /.*验证码为(\d{6}).*/.exec(email.body!!)!![1] 取自here
我正在使用以下文件结构在 Webstorm 中开发一个项目 | src | ... | many files | types | SomeInterface |
在 TypeScript 类中,可以为属性声明类型,例如: class className { property: string; }; 如何在对象字面量中声明属性的类型? 我试过下面的代码,但它
我正在寻找一种在不丢失推断类型信息的情况下将 TypeScript 中的文字值限制为特定类型的好方法。 让我们考虑一个类型Named,它保证有一个名字。 type Named = { name:
在 TypeScript 中,我想创建一个联合类型来表示属于一个或多个不同类型的值,类似于 oneOf在 OpenAPI或 JSON Schema .根据a previous answer on a
type Func = (foo:string) => void // function expression const myFunctionExpression:Func = function(f
假设我有一个联合类型,我正在使用类似 reducer 的 API 调用模式,看起来像这样: type Action = { request: { action: "create
我在 typescript 中有以下去抖功能: export function debounce( callback: (...args: any[]) => void, wait: numb
在 Vue3 的 defineComponent 函数中,第一个泛型参数是 Props,所以我在这里使用 Typescript 接口(interface)提供我的 props 类型。喜欢: expor
我是一名优秀的程序员,十分优秀!