Graph or Permutations With Constraints?

This is a variation (generalization) of an interview question asked by one of the engineers in our team (btw, you can check out one of her amazing talks, it is right here: https://forwardcourses.com/lectures/239). The problem is the following:

You'll be given a set of characters, let's name them C1, C2, ..., Cn, (for simplicity sake, there is no repetition) and you need to generate all permutations of these characters without violating any constraint. You're also given a set of constraints. Each constraint will be a pair of characters (Ci, Cj), and the semantics is that for Cj to show up in the resulting string, it can only be there when Ci shows up before it. Noticed that Ci and Cj do not have to necessarily be adjacent to each other
Here is an example. Suppose that the set of characters is "ABC" and there is just one constraint: (A,C). Which means that for C to show up in the resulting string, A must precede it. There are only 3 possibilities then:

ABC
ACB
BAC

But the input characters and the constraints can be very complex. For example, suppose that the characters become "ABCDEFGHI" and the set of constraints is: (A,D) (D,B) (D,G) (H,G) (I,G) (C,A) (C,B) (H,A) (H,D) (H,E) (H,F) (F,B) (B,I) (H,E) (E,A). In this case there are only few solutions too:

CHEADFBIG
CHEAFDBIG
CHEFADBIG
CHFEADBIG
HCEADFBIG
HCEAFDBIG
HCEFADBIG
HCFEADBIG
HECADFBIG
HECAFDBIG
HECFADBIG
HEFCADBIG
HFCEADBIG
HFECADBIG

To solve this problem in a general manner, there are two approaches:
a) Graph: using this one you do an analysis of the constraints, build a graph out of it (bonus for auto-detection of loops in which case there won't be any solution), traverse the graph and generate the possible combinations.
b) Permutation with Constraints: in this approach the code will attempt to generate the permutations of the input characters, but it will only call recursively whenever all the constraints are met.

I decided to go with option (b), I think in the end the time complexity (worst case is exponential in the number of input characters) will be the same, but the code becomes simpler with (b). Code's below, cheers, Marcelo.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;

namespace CombinationWithConstraints
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length < 2)
            {
                Console.WriteLine("Usage: CombinationWithConstraints.exe [alphabet] [constraint 1] [constraint 2] ... [constraint N]");
                Console.WriteLine("Constraint examples: A,B  that means that A must come before B");
                return;
            }

            string alphabet = args[0];

            char[] before = new char[args.Length - 1];
            char[] after = new char[args.Length - 1];

            for (int i = 1; i < args.Length; i++)
            {
                string[] constraint = args[i].Split(',');
                before[i - 1] = constraint[0][0];
                after[i - 1] = constraint[1][0];
            }

            ArrangeWithConstraints(alphabet, "", 0, new Hashtable(), before, after);
        }

        static void ArrangeWithConstraints(string alphabet,
                                           string current,
                                           int currentIndex,
                                           Hashtable lettersUsed,
                                           char[] before,
                                           char[] after)
        {
            if (currentIndex == alphabet.Length)
            {
                Console.WriteLine(current);
                return;
            }

            foreach (char c in alphabet)
            {
                if (!lettersUsed.ContainsKey(c))
                {
                    bool proceed = true;
                    for (int i = 0; i < after.Length; i++)
                    {
                        if (c == after[i] && !lettersUsed.ContainsKey(before[i]))
                        {
                            proceed = false;
                            break;
                        }
                    }

                    if (proceed)
                    {
                        lettersUsed.Add(c, true);
                        ArrangeWithConstraints(alphabet, current + c.ToString(), currentIndex + 1, lettersUsed, before, after);
                        lettersUsed.Remove(c);
                    }
                }
            }
        }
    }
} 

Comments

Popular posts from this blog

Advent of Code - Day 6, 2024: BFS and FSM

Golang vs. C#: performance characteristics (simple case study)

Advent of Code - Day 7, 2024: Backtracking and Eval