1

I have a list of strings, each of variable length. I want to project a subset out of the list which has strings concatenated from the original list having length equal to 5. I'm using the aggregate function for it, but it's not giving me the desired result. What would be an appropriate LINQ query for this projection? Can you please help?

Code:

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<string> items = new List<string> {"abc", "ab", "abcd", "abcde", "abcdef", "a", "ab", "cde"};

        //All combinations of concatenations equal to length 5.
        //Only need to process an item once and need to concatenate 2 items and no more

        var filteredList = items.Where(x => x.Length < 5)
                                .Aggregate(Execute).ToList();

        foreach (var f in filteredList)
        {
            //Should out put : abc+ab = abcab
            //Should out put : abcde
            //Should out put : abcd+a = abcda
            //Should out put : ab+cde = abcde
            Console.WriteLine(f);
        }
    }

    private static string Execute(string a, string b)
    {
        if (string.IsNullOrEmpty(a) || string.IsNullOrEmpty(b))
            return null;

        if ((a.Length + b.Length) == 5)
            return a + b;

        return null;
    }
}

Couple of points:

  • Once an item is processed, I dont need to consider that item again for a combination

  • Above is true until I find the same item again in the list, once I find it I should try to concatenate it with another item which was not used in a previous concatenation.

  • No need of it to be LINQ, I'm just looking for a solution.

  • An output cannot consist of more than two strings? (a + bc + de) is not required.

  • An item need not be concatenated with itself.

  • I have mentioned the output as part of the question.

Note:Using .NET 3.5 (but would like to see a shortcut in .NET 4.0 as well if possible)

ekad
  • 14,436
  • 26
  • 44
  • 46
