分隔符的分区列表

时间:2015-03-01 19:18:15

标签: c# linq split

是否有一种优雅的方式(例如使用LINQ)通过分隔符将列表分区为子列表?按{ 1, 2, delim, delim, 3, delim, 4, 5 }分区delim可能会产生{ { 1, 2 }, { 3 }, { 4, 5 } } ....

4 个答案:

答案 0 :(得分:2)

我不认为使用默认的LINQ方法有一种简单而优雅的方法。但您可以创建自己的扩展方法:

public static class MyExtensions
{
    public static IEnumerable<IEnumerable<TElement>> SplitBy<TElement>(
        this IEnumerable<TElement> source,
        TElement split,
        bool skipEmptyGroups = true)
        where TElement : IEquatable<TElement>
    {
        var group = new List<TElement>();

        foreach (var item in source)
        {
            if (split.Equals(item))
            {
                if (group.Count > 0 || !skipEmptyGroups)
                {
                    yield return group;
                    group = new List<TElement>();
                }
            }
            else
            {
                group.Add(item);
            }
        }

        if (group.Count > 0 || !skipEmptyGroups)
            yield return group;
    }
}

用法非常简单:

var source = new List<int> { 1, 2, 3, 3, 4, 3, 5, 3, 6, 7, 8 };

var result = source.SplitBy(3);

如果要返回空组,可以传递额外的bool参数:

var resultWithEmptyGroups = source.SplitBy(3, false);

答案 1 :(得分:1)

直接在Linq,我认为这很难,但你可以创建一个自定义运算符。也许是这样的:

List<String> test = new List<String>() { "1", "8", ";", "2", "7", "42", ";", "3" };
var restul = test.StrangePartition(";");

with:

public static class Helper
{
    public static IEnumerable<IEnumerable<T>> StrangePartition<T>(this IEnumerable<T> source, T partitionKey)
    {
        List<List<T>> partitions = new List<List<T>>();
        List<T> partition = new List<T>();
        foreach (T item in source)
        {

            if (item.Equals(partitionKey))
            {
                partitions.Add(partition);
                partition = new List<T>();
            }
            else
            {
                partition.Add(item);
            }
        }
        partitions.Add(partition);
        return partitions;
    }
}

答案 2 :(得分:0)

不,没有类似的东西,但实施它们非常容易:

public static class LinqEx
{
    public static IEnumerable<List<TSource>> Split<TSource>(this IEnumerable<TSource> enu, TSource delimiter, IEqualityComparer<TSource> comparer = null)
    {
        // list == null handles the case where enu is empty
        List<TSource> list = null;

        if (comparer == null)
        {
            // Note how the equality comparer is "selected".
            // This is how LINQ methods do it
            // (see Enumerable.SequenceEqual<TSource>)
            comparer = EqualityComparer<TSource>.Default;
        }

        foreach (TSource el in enu)
        {
            if (comparer.Equals(el, delimiter))
            {
                if (list == null)
                {
                    list = new List<TSource>();
                }

                yield return list;

                // Note that we have to recreate the list every time! 
                // We can't simply do a list.Clear()
                list = new List<TSource>();
                continue;
            }

            if (list == null)
            {
                list = new List<TSource>();
            }

            list.Add(el);
        }

        if (list != null)
        {
            yield return list;
        }
    }

    public static IEnumerable<List<TSource>> SplitRemoveEmpty<TSource>(this IEnumerable<TSource> enu, TSource delimiter, IEqualityComparer<TSource> comparer = null)
    {
        var list = new List<TSource>();

        if (comparer == null)
        {
            // Note how the equality comparer is "selected".
            // This is how LINQ methods do it
            // (see Enumerable.SequenceEqual<TSource>)
            comparer = EqualityComparer<TSource>.Default;
        }

        foreach (TSource el in enu)
        {
            if (comparer.Equals(el, delimiter))
            {
                if (list.Count != 0)
                {
                    yield return list;

                    // Note that we have to recreate the list every time! 
                    // We can't simply do a list.Clear()
                    list = new List<TSource>();
                }

                continue;
            }

            list.Add(el);
        }

        if (list.Count != 0)
        {
            yield return list;
        }
    }
}

有两种变体:第一种(Split)将返回空组,第二种将剥离空组。

例如:

{ delim, 1, 2, delim, delim, 3, delim, 4, 5, delim }

使用Split您将收到

{ { }, {1, 2}, { }, {3}, {4, 5}, { } }

使用SplitRemoveEmpty

{ {1, 2}, {3}, {4, 5} }

使用它们:

var res = new[] { 1, 2, 0, 0, 3, 4, 0, 5 }.SplitRemoveEmpty(0);

foreach (List<int> group in res)
{
    // Do something
}

请记住,这些拆分方法基于Equals方法的正确性!但是有一个可选的最后一个参数,你可以给出方法将使用的IEqualityComparer<TSource>而不是默认值。

答案 3 :(得分:0)

虽然其他人提到使用命令式方法会更容易,但它当然可以在LINQ中使用。 Aggregate运算符有很多用途,但通常可读性差。

var testCase = new List<String> { "1", "8", ";", "2", "7", "42", ";", "3" };
var result = testCase.Aggregate(new List<List<String>>() { new List<String>() }, (l, s) => {
    if (s == ";") {
        l.Add(new List<String>());
    } else {
        l[l.Count - 1].Add(s);
    }
    return l;
}).ToList();

它会产生所需的输出,但它不仅难以理解,而且还会在查询中产生副作用(不像在查询之外产生副作用那么糟糕,但仍然是不明智的)。

如果您愿意,可以将它放入您自己的扩展方法中:

public static IEnumerable<IEnumerable<T>> PartitionBy(this IEnumerable<T> source, T delimiter) {
    return source.Aggregate(new List<List<T>>() { new List<T>() }, (l, elem) =>
        if(elem.Equals(delimiter)) {
            l.Add(new List<T>());
        } else {
            l[l.Count - 1].Add(elem);
        }
        return l;
    });
}

同样,请考虑这个答案仅仅是为了LINQ答案的完整性。