-
Notifications
You must be signed in to change notification settings - Fork 235
Expand file tree
/
Copy pathall_paths.c
More file actions
193 lines (167 loc) · 6.63 KB
/
Copy pathall_paths.c
File metadata and controls
193 lines (167 loc) · 6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
/*
* Copyright 2018-2021 Redis Labs Ltd. and Contributors
*
* This file is available under the Redis Labs Source Available License Agreement
*/
#include "all_paths.h"
#include "RG.h"
#include "../util/arr.h"
#include "../util/rmalloc.h"
// Make sure context levels array have atleast 'level' entries,
// Append given 'node' to given 'level' array.
static void _AllPathsCtx_AddConnectionToLevel(AllPathsCtx *ctx, uint level, Node *node,
Edge *edge) {
while(array_len(ctx->levels) <= level) {
array_append(ctx->levels, array_new(LevelConnection, 1));
}
LevelConnection connection;
connection.node = *node;
if(edge) connection.edge = *edge;
array_append(ctx->levels[level], connection);
}
// Check to see if context levels array has entries at position 'level'.
static bool _AllPathsCtx_LevelNotEmpty(const AllPathsCtx *ctx, uint level) {
return (level < array_len(ctx->levels) && array_len(ctx->levels[level]) > 0);
}
// Traverse from the frontier node in the specified direction and add all encountered nodes and edges.
static void _addNeighbors(AllPathsCtx *ctx, LevelConnection *frontier, uint32_t depth,
GRAPH_EDGE_DIR dir) {
EntityID frontierId = INVALID_ENTITY_ID;
if(depth > 1) frontierId = ENTITY_GET_ID(&frontier->edge);
// Get frontier neighbors.
for(int i = 0; i < ctx->relationCount; i++) {
Graph_GetNodeEdges(ctx->g, &frontier->node, dir, ctx->relationIDs[i], &ctx->neighbors);
}
// Add unvisited neighbors to next level.
uint32_t neighborsCount = array_len(ctx->neighbors);
//--------------------------------------------------------------------------
// apply filter to edge
//--------------------------------------------------------------------------
if(ctx->ft) {
for(uint32_t i = 0; i < neighborsCount; i++) {
Edge e = ctx->neighbors[i];
// update the record with the current edge
Record_AddEdge(ctx->r, ctx->edge_idx, e);
// drop edge if it doesn't passes filter
if(FilterTree_applyFilters(ctx->ft, ctx->r) != FILTER_PASS) {
array_del_fast(ctx->neighbors, i);
i--;
neighborsCount--;
}
}
}
for(uint32_t i = 0; i < neighborsCount; i++) {
// Don't follow the frontier edge again.
if(frontierId == ENTITY_GET_ID(ctx->neighbors + i)) continue;
// Set the neighbor by following the edge in the correct directoin.
Node neighbor = GE_NEW_NODE();
switch(dir) {
case GRAPH_EDGE_DIR_OUTGOING:
Graph_GetNode(ctx->g, Edge_GetDestNodeID(ctx->neighbors + i), &neighbor);
break;
case GRAPH_EDGE_DIR_INCOMING:
Graph_GetNode(ctx->g, Edge_GetSrcNodeID(ctx->neighbors + i), &neighbor);
break;
default:
ASSERT(false && "encountered unexpected traversal direction in AllPaths");
break;
}
// Add the node and edge to the frontier.
_AllPathsCtx_AddConnectionToLevel(ctx, depth, &neighbor, (ctx->neighbors + i));
}
array_clear(ctx->neighbors);
}
AllPathsCtx *AllPathsCtx_New(Node *src, Node *dst, Graph *g, int *relationIDs, int relationCount,
GRAPH_EDGE_DIR dir, uint minLen, uint maxLen,
Record r, FT_FilterNode *ft, uint edge_idx) {
ASSERT(src != NULL);
AllPathsCtx *ctx = rm_malloc(sizeof(AllPathsCtx));
ctx->g = g;
ctx->r = r;
ctx->ft = ft;
ctx->dir = dir;
ctx->edge_idx = edge_idx;
// Cypher variable path "[:*min..max]"" specifies edge count
// While the path constructed here contains only nodes.
// As such a path which require min..max edges
// should contain min+1..max+1 nodes.
ctx->minLen = minLen + 1;
ctx->maxLen = maxLen + 1;
ctx->relationIDs = relationIDs;
ctx->relationCount = relationCount;
ctx->levels = array_new(LevelConnection *, 1);
ctx->path = Path_New(1);
ctx->neighbors = array_new(Edge, 32);
ctx->dst = dst;
_AllPathsCtx_AddConnectionToLevel(ctx, 0, src, NULL);
// in case we have filter tree validate that we can access the filtered edge
ASSERT(!ctx->ft || ctx->edge_idx < Record_length(ctx->r));
return ctx;
}
Path *AllPathsCtx_NextPath(AllPathsCtx *ctx) {
if(!ctx) return NULL;
// As long as path is not empty OR there are neighbors to traverse.
while(Path_NodeCount(ctx->path) || _AllPathsCtx_LevelNotEmpty(ctx, 0)) {
uint32_t depth = Path_NodeCount(ctx->path);
// Can we advance?
if(_AllPathsCtx_LevelNotEmpty(ctx, depth)) {
// Get a new frontier.
LevelConnection frontierConnection = array_pop(ctx->levels[depth]);
Node frontierNode = frontierConnection.node;
/* See if frontier is already on path,
* it is OK for a path to contain an entity twice,
* such as in the case of a cycle, but in such case we
* won't expand frontier.
* i.e. closing a cycle and continuing traversal. */
bool frontierAlreadyOnPath = Path_ContainsNode(ctx->path, &frontierNode);
// Add frontier to path.
Path_AppendNode(ctx->path, frontierNode);
/* If depth is 0 this is the source node, there is no leading edge to it.
* For depth > 0 for each frontier node, there is a leading edge. */
if(depth > 0) Path_AppendEdge(ctx->path, frontierConnection.edge);
// Update path depth.
depth++;
/* Introduce neighbors only if path depth < maximum path length.
* and frontier wasn't already expanded. */
if(depth < ctx->maxLen && !frontierAlreadyOnPath) {
GRAPH_EDGE_DIR dir = ctx->dir;
if(dir == GRAPH_EDGE_DIR_BOTH) {
/* If we're performing a bidirectional traversal, first add all incoming
* edges, then switch to outgoing edges for the default call. */
_addNeighbors(ctx, &frontierConnection, depth, GRAPH_EDGE_DIR_INCOMING);
dir = GRAPH_EDGE_DIR_OUTGOING;
}
_addNeighbors(ctx, &frontierConnection, depth, dir);
}
// See if we can return path.
/* TODO Note that further calls to this function will continue to operate on
* this path, so it is essential that the caller does not modify it (or creates
* a copy beforehand). If future features like an algorithm API use this routine,
* they should either be responsible for memory safety or a memory-safe boolean/routine
* should be offered. */
if(depth >= ctx->minLen && depth <= ctx->maxLen) {
if(ctx->dst != NULL) {
Node dst = Path_Head(ctx->path);
if(ENTITY_GET_ID(ctx->dst) != ENTITY_GET_ID(&dst)) continue;
}
return ctx->path;
}
} else {
// No way to advance, backtrack.
Path_PopNode(ctx->path);
if(Path_EdgeCount(ctx->path)) Path_PopEdge(ctx->path);
}
}
// Couldn't find a path.
return NULL;
}
void AllPathsCtx_Free(AllPathsCtx *ctx) {
if(!ctx) return;
uint32_t levelsCount = array_len(ctx->levels);
for(int i = 0; i < levelsCount; i++) array_free(ctx->levels[i]);
array_free(ctx->levels);
Path_Free(ctx->path);
array_free(ctx->neighbors);
rm_free(ctx);
ctx = NULL;
}