michael
michael

Reputation: 15302

What's the best way to apply a "Join" method generically similar to how String.Join(...) works?

If I have a string array, for example: var array = new[] { "the", "cat", "in", "the", "hat" }, and I want to join them with a space between each word I can simply call String.Join(" ", array).

But, say I had an array of integer arrays (just like I can have an array of character arrays). I want to combine them into one large array (flatten them), but at the same time insert a value between each array.

var arrays = new[] { new[] { 1, 2, 3 }, new[] { 4, 5, 6 }, new { 7, 8, 9 }};

var result = SomeJoin(0, arrays); // result = { 1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9 }

I wrote something up, but it is very ugly, and I'm sure that there is a better, cleaner way. Maybe more efficient?

var result = new int[arrays.Sum(a => a.Length) + arrays.Length - 1];

int offset = 0;
foreach (var array in arrays)
{
     Buffer.BlockCopy(array, 0, result, offset, b.Length);
     offset += array.Length;

     if (offset < result.Length)
     {
         result[offset++] = 0;
     }
}

Perhaps this is the most efficient? I don't know... just seeing if there is a better way. I thought maybe LINQ would solve this, but sadly I don't see anything that is what I need.

Upvotes: 3

Views: 69

Answers (3)

Reed Copsey
Reed Copsey

Reputation: 564451

You can generically "join" sequences via:

public static IEnumerable<T> Join<T>(T separator, IEnumerable<IEnumerable<T>> items)
{
    var sep = new[] {item};
    var first = items.FirstOrDefault();
    if (first == null)
        return Enumerable.Empty<T>();
    else
        return first.Concat(items.Skip(1).SelectMany(i => sep.Concat(i)));      
}

This works with your code:

var arrays = new[] { new[] { 1, 2, 3 }, new[] { 4, 5, 6 }, new { 7, 8, 9 }};
var result = Join(0, arrays); // result = { 1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9 }

The advantage here is that this will work with any IEnumerable<IEnumerable<T>>, and isn't restricted to lists or arrays. Note that this will insert a separate in between two empty sequences, but that behavior could be modified if desired.

Upvotes: 5

alc
alc

Reputation: 1557

This may not be the most efficient, but it is quite extensible:

public static IEnumerable<T> Join<T>(this IEnumerable<IEnumerable<T>> source, T separator)
{
  bool firstTime = true;
  foreach (var collection in source)
  {
    if (!firstTime)
      yield return separator;

    foreach (var value in collection)
      yield return value;

    firstTime = false;
  }
}

...

var arrays = new[] { new[] { 1, 2, 3 }, new[] { 4, 5, 6 }, new[] { 7, 8, 9 }};
var result = arrays.Join(0).ToArray();
//  result = { 1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9 }

Upvotes: 2

King King
King King

Reputation: 63327

public T[] SomeJoin<T>(T a, T[][] arrays){
   return arrays.SelectMany((x,i)=> i == arrays.Length-1 ? x : x.Concat(new[]{a}))
                .ToArray();
}

NOTE: The code works seamlessly because of using Array, otherwise we may lose some performance cost to get the Count of the input collection.

Upvotes: 2

Related Questions