Recursion in C#
It's not often that we use recursion in real-world programming. The last time I wrote recursion was during an interview 6+ years ago. Recursive functions are not performant and thus are avoided. A few days ago, however, I had a task to navigate down a tree given an ancestor node, so ... recursion it is!
The Scenario
Given a GUID Id of an ancestor Account object (think like a Salesforce account or an Organization), find all the Accounts under that ancestor to a maximum depth of 5. Something like this:
Given Account A's Id, get all the Accounts under it. First, here's the function which executes the recursion:
public async Task<IEnumerable<Account>> GetAccountsWithChildren(Guid id)
{
// Get all accounts first
var allAccounts = await GetAccounts();
// Bail early if no results
if (allAccounts == null || allAccounts.Count == 0) return new List<Account>();
// Get the root account
var rootAccount = allAccounts.FirstOrDefault(x => x.Id == id);
// Bail if root account not found
if (rootAccount == null) return new List<Account>();
// Define the max depth we want to run our recursion
var maxDepth = 5;
// Set up the list we're going to populate with results
List<Account> childAccounts = new List<Account>();
// Add the root account at the start
childAccounts.Add(rootAccount);
// Run the recursion!
ExtractAccountsRecursively(maxDepth, id, allAccounts, childAccounts, 0);
// Finally, return the results
return childAccounts;
}
Nothing exceptional in the above method. The fun stuff is in this next bit:
private void ExtractAccountsRecursively(
int maxDepth,
Guid? parentId,
IEnumerable<Account> allAccounts,
List<Account> childAccounts,
int currDepth = 0)
{
// Bail if depth reached
if (currDepth >= maxDepth) return;
// Bail if parentId is null
if (!parentId.HasValue) return;
// Bail if there are no more children
var currentChildren = allAccounts.Where(a => a.Parent != null && a.Parent.Id == parentId);
// Increase the current depth to pass down to the recursive function
currDepth++;
// Iterate over the children found with the parentId above
foreach (var account in currentChildren)
{
// Prevent duplicates
if (childAccounts.Contains(account)) continue;
// Add the account to the child accounts list
childAccounts.Add(account);
// Run this function again for the account we're iterating through
ExtractAccountsRecursively(maxDepth, account.Id, allAccounts, childOnlyAccounts, currDepth);
}
}
I commented the code above so it should be easy to follow. The incoming list, allAccounts
, is a flat list of Account objects with ParentId
property referring to the Id of another Account object.
I suspect there's a better way to write this using yield
but that syntax confuses me. If anyone has a good example for the above scenario using yield
I'd be interested to check it out.
Cheers!