Skip to content

Instantly share code, notes, and snippets.

@ncalm
Created January 18, 2023 14:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ncalm/f1b9beb84f3268cbc6ce707f06bc4f44 to your computer and use it in GitHub Desktop.
Save ncalm/f1b9beb84f3268cbc6ce707f06bc4f44 to your computer and use it in GitHub Desktop.
This gist supports breadth-first search of graph data using Excel Lambda functions
/*
Note, this function is used by the functions in the graph namespace
*/
IFOMITTED = LAMBDA(arg, then, [else],
LET(_else, IF(ISOMITTED(else), arg, else), IF(ISOMITTED(arg), then, _else))
);
/*
Note: import these functions to a new namespace in a workbook. The namespace should be called "graph"
*/
author = "Owen Price";
/*
An array of graph data (edit as necessary)
First column is the node as a single value
Second column is the neighbors of that node, as a ", "-separated string
e.g. this row tells us that node A has neighbors B and C
{"A", "B, C"}
*/
data = Sheet1!A2:B4;
/*
Returns the neighbors of a node from an array of graph data
*/
get_neighbors_fn = LAMBDA(data,
LAMBDA(node,
LET(
_neighbors, INDEX(FILTER(TAKE(data, , -1), TAKE(data, , 1) = node), 1, 1),
TEXTSPLIT(_neighbors, , ", ")
)
)
);
/*
Returns a function that returns the neighbors of a node as a vertical array from the array
of data defined in the named range graph.data
*/
get_neighbors = graph.get_neighbors_fn(graph.data);
/*
Returns an array detailing the nodes visited during a breadth-first search of graph data
*/
breadth_first_search = LAMBDA(queue, end, [visited], [iteration],
LET(
//dequeue
_iteration, IFOMITTED(iteration, 1, iteration + 1),
_node, INDEX(TAKE(queue, 1), 1, 1),
_visited, IFOMITTED(visited, HSTACK(_node, "None")),
_is_undiscovered, LAMBDA(node, ISERROR(XMATCH(node, TAKE(_visited, , 1)))),
_neighbors, graph.get_neighbors(_node),
_end_is_neighbor, NOT(ISERROR(XMATCH(end, _neighbors))),
_newqueue,
IF(
OR(ISERROR(DROP(queue, 1))),
_neighbors,
REDUCE(
DROP(queue, 1),
_neighbors,
LAMBDA(a, b, IF(_is_undiscovered(b), VSTACK(a, b), a))
)
),
_newvisited, REDUCE(
_visited,
_neighbors,
LAMBDA(a, b, IF(_is_undiscovered(b), VSTACK(a, HSTACK(b, _node)), a))
),
_result, IF(
OR(_node = end, _end_is_neighbor),
VSTACK(_visited, HSTACK(end, _node)),
graph.breadth_first_search(_newqueue, end, _newvisited, _iteration)
),
_result
)
);
/*
Returns an array tracing the path between start and end using
a search algorithm function passed to search_function (e.g. breadth_first_search).
The search_function must return a "visited array" similar in structure to that
returned by breadth_first_search.
*/
get_path = LAMBDA(start, next, search_function, [visited], [path],
LET(
_visited, IFOMITTED(visited, search_function(start, next)),
_step, FILTER(_visited, TAKE(_visited, , 1) = next),
_path, IFOMITTED(path, _step, VSTACK(_step, path)),
_next, INDEX(_step, 1, 2),
_result, IF(
_next = start,
CHOOSECOLS(_path, {2, 1}),
graph.get_path(start, _next, search_function, _visited, _path)
),
_result
)
);
/*
Returns a single-value representing a path returned by get_path
*/
get_path_text = LAMBDA(start, end, search_function,
LET(
_path, graph.get_path(start, end, search_function),
TEXTJOIN(" " & UNICHAR(10132) & " ", TRUE, UNIQUE(TOCOL(_path)))
)
);
/*
Returns an integer represenging the count of edges traversed by a search
*/
get_distance =
LAMBDA(from, to, search_function,
LET(
_path, graph.get_path(from, to, search_function),
ROWS(_path)
)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment