How to Split an Array Into Odd Pairs (An Interview Exercise)

Here’s an exercise that I may or may not have found… or been asked to solve.

Off the top of my head, the exercise might or might not have looked like this,

Let’s start with an array of integers of even size. Return if the array can be divided into N/2 pairs where each pair adds up to an odd number and those N/2 pairs contain every element of the given array.

For example, [1, 2, 3, 4] passes those two conditions. Here are its pairs: (1, 2) and (3, 4). Their sum is an odd number.

But [1, 3, 5, 7] doesn’t pass our two conditions. Each pair adds up to an even number.

Cheating with StackOverflow and Copilot

Let’s create a producer-like method to generate all pairs, and then filter them out based on our two conditions.

I’d like to write something like this,

bool HasFunnyPairs(int[] array)
    => array.Pais()
        .Where(pair => IsOdd(Sum(pair)))
        .Combinations(array.Length / 2)
        .Any(combination => IsEachElementInExactlyOnePair(array, combination));

First, generate all pairs with an odd sum, then form N/2-size combinations, filtering out those that don’t include every element of the array.

Since, we’re already in the future and I’m not in front of a whiteboard and an interviewer, I’m cheating with StackOverflow and Copilot.

Generating k-Combinations

Here’s a StackOverflow answer with a recursive method in JavaScript to choose k-combinations of an array,

function choose(arr, k, prefix=[]) {
    if (k == 0) return [prefix];
    return arr.flatMap((v, i) =>
        choose(arr.slice(i+1), k-1, [...prefix, v])
    );
}

I asked Copilot to translate it to C#. After some tweaking, we have this,

static IEnumerable<IEnumerable<T>> Combinations<T>(IEnumerable<T> source, int size)
{
	return CombinationsInternal(source, size, null);

	static IEnumerable<IEnumerable<T>> CombinationsInternal(IEnumerable<T> source, int size, List<T>? prefix = null)
	{
		prefix ??= [];

		if (size == 0)
		{
			yield return new List<T>(prefix);
			yield break;
		}

		var length = source.Count();
		foreach ((T element, int position) in source.Select((element, position) => (element, position)))
		{
			List<T> newPrefix = [.. prefix, element];
			foreach (var combination in CombinationsInternal([.. source.Skip(position + 1)], size - 1, newPrefix))
			{
				yield return combination;
			}
		}
	}
}

With that method, we can write Pairs() to generate a list of tuples, representing each pair,

static IEnumerable<(int First, int Second)> Pairs(this int[] array)
    => array.Combinations(2)
            .Select(pair => (First: pair.First(), Second: pair.Last()));

Finding if each element is present in only one pair

The methods IsOdd() and Sum() are easy. The one left is IsEachElementInExactlyOnePair(). Copilot to the rescue again,

static bool IsEachElementInExactlyOnePair(int[] elements, IEnumerable<(int First, int Second)> pairs)
{
    var elementCount = new Dictionary<int, int>();

    // Count occurrences of elements in pairs
    foreach (var (First, Second) in pairs)
    {
        elementCount[First] = elementCount.GetValueOrDefault(First) + 1;
        elementCount[Second] = elementCount.GetValueOrDefault(Second) + 1;
    }

    // Check if each element appears exactly once
    return elements.All(e => elementCount.GetValueOrDefault(e) == 1)
           && elements.All(e => elementCount.ContainsKey(e));
}

Now, after some gluing and turning some of those methods into extension methods, here’s the final solution,

On a Helpers.cs file,

namespace HereIGo;

public static class Helpers
{
    public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> array, int size)
    {
        return CombinationsInternal(array, size, null);

        static IEnumerable<IEnumerable<T>> CombinationsInternal(IEnumerable<T> array, int size, List<T>? prefix = null)
        {
            prefix ??= [];

            if (size == 0)
            {
                yield return new List<T>(prefix);
                yield break;
            }

            var length = array.Count();
            foreach ((T element, int position) in array.Select((element, position) => (element, position)))
            {
                List<T> newPrefix = [.. prefix, element];
                foreach (var combination in CombinationsInternal([.. array.Skip(position + 1)], size - 1, newPrefix))
                {
                    yield return combination;
                }
            }
        }
    }

    public static IEnumerable<(int First, int Second)> Pairs(this int[] array)
        => array.Combinations(2)
            .Select(pair => (First: pair.First(), Second: pair.Last()));

    public static int Sum(this (int First, int Second) pair) => pair.First + pair.Second;

    public static bool IsOdd(this int n) => n % 2 != 0;

    public static bool IsEachElementInExactlyOnePair(this int[] elements, IEnumerable<(int First, int Second)> pairs)
    {
        var elementCount = new Dictionary<int, int>();

        // Count occurrences of elements in pairs
        foreach (var (First, Second) in pairs)
        {
            elementCount[First] = elementCount.GetValueOrDefault(First) + 1;
            elementCount[Second] = elementCount.GetValueOrDefault(Second) + 1;
        }

        // Check if each element appears exactly once
        return elements.All(e => elementCount.GetValueOrDefault(e) == 1)
               && elements.All(e => elementCount.ContainsKey(e));
    }
}

On a Main.cs file,

using HereIGo;

static bool OddPairs(int[] array)
    => array.Pairs()
        .Where(p => p.Sum().IsOdd())
        .Combinations(array.Length / 2)
        .Any(combination => array.AllExactlyInOnePair(combination));

OddPairs([1, 2, 3, 4]); // true
OddPairs([1, 3, 5, 7]); // false
OddPairs([1, -1]); // false
OddPairs([2, -1]); // true

AI has changed the the tech interview landscape for good. The other day, I asked AI to solve another interview exercise. And I was surprised. And more recently, I’ve used Copilot to help me with these 4 boring coding tasks. Yes, AI isn’t going anywhere. So we as coders have to adapt, like we have always done.