Mike
  • 3,204
  • 8
  • 47
  • 74
  • I'm not sure I understand the requirement. Why isn't ababc (ab + abc) a desired output? – recursive Oct 15 '13 at 16:44
  • Because abc+ab has already produced an output, and then you dont need to consider both abc and ab again, until you find the next abc or ab. – Mike Oct 15 '13 at 16:48
  • I think you are looking for [cross join](http://stackoverflow.com/questions/56547/how-do-you-perform-a-cross-join-with-linq-to-sql) of filteredList to itself, not an `Aggregate` call (Assuming "All combinations" means all combinations:) ) – Alexei Levenkov Oct 15 '13 at 16:49
  • does it need to be linq? – Magnus Oct 15 '13 at 16:55
  • 1
    Could an output consist of more than two strings? (a + bc + de) – recursive Oct 15 '13 at 17:00
  • @Mike: This isn't a question about LINQ as much as it is a call for someone else to write an entire function for you. Is there something specific that you have a question about? – Andrew Coonce Oct 15 '13 at 22:20

6 Answers6

1
        List<string> items = new List<string> { "abc", "ab", "abcd", "abcde", "abcdef", "a", "ab", "cde" };
        var result = items
                        .SelectMany((x, i) => items.Skip(i + 1).Concat(new[] {string.Empty}), (s1, s2) => s1 + s2)
                        .Where(s => s.Length == 5);
schglurps
  • 1,387
  • 1
  • 14
  • 26
  • Why the `.Take(items.Count - i - 1)`? It looks like you're specially excluding the last element. – recursive Oct 15 '13 at 17:07
  • Oops... In fact I wanted to exclude the current element, in order to not concatenate it with itself. So I replaced Skip(i) by Skip(i + 1)... and I don't exclude the last element ! Thank you for your remark ! – schglurps Oct 16 '13 at 07:15
  • Ok, I see. In that case, the `Take` is redundant, since by default, the `IEnumerable` will enumerate over all the remaining elements. – recursive Oct 16 '13 at 15:42
1

If you ask me, I say: "Don't get lazy".

    private static List<string> ValidCombinationsFind(List<string> iSource)
    {
        List<string> lstResult = new List<string>();

        //Use and explicit mask to remember indexes in iSource which were used.
        bool[] ablnUsageMask = new bool[iSource.Count];

        int intCurrentIndex = 0;

        //Examine the source list one by one.
        while (intCurrentIndex < iSource.Count - 1)
        {
            //If the next item is not already used then go on.
            if (!ablnUsageMask[intCurrentIndex])
            {
                string strCurrentItem = iSource[intCurrentIndex];

                //If the item is ok then check every remaining item for a match.
                if (!string.IsNullOrEmpty(strCurrentItem))
                {
                    //Check if an item fits on its own.
                    if (strCurrentItem.Length == 5)
                    {
                        ablnUsageMask[intCurrentIndex] = true;
                        lstResult.Add(strCurrentItem);
                    }
                    else
                    {
                        for (int intNextItemIndex = intCurrentIndex + 1; intNextItemIndex < iSource.Count; intNextItemIndex++)
                        {
                            //If the next item is not already used then go on.
                            if (!ablnUsageMask[intNextItemIndex])
                            {
                                string strNextItem = iSource[intNextItemIndex];

                                if (!string.IsNullOrEmpty(strNextItem))
                                {
                                    if ((strCurrentItem.Length + strNextItem.Length) == 5)
                                    {
                                        ablnUsageMask[intCurrentIndex] = true;
                                        ablnUsageMask[intNextItemIndex] = true;
                                        lstResult.Add(strCurrentItem + strNextItem);
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
            }

            intCurrentIndex++;
        }

        return lstResult;
    }
Zverev Evgeniy
  • 3,643
  • 25
  • 42
0
void Main()
{
    var items = new List<string> {"abc", "ab", "abcd", "abcde", "abcdef", "a", "ab", "cde"};
    bool[] flag=new bool[items.Count()];
    var output=items.SelectMany((x,i)=>items.Select((y,j)=>concat(flag,items,i,j)))
                    .Where(x=>x!="");
}

public string concat(bool[] flag,List<string> items,int i,int j)
{
if(flag[i]==false && flag[j]==false && (items[i].Length==5||(items[i]+items[j]).Length==5))
{
      flag[i]=true;
      flag[j]=true;

      if(i==j)return items[i];
      else return items[i]+","+items[j];
}
else return "";
}

Output:

abc,ab 
abcd,a 
abcde 
ab,cde 
Anirudha
  • 32,393
  • 7
  • 68
  • 89
  • 1
    This provides items concatted with themselves. It's not clear if they should be included or excluded. This also doesn't display the single string value in the example; it only shows the pairs. – Servy Oct 15 '13 at 17:03
  • No, yours misses the last one i.e ab+cde. As I said, if the item occurs again, that should be considered. – Mike Oct 15 '13 at 17:12
  • I said, if an item is processed no need of it to be considered again. For example abc+ab = abcab (now abc and ab is used) which means abc should not appear in another concatenation. – Mike Oct 15 '13 at 17:24
0

I believe this does what you want:

  var filteredList = items.Where(x => x.Length < 5)
    .SelectMany(x => items, (y, z) => { return Execute(y, z); })
    .Where(x => x != null).Distinct().ToList();
Daniel
  • 10,864
  • 22
  • 84
  • 115
0

So the idea here is to pair up objects such that these two objects will have exactly the right size. What we can do here is group all of the strings by their size. We know that all of the items of size one and four need to be paired up, those of size 2 and three need to be paired up, and those of size 5 are just fine on their own.

So we can then just iterate through the possible sizes, grab the group with that size and the group with the size to create the desirable items, and then zip up those two collections. Zip will take the first item from the first group, match it with the first item from the second, and then so on, for each index. This means each string will never be repeated. We can then add onto the end the items that were exactly the right size.

private static IEnumerable<string> MakeDesiredSize(
    List<string> items, int desiredSide)
{
    var lookup = items.Where(item => item.Length <= desiredSide)
        .ToLookup(item => item.Length);
    return Enumerable.Range(1, desiredSide / 2)
            .SelectMany(i => lookup[i].Zip(lookup[desiredSide - i]
                , (a, b) => a + b))
            .Concat(lookup[desiredSide]);
}

Since you're on 3.5, here's an implementation of Zip:

public static IEnumerable<TResult> Zip<TSource, TResult>(
    this IEnumerable<TSource> first,
    IEnumerable<TSource> second,
    Func<TSource, TSource, TResult> resultSelector)
{
    using (var itOne = first.GetEnumerator())
    using (var itSecond = second.GetEnumerator())
        while (itOne.MoveNext() & itSecond.MoveNext())
            yield return resultSelector(itOne.Current, itSecond.Current);
}
Servy
  • 202,030
  • 26
  • 332
  • 449
  • What would be the alterantive of Zip in .NET 3.5 – Mike Oct 15 '13 at 17:26
  • @Mike Just reimplement it; it's pretty simple to write. Just accept the two sequences, iterate them both at the same time and yield the result of the selector on the current items. – Servy Oct 15 '13 at 17:28
  • @Mike I get the exact combinations as in your question when running the code. Note that based on the requirements that you gave there are a number of different possible solutions that are all valid based on which items choose to be matched with which, and which order you choose to display the pairs of items. If it's important that you get one particular solution among those different options you'll need to specify how to determine which matches are correct. Side note, if you change `Zip` to use `(a, b) => a + "+" + b)` it makes it clear what the combinations are. – Servy Oct 15 '13 at 17:37
  • The program outputs aabcd, abcbc, abcde and abcde for me. – Mike Oct 15 '13 at 17:44
  • @Mike I have `ababc` for the second item. Are you sure that's not what you got too? Given that change, each item the corresponds exactly to the pairs that you have, it just has the combinations in a different order. My first is your third, but with the items reversed, my second is your first, my third is your fourth, and my fourth is your second. That's two different, but equally valid, solutions given the rules that you provided. – Servy Oct 15 '13 at 17:50
0

Using a double for loop while keeping track of which indexes gave a match should do it:

IEnumerable<string> items = new List<string> { "abc", "ab", "abcd", "abcde", "abcdef", "a", "ab", "cde" };
var filteredList = GimmeFives(items).ToList();

private IEnumerable<string> GimmeFives(IEnumerable<string> items)
{
    //"Once an item is processed, I dont need to consider that item again for a combination"
    var indexesProcessed = new List<int>();

    for (int i = 0; i < items.Count(); i++)
    {
        if (indexesProcessed.Contains(i)) { continue; }

        var first = items.ElementAt(i);
        if (first.Length == 5)
        {
            yield return first;
        }
        else
        {
            //Start the second loop after index "i", to avoid including previously processed items:
            for (int j = i+1; j < items.Count(); j++)
            {
                if (indexesProcessed.Contains(j)) { continue; }

                var second = items.ElementAt(j);

                if ((first.Length + second.Length) == 5)
                {
                    //Remove the middle "+" sign in production code...
                    yield return (first + "+" + second);

                    indexesProcessed.Add(i);
                    indexesProcessed.Add(j);

                    //"Once an item is processed, I dont need to consider that item again for a combination"
                    //"first" has gotten its match, so we don't need to search for another "second":
                    break;
                }
            }
        }

    }
}

Output:

abc+ab
abcd+a
abcde
ab+cde
Sphinxxx
  • 12,484
  • 4
  • 54
  • 84