【问题标题】:Splitting LINQ query based on predicate基于谓词拆分 LINQ 查询
【发布时间】:2012-07-11 17:34:10
【问题描述】:

我想要一种方法,它可以在谓词处拆分 IEnumerable,并根据它们相对于谓词的索引将项目组合在一起。例如,它可以在满足x => MyRegex.Match(x).Success 的项目上拆分List<string>,并将这些匹配项“介于两者之间”的项目组合在一起。

它的签名可能看起来很像

public static IEnumerable<IEnumerable<TSource>> Split<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate,
    int bool count
)

,可能带有包含所有分隔符的输出的额外元素。

有没有比foreach 循环更有效和/或更紧凑的方法来实现它?我觉得应该可以用 LINQ 方法来实现,但我不能指望它。

示例:

string[] arr = {"One", "Two", "Three", "Nine", "Four", "Seven", "Five"};
arr.Split(x => x.EndsWith("e"));

以下任何一种都可以:

IEnumerable<string> {{}, {"Two"}, {}, {"Four", "Seven"}, {}}
IEnumerable<string> {{"Two"}, {"Four", "Seven"}}

用于存储匹配的可选元素是{"One", "Three", "Nine", "Five"}

【问题讨论】:

  • 你的意思不是group by
  • “分裂”是什么意思?返回两个列表,一个匹配一个不匹配?
  • 是的 - 它不会根据物品的特征而是根据定位进行拆分。
  • @Arithmomaniac,你的意思是Skip()Take()
  • 你能写出你的意思的foreach循环吗?

标签: c# linq


【解决方案1】:

如果您想避免使用扩展方法,您可以随时使用:

var arr = new[] {"One", "Two", "Three", "Nine", "Four", "Seven", "Five"};

var result = arr.ToLookup(x => x.EndsWith("e"));

// result[true]  == One Three Nine Five
// result[false] == Two Four Seven

【讨论】:

  • 这是一个巧妙的技巧,但不会保留真正的项目按拆分分组。
  • 谢谢,这帮助我解决了类似的问题
  • 这是一个很好的解决方案。此外,我们可以用肯定的词来命名变量。在 Andy 的示例中,将变量命名为 itemsEndWithE,因此 itemsEndWithE[false] 表示项目不以 E 结尾。我们不需要其他变量来表示它。
  • 这是一个很好的解决方案,尽管是针对不同的问题。这个标题听起来像的问题,而不是问的问题。
  • 加分,因为 LookUp 允许空输入和空输出。
【解决方案2】:

您应该通过扩展方法来执行此操作(此方法假定您忽略分区项):

/// <summary>Splits an enumeration based on a predicate.</summary>
/// <remarks>
/// This method drops partitioning elements.
/// </remarks>
public static IEnumerable<IEnumerable<TSource>> Split<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> partitionBy,
    bool removeEmptyEntries = false,
    int count = -1)
{
    int yielded = 0;
    var items = new List<TSource>();
    foreach (var item in source)
    {
        if (!partitionBy(item))
            items.Add(item);
        else if (!removeEmptyEntries || items.Count > 0)
        {
            yield return items.ToArray();
            items.Clear();

            if (count > 0 && ++yielded == count) yield break;
        }
    }

    if (items.Count > 0) yield return items.ToArray();
}

【讨论】:

  • 认为如果有两个或多个满足partitionBy()的连续项目,这将返回空的插页式数组。
  • @FrédéricHamidi:是的,他的例子包括中间的空男人。
  • @sixlettervariables 的想法是,如果第一个和/或最后一个项目匹配,它只会在边缘执行此操作。如果这样更容易实现,它们也可以是空白的。
  • @Arithmomaniac:您可以轻松修改此算法来处理这种情况。相反,我选择在String.Split 之后对其进行建模。 在罗马时!
  • 您可以继续为string.split 建模并提供一个重载来确定是否包含空值。当该设置处于活动状态时,检查列表是否为空而不是yield return 很容易。
【解决方案3】:

我将使用提供的键选择器对源集合进行分区。这样,您还可以根据简单属性对复杂对象进行切片。

public static class LinqExtension
{
    public static IEnumerable<IEnumerable<TSource>> Slice<TSource, TKey>(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> selector,
        Func<TKey, TKey, bool> partition)
    {
        if (source == null) throw new ArgumentNullException(nameof(source));
        if (selector == null) throw new ArgumentNullException(nameof(selector));
        if (partition == null) throw new ArgumentNullException(nameof(partition));

        var seed = new List<List<TSource>> { new List<TSource>() };

        return source.Aggregate(seed, (slices, current) => 
        {
            var slice = slices.Last();
            if (slice.Any())
            {
                var previous = slice.Last();
                if (partition(selector(previous), selector(current)))
                {
                    slice = new List<TSource>();
                    slices.Add(slice);
                }
            }
            slice.Add(current);
            return slices;

        }).Select(x => x.AsReadOnly());
    }
}

一个简单的例子:

// slice when the difference between two adjacent elements is bigger than 5
var input = new[] { 1, 2, 3, 10, 11, 20 };
var output = input.Slice(i => i, (x, y) => y - x > 5);

【讨论】:

    【解决方案4】:
    public static IEnumerable<IEnumerable<TSource>> Partition<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        yield return source.Where(predicate);
        yield return source.Where(x => !predicate(x));
    }
    

    例子:

    var list = new List<int> { 1, 2, 3, 4, 5 };
    var parts = list.Partition(x => x % 2 == 0);
    var even = parts.ElementAt(0); // contains 2, 4
    var odd = parts.ElementAt(1); // contains 1, 3, 5
    

    【讨论】:

    • 这个解决方案对性能来说不是很好,你将迭代列表两次。
    【解决方案5】:
    public static IEnumerable<IEnumerable<TSource>> Split<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, bool> predicate)
    {
        List<TSource> group = new List<TSource>();
        foreach (TSource item in source)
        {
            if (predicate(item))
            {
                yield return group.AsEnumerable();
                group = new List<TSource>();
            }
            else
            {
                group.Add(item);
            }
        }
        yield return group.AsEnumerable();
    }
    

    【讨论】:

    • 这里有什么理由打电话给AsEnumerable吗?
    猜你喜欢
    • 2010-09-12
    • 1970-01-01
    • 2017-03-15
    • 2022-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多