1

Is there an inverse/complement of IEnumerable.SelectMany? That is, is there a method of the form IEnumerable<T>.InverseSelectMany(Func<IEnumerable<T>,T>) which will find a sequence in the input sequence and perform a transform to a single element and then flatten the whole thing out again?

For example, if you wanted to replace all escape sequences { 0x7E, 0x7E } in an HDLC frame with just a single byte 0x7E, you could do something like

byte[] byteArray = new byte[] { 0x01, 0x02, 0x7E, 0x7E, 0x04 }; // etc.
byte[] escapeSequence = new byte[] { 0x7E, 0x7E };
byte[] outputBytes = byteArray.InverseSelectMany<byte,byte>(x =>
{
   if (x.SequenceEqual(escapeSequence))
   {
      return new List<byte> { 0x7E };
   {
   else
   {
      return x;
   }
});

Does that make any sense or am I missing something critical here?

Kongress
  • 2,244
  • 3
  • 20
  • 30
  • 1
    http://stackoverflow.com/questions/807797/linq-select-an-object-and-change-some-properties-without-creating-a-new-object – Josiah Ruddell Jul 19 '11 at 17:44
  • So you want something that take a list of things and outputs a list of lists where each n things is in a list? – antlersoft Jul 19 '11 at 17:46
  • @Josiah Would you explain the application of the link? I don't want to change the properties of an element in the collection, I want to replace an arbitrary sequence of elements in the collection with another element. The arbitrary sequence is not a collection object within the top level collection. – Kongress Jul 19 '11 at 20:26
  • @antlersoft I want to take a list of things and output a list of things, where every occurrence of a given sequence of things in the list is replaced by a single thing. Think SelectMany in reverse, with the flattening effect also in reverse (somehow). – Kongress Jul 19 '11 at 20:27

2 Answers2

3

There isn't anything built-in like that. The first problem is that by passing an arbitrary Func<IEnumerable<T>,T> to the enumerator, it won't know how many bytes it will need to "take" and pass to the function. A more reasonable approach is shown below, where you can pass a sequence to be replaced, and the other sequence to replace, and do a simple search for that.

public static class Extensions
{
    public static IEnumerable<T> ReplaceSequence<T>(this IEnumerable<T> original, IEnumerable<T> toSearch, IEnumerable<T> toReplace) where T : IEquatable<T>
    {
        T[] toSearchItems = toSearch.ToArray();
        List<T> window = new List<T>();
        foreach (T value in original)
        {
            window.Add(value);
            if (window.Count == toSearchItems.Length)
            {
                bool match = true;
                for (int i = 0; i < toSearchItems.Length; i++)
                {
                    if (!toSearchItems[i].Equals(window[i]))
                    {
                        match = false;
                        break;
                    }
                }

                if (match)
                {
                    foreach (T toReplaceValue in toReplace)
                    {
                        yield return toReplaceValue;
                    }

                    window.Clear();
                }
                else
                {
                    yield return window[0];
                    window.RemoveAt(0);
                }
            }
        }

        foreach (T value in window)
        {
            yield return value;
        }
    }
}
// http://stackoverflow.com/q/6751533/751090
public class StackOverflow_6751533
{
    public static void Test()
    {
        byte[] byteArray = new byte[] { 0x01, 0x02, 0x7E, 0x7E, 0x04 };
        byte[] escapeSequence = new byte[] { 0x7E, 0x7E };
        byte[] unescapedSequence = new byte[] { 0x7E };
        byte[] outputBytes = byteArray.ReplaceSequence(escapeSequence, unescapedSequence).ToArray();
        for (int i = 0; i < outputBytes.Length; i++)
        {
            Console.Write("{0:X2} ", (int)outputBytes[i]);
        }
        Console.WriteLine();
    }
}
carlosfigueira
  • 85,035
  • 14
  • 131
  • 171
  • 3
    +1 for using the class name of StackOverflow_6751533 instead of Foo – Conrad Frix Jul 19 '11 at 23:15
  • Not quite what I'd hoped for, but then the question did involve some fuzzy/wishful thinking. Regardless, your solution is thorough and slick. Thanks for taking the time. – Kongress Jul 20 '11 at 12:43
0

I don't know if this would help anybody else, but this is "inverse" in my mind. If, for example, you had an object a.Items with {1, 2, 3} and b.Items with {3, 4, 5} calling DeselectMany will give you 6 KeyValuePairs of 1:a, 2:a, 3:a, 3:b, 4:b, 5:b. Optionally you can call the overload that lets you return something other than KeyValuePair

    public static IEnumerable<KeyValuePair<TChild, TItem>> DeselectMany<TItem, TChild>(this IEnumerable<TItem> enumerable, Func<TItem, IEnumerable<TChild>> getElements)
    {
        return DeselectMany(
            enumerable,
            getElements,
            (c, i) => new KeyValuePair<TChild, TItem>(c, i)
            );
    }

    public static IEnumerable<TResult> DeselectMany<TItem, TChild, TResult>(this IEnumerable<TItem> enumerable, Func<TItem, IEnumerable<TChild>> getElements, Func<TChild, TItem, TResult> resultFactory)
    {
        foreach (var item in enumerable)
        {
            var elements = getElements(item);

            foreach (var element in elements)
            {
                var result = resultFactory(element, item);
                yield return result;
            }
        }
    }
Brian Booth
  • 727
  • 1
  • 6
  • 10