Skip to content

Instantly share code, notes, and snippets.

@msmolcic
Created February 5, 2019 03:15
Show Gist options
  • Save msmolcic/fa44f2e6daa99b29080c9a9d31e92a87 to your computer and use it in GitHub Desktop.
Save msmolcic/fa44f2e6daa99b29080c9a9d31e92a87 to your computer and use it in GitHub Desktop.
Nested arrays problem
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace NestedArrays
{
public static class Extensions
{
/// <summary>
/// Flattens the structure of the input of the indefinite depth into
/// the array of integers found while traversing through the input.
/// </summary>
/// <remarks>
/// All non-integer values will be skipped while traversing.
/// Returns an empty array when the input is null.
/// </remarks>
/// <param name="input">Input values containing anything.</param>
/// <returns>Flattened array of integers.</returns>
public static int[] Flatten(this IEnumerable<object> input)
{
return Traverse(input).ToArray();
}
/// <summary>
/// Traverses through the input and returns all integers found.
/// </summary>
/// <remarks>
/// Using Stack data structure internally instead of recursive
/// calls in order to avoid StackOverflowException.
/// </remarks>
/// <param name="input">Input values containing anything.</param>
/// <returns>All integers found in the input.</returns>
private static IEnumerable<int> Traverse(IEnumerable<object> input)
{
if (input == null)
yield break;
// Stack will contain all the items that are yet to be processed.
var stack = new Stack<IEnumerable<object>>();
stack.Push(input);
while (stack.Any())
{
var next = stack.Pop().ToArray();
for (int index = 0; index < next.Length; ++index)
{
var item = next[index];
if (item is IEnumerable items)
{
// If there are remaining items to process after the current item,
// add them to the stack before new items. That way we're keeping
// items in the same order they were in the input.
if (index + 1 < next.Length)
stack.Push(next.Skip(index + 1));
stack.Push(items.Cast<object>());
break;
}
if (item is int number)
yield return number;
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace NestedArrays
{
public class ExtensionsTest
{
[Fact]
public void Flatten_NestedArraySetToNull_ReturnsEmptyArray()
{
// Arrange
List<object> nestedArray = null;
// Act
int[] result = nestedArray.Flatten();
// Assert
Assert.Empty(result);
}
[Fact]
public void Flatten_NestedArrayHugeDepth_DoesNotThrowStackOverflowException()
{
// Arrange
const int DepthLevel = 100000;
var nestedArray = new List<object>();
var childArray = new List<object>();
nestedArray.Add(childArray);
for (int index = 0; index < DepthLevel; index++)
{
var newList = new List<object>() { index + 1 };
childArray.Add(newList);
childArray = newList;
}
int[] expectedResult = Enumerable.Range(1, DepthLevel).ToArray();
// Act
int[] result = nestedArray.Flatten();
// Assert
Assert.Equal(expectedResult, result);
}
[Fact]
public void Flatten_NestedArrayWithMixedElements_ReturnsOnlyIntegersInTheCorrectOrder()
{
// Arrange
var nestedArray = new List<object>()
{
null, 1,
new DateTime(2000, 10, 10),
2, false, 'a',
new List<object>()
{
3, 4,
new List<int>()
{
5, 6
},
new List<decimal>()
{
3.14m,
6.22m,
4m
},
new Dictionary<int, int>()
{
{ 1, 1 },
{ 2, 2 }
},
new List<object>()
{
null, "Irrelevant string", 7, 8,
new int[] { 9 }
},
10, 11
},
12, 13, null, true, 14, 15,
new Func<int, int>((num) => num + 2)
};
int[] expectedResult = Enumerable.Range(1, 15).ToArray();
// Act
int[] result = nestedArray.Flatten();
// Assert
Assert.Equal(expectedResult, result);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment