abatishchev
abatishchev

Reputation: 100288

An analog of String.Join(string, string[]) for IEnumerable<T>

class String contains very useful method - String.Join(string, string[]).

It creates a string from an array, separating each element of array with a symbol given. But general - it doesn't add a separator after the last element! I uses it for ASP.NET coding for separating with "<br />" or Environment.NewLine.

So I want to add an empty row after each row in asp:Table. What method of IEnumerable<TableRow> can I use for the same functionality?

Upvotes: 10

Views: 7597

Answers (9)

Jason C
Jason C

Reputation: 40336

I found this post looking for the same thing, but was very unhappy with the answers (most seem to be about joining strings [?], and the accepted answer seems verbose). So I ended up just figuring out some other ways to do it.

The way I prefer is this:

public static IEnumerable<T> AfterEach <T> (this IEnumerable<T> source, T delimiter) =>
    source.SelectMany((item) => Enumerable.Empty<T>().Append(item).Append(delimiter));

Or, alternatively:

public static IEnumerable<T> AfterEach <T> (this IEnumerable<T> source, T delimiter) =>
    source.SelectMany((item) => new T[] { item, delimiter });

They're both equivalent: For each item, create a new sequence { item, delimiter }, and use SelectMany to concatenate those all together.

Another approach, which isn't a one liner but might be easier to read and is very straightforward:

public static IEnumerable<T> AfterEach <T> (this IEnumerable<T> source, T delimiter) {
    foreach (T item in source) {
        yield return item;
        yield return delimiter;
    }
}

If you don't want the extra delimiter on the end, the approach is similar, except you concatenate { delimiter, item } sequences then Skip the extra delimiter at the beginning (this is more performant than skipping one at the end).

public static IEnumerable<T> Delimit <T> (this IEnumerable<T> source, T delimiter) =>
    source.SelectMany((item) => Enumerable.Empty<T>().Append(delimiter).Append(item)).Skip(1);

public static IEnumerable<T> Delimit <T> (this IEnumerable<T> source, T delimiter) =>
    source.SelectMany((item) => new T[] { delimiter, item }).Skip(1);

The yield approach would be like this (again, two similar options, just depends on how you're feeling):

public static IEnumerable<T> Delimit <T> (this IEnumerable<T> source, T delimiter) {
    foreach (T item in source.Take(1)) // protects agains empty source
        yield return item;
    foreach (T item in source.Skip(1)) {
        yield return delimiter;
        yield return item;
    }
}

public static IEnumerable<T> Delimit <T> (this IEnumerable<T> source, T delimiter) {
    static IEnumerable<U> Helper<U> (IEnumerable<U> source, U delimiter) {
        foreach (U item in source) {
            yield return delimiter;
            yield return item;
        }
    }
    return Helper(source, delimiter).Skip(1);
}

There's some runnable examples and test code (for Delimit, not AfterEach) here. There's an implementation with Aggregate there too that I didn't think was worth writing about here. It could be adapted to AfterEach in a similar manner.

Upvotes: 1

Fidel
Fidel

Reputation: 7397

I find that I use this extension method a lot.

With luck, it might be incorporated into C#.

public static class Extensions
{
    public static string ToString(this IEnumerable<string> list, string separator)
    {
        string result = string.Join(separator, list);
        return result;
    }
}

Usage:

var people = new[]
{
    new { Firstname = "Bruce", Surname = "Bogtrotter" },
    new { Firstname = "Terry", Surname = "Fields" },
    new { Firstname = "Barry", Surname = "Wordsworth"}
};

var guestList = people
                    .OrderBy(p => p.Firstname)
                    .ThenBy(p => p.Surname)
                    .Select(p => $"{p.Firstname} {p.Surname}")
                    .ToString(Environment.NewLine);

Upvotes: 1

spender
spender

Reputation: 120480

I wrote an extension method:

    public static IEnumerable<T> 
        Join<T>(this IEnumerable<T> src, Func<T> separatorFactory)
    {
        var srcArr = src.ToArray();
        for (int i = 0; i < srcArr.Length; i++)
        {
            yield return srcArr[i];
            if(i<srcArr.Length-1)
            {
                yield return separatorFactory();
            }
        }
    }

You can use it as follows:

tableRowList.Join(()=>new TableRow())

Upvotes: 8

Michael Kropat
Michael Kropat

Reputation: 15217

What you are looking for is an Intersperse function. For LINQ implementations of such a function, see this question.


Incidentally, another possible analog of String.Join is the Intercalate function, which is actually what I was looking for:

public static IEnumerable<T> Intercalate<T>(this IEnumerable<IEnumerable<T>> source,
                                            IEnumerable<T> separator) {
    if (source == null) throw new ArgumentNullException("source");
    if (separator == null) throw new ArgumentNullException("separator");
    return source.Intersperse(separator)
        .Aggregate(Enumerable.Empty<T>(), Enumerable.Concat);
}

Upvotes: 1

Damian Powell
Damian Powell

Reputation: 8775

If you are going to do this sort of thing frequently, then it's worth building your own extension method to do it. The implementation below allows you to do the equivalent of string.Join(", ", arrayOfStrings) where the arrayOfStrings can be an IEnumerable<T>, and separator can be any object at all. It allows you to do something like this:

var names = new [] { "Fred", "Barney", "Wilma", "Betty" };
var list = names
    .Where(n => n.Contains("e"))
    .Join(", ");

Two things I like about this are:

  1. It's very readable in a LINQ context.
  2. It's fairly efficient because it uses StringBuilder and avoids evaluating the enumeration twice.
public static string Join<TItem,TSep>(
    this IEnumerable<TItem> enuml,
    TSep                    separator)
{
    if (null == enuml) return string.Empty;

    var sb = new StringBuilder();

    using (var enumr = enuml.GetEnumerator())
    {
        if (null != enumr && enumr.MoveNext())
        {
            sb.Append(enumr.Current);
            while (enumr.MoveNext())
            {
                sb.Append(separator).Append(enumr.Current);
            }
        }
    }

    return sb.ToString();
}

Upvotes: 0

Scott Weinstein
Scott Weinstein

Reputation: 19117

The Linq equivalent of String.Join is Aggregate

For instance:

IEnumerable<string> strings;
string joinedString = strings.Aggregate((total,next) => total + ", " + next);

If given an IE of TableRows, the code will be similar.

Upvotes: 9

Svish
Svish

Reputation: 158101

If I couldn't find a method that suits my need, I would just create my own. And extension methods are very nice that way since they let you extend stuff like that. Don't know much about asp:table, but here is an extension method at least which you can tweak to whatever :p

public static class TableRowExtensions
{
    public string JoinRows(this IEnumerable<TableRow> rows, string separator)
    {
        // do what you gotta do
    }
}

Upvotes: 1

Ian Mercer
Ian Mercer

Reputation: 39277

In .NET 3.5 you can use this extension method:

public static string Join<TItem>(this IEnumerable<TItem> enumerable, string separator)
{
   return string.Join(separator, enumerable.Select(x => x.ToString()).ToArray());
}

or in .NET 4

public static string Join<TItem>(this IEnumerable<TItem> enumerable, string separator)
{
   return string.Join(separator, enumerable);
}

BUT the question wanted a separator after each element including the last for which this (3.5 version) would work:-

public static string AddDelimiterAfter<TItem>(this IEnumerable<TItem> enumerable, string delimiter)
{
   return string.Join("", enumerable.Select(x => x.ToString() + separator).ToArray());
}

You could also use .Aggregate to do this without an extension method.

Upvotes: 8

driis
driis

Reputation: 164301

There is no built in method to do that, you should roll your own.

Upvotes: 1

Related Questions