tags:

views:

92

answers:

3

Okay - I'm not even sure that the term is right - and I'm sure there is bound to be a term for this - but I'll do my best to explain. This is not quite a cross product here, and the order of the results are absolutely crucial.

Given:

IEnumerable<IEnumerable<string>> sets = 
      new[] { 
              /* a */ new[] { "a", "b", "c" },
              /* b */ new[] { "1", "2", "3" },
              /* c */ new[] { "x", "y", "z" }
            };

Where each inner enumerable represents an instruction to produce a set of concatenations as follows (the order here is important):

set a* = new string[] { "abc", "ab", "a" };
set b* = new string[] { "123", "12", "1" };
set c* = new string[] { "xyz", "xy", "x" };

I want to produce set ordered concatenations as follows:

set final = new string { a*[0] + b*[0] + c*[0], /* abc123xyz */
                         a*[0] + b*[0] + c*[1], /* abc123xy  */
                         a*[0] + b*[0] + c*[2], /* abc123x   */
                         a*[0] + b*[0],         /* abc123    */
                         a*[0] + b*[1] + c*[0], /* abc12xyz  */
                         a*[0] + b*[1] + c*[1], /* abc12xy   */
                         a*[0] + b*[1] + c*[2], /* abc12x    */
                         a*[0] + b*[1],         /* abc12     */
                         a*[0] + b*[2] + c*[0], /* abc1xyz   */
                         a*[0] + b*[2] + c*[1], /* abc1xy    */
                         a*[0] + b*[2] + c*[2], /* abc1x     */
                         a*[0] + b*[2],         /* abc1      */
                         a*[0],                 /* abc       */
                         a*[1] + b*[0] + c*[0], /* ab123xyz  */

                         /* and so on for a*[1] */
                         /* ... */

                         a*[2] + b*[0] + c*[0], /* a123xyz   */

                         /* and so on for a*[2] */
                         /* ... */

                         /* now lop off a[*] and start with b + c */

                         b*[0] + c*[0],         /* 123xyz    */

                         /* rest of the combinations of b + c
                            with b on its own as well */

                         /* then finally */
                         c[0],
                         c[1],
                         c[2]};

So clearly, there are going to be a lot of combinations!

I can see similarities with Numeric bases (since the order is important as well), and I'm sure there are permutations/combinations lurking in here too.

The question is - how to write an algorithm like this that'll cope with any number of sets of strings? Linq, non-Linq; I'm not fussed.

Why am I doing this?

Indeed, why!?

In Asp.Net MVC - I want to have partial views that can be redefined for a given combination of back-end/front-end culture and language. The most basic of these would be, for a given base view View, we could have View-en-GB, View-en, View-GB, and View, in that order of precedence (recognising of course that the language/culture codes could be the same, so some combinations might be the same - a Distinct() will solve that).

But I also have other views that, in themselves, have other possible combinations before culture is even taken into account (too long to go into - but the fact is, this algo will enable a whole bunch of really cool that I want to offer my developers!).

I want to produce a search list of all the acceptable view names, iterate through the whole lot until the most specific match is found (governed by the order that this algo will produce these concatenations in) then serve up the resolved Partial View.

The result of the search can later be cached to avoid the expense of running the algorithm all the time.

I already have a really basic version of this working that just has one enumerable of strings. But this is a whole different kettle of seafood!

Any help greatly appreciated.

+2  A: 

This should produce what you want:

using System;
using System.Linq;
using System.Collections.Generic;

namespace SO3014119
{
    class Program
    {
        private static IEnumerable<string> GetStringCombinations(
            string prefix, 
            IEnumerable<string>[] collections, int startWithIndex)
        {
            foreach (var element in collections[startWithIndex])
            {
                if (startWithIndex < collections.Length - 1)
                {
                    foreach (var restCombination in
                        GetStringCombinations(prefix + element, collections,
                            startWithIndex + 1))
                    {
                        yield return restCombination;
                    }
                }

                yield return prefix + element;
            }
        }

        public static IEnumerable<string> GetStringCombinations(
            params IEnumerable<string>[] collections)
        {
            while (collections.Length > 0)
            {
                foreach (var comb in GetStringCombinations("", collections, 0))
                    yield return comb;

                // "lop off" head and iterate
                collections = collections.Skip(1).ToArray();
            }
        }

        static void Main(string[] args)
        {
            var a = new string[] { "a1", "a2", "a3" };
            var b = new string[] { "b1", "b2", "b3" };
            var c = new string[] { "c1", "c2", "c3" };

            foreach (string combination in GetStringCombinations(a, b, c))
            {
                Console.Out.WriteLine(combination);
            }
        }
    }
}

This produces (notice I changed the entries in the input collections to make it easier to see how they were combined):

a1b1c1
a1b1c2
a1b1c3
a1b1
a1b2c1
a1b2c2
a1b2c3
a1b2
a1b3c1
a1b3c2
a1b3c3
a1b3
a1
a2b1c1
a2b1c2
a2b1c3
a2b1
a2b2c1
a2b2c2
a2b2c3
a2b2
a2b3c1
a2b3c2
a2b3c3
a2b3
a2
a3b1c1
a3b1c2
a3b1c3
a3b1
a3b2c1
a3b2c2
a3b2c3
a3b2
a3b3c1
a3b3c2
a3b3c3
a3b3
a3
b1c1
b1c2
b1c3
b1
b2c1
b2c2
b2c3
b2
b3c1
b3c2
b3c3
b3
c1
c2
c3
Lasse V. Karlsen
Thanks for your answer. Interesting - will copy/paste and test; however it's missing some; because it should start "a1a2a3b1b2b3c1c2c3", with all the c's dropping off, then 'b3' drops off and we apply all the C's again, ad nauseam (literally!)
Andras Zoltan
I'm sorry, but I can only answer the question that was asked, not the question you should've asked instead. You specifically listed what you wanted the output to be, I produced exactly that output. If you want a different output, then you need to change the question.
Lasse V. Karlsen
@Lasse V. Karlsen: I've just run the code on my original input set "abc", "123", "xyz". In the question I clearly state that the first entry should be "abc123xyz"; this code produces "a1x". It's possible that my example confused as I used a*[0], b*[0], c*[0] - previously defined as the arrays of concatenations produced by the original a, b and c.Once again, though, I thank you for the effort you have gone to.
Andras Zoltan
@Lasse V. Karlsen: Ah - the only thing missing from this code is the transformation of a,b,c;1,2,3;x,y,z into abc,ab,c;123,12,1;xyz,xy,x - if I feed these into your algo then it does the job - all I have to do is to first produce the a* b* and c* arrays. Very smart. Thank you!
Andras Zoltan
+1  A: 

The solution seems to be simple (algorithmically)

Add an extry empty string at the end of each array a*,b*,c*

string[] a* = { "abc","ab","a","" };
string[] b* = { "123","12","1","" };
string[] c* = { "xyz","xy","x","" };

List<string> final = new List<string>();

Now do a newsted for over the three arrays

foreach(string aMember in a*)
foreach(string bMember in b*)
foreach(string cMember in c*)
final.add(aMember+bMember+cMember);

The extra empty string at the end of a*,b* and c* will generate the special strings like a[0] ( = a[0]+b[3] + c[3]) in the required order.

EDIT : This code will produce some extra strings. See comment below.

apoorv020
It also produces a[0]+b[3]+c[0], ie. "abcxyz", no b* value part of the results.
Lasse V. Karlsen
Adding the empty string - yes I realised that that might form part of the solution to make it simpler; however while this algorithm might work for exactly three sets of strings, it can't be applied to an unknown number of sets of strings.
Andras Zoltan
@Lasse : Thanks for pointing out that mistake. Stupid of me to miss it.
apoorv020
+2  A: 

This is my try:

void Main()
{
    IEnumerable<IEnumerable<string>> sets = 
          new[] { 
                  /* a */ new[] { "a", "b", "c" },
                  /* b */ new[] { "1", "2", "3" },
                  /* c */ new[] { "x", "y", "z" }
                };

    var setCombinations = from set in sets
                          select (from itemLength in Enumerable.Range(1, set.Count()).Reverse()
                                  select string.Concat(set.Take(itemLength).ToArray()));

    IEnumerable<string> result = new[] { string.Empty };

    foreach (var list in setCombinations) {
        result = GetCombinations(result, list);
    }
    // do something with the result
}

IEnumerable<string> GetCombinations(IEnumerable<string> root, IEnumerable<string> append) {
    return from baseString in root
           from combination in ((from str in append select baseString + str).Concat(new [] { baseString }))
           select combination;
}
Botz3000
works nicely - and it includes code to transform the original input array into their concatenated sets - excellent, thanks!
Andras Zoltan
+1 for LINQ implementation :)
Simon
I'm marking this as the answer primarily because it represents the full round-trip from input arrays to output.And also because of the Linq usage; nifty :)
Andras Zoltan