如何使用 linq 按顺序计算每组中的项目数?

发布于 2024-11-07 17:34:10 字数 163 浏览 0 评论 0原文

例如,我有一个整数序列,

1122211121

我想要一些字典/匿名类显示:

item | count
1    | 2
2    | 3
1    | 3
2    | 1
1    | 1

For example, I have a sequence of integers

1122211121

I'd like to get some dictionary/anonymous class showing:

item | count
1    | 2
2    | 3
1    | 3
2    | 1
1    | 1

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

丿*梦醉红颜 2024-11-14 17:34:10
        var test = new[] { 1, 2, 2, 2, 2, 1, 1, 3 };
        int previous = test.First();
        int idx = 0;
        test.Select(x =>
                x == previous ?
                new { orig = x, helper = idx } :
                new { orig = previous = x, helper = ++idx })
            .GroupBy(x => x.helper)
            .Select(group => new { number = group.First().orig, count = group.Count() });

如果您想更加 Linqy,可以在 let 子句中完成 previousidx 的初始化。

       from whatever in new[] { "i want to use linq everywhere" }
       let previous = test.First()
       let idx = 0
       from x in test
       ...

函数式编程很好,但恕我直言,在这种情况下,在 C# 中我肯定会选择过程式方法。

        var test = new[] { 1, 2, 2, 2, 2, 1, 1, 3 };
        int previous = test.First();
        int idx = 0;
        test.Select(x =>
                x == previous ?
                new { orig = x, helper = idx } :
                new { orig = previous = x, helper = ++idx })
            .GroupBy(x => x.helper)
            .Select(group => new { number = group.First().orig, count = group.Count() });

initialization of previous and idx could be done in let clause if you want to be even more Linqy.

       from whatever in new[] { "i want to use linq everywhere" }
       let previous = test.First()
       let idx = 0
       from x in test
       ...

Functional programming is nice, but imho this is a case where in C# I would surely choose rather procedural approach.

影子是时光的心 2024-11-14 17:34:10

您希望执行类似于 morelinq 项目中的“Batch”运算符的操作,然后输出组的计数。

不幸的是,morelinq 中的批处理运算符只接受一个大小并返回按该大小批处理的存储桶(或者在我查看 morelinq 时确实如此)。为了纠正这个缺陷,我必须编写自己的批处理实现。

private static IEnumerable<TResult> BatchImplementation<TSource, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, int, bool> breakCondition,
        Func<IEnumerable<TSource>, TResult> resultSelector
    )
{
    List<TSource> bucket = null;
    var lastItem = default(TSource);
    var count = 0;

    foreach (var item in source)
    {
        if (breakCondition(item, lastItem, count++))
        {
            if (bucket != null)
            {
                yield return resultSelector(bucket.Select(x => x));
            }

            bucket = new List<TSource>();
        }
        bucket.Add(item);
        lastItem = item;
    }

    // Return the last bucket with all remaining elements
    if (bucket.Count > 0)
    {
        yield return resultSelector(bucket.Select(x => x));
    }
}

这是我公开了验证输入参数的各种公共重载的私有版本。您希望您的breakCondition具有以下形式:

Func<int, int, int, bool> breakCondition = x, y, z => x != y;

对于您的示例序列,这应该为您提供: {1, 1}, {2, 2, 2}, {1, 1, 1}, {2} , {1}

从这里开始,抓取每个序列的第一项然后对序列进行计数就很简单了。

编辑:为了协助实施-

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, int, bool> breakCondition
    )
{
    //Validate that source, breakCondition, and resultSelector are not null
    return BatchImplemenatation(source, breakCondition, x => x);
}

您的代码将是:

var sequence = {1, 1, 2, 2, 2, 1, 1, 1, 2, 1};
var batchedSequence = sequence.batch((x, y, z) => x != y);
//batchedSequence = {{1, 1}, {2, 2, 2}, {1, 1, 1}, {2}, {1}}
var counts = batchedSequence.Select(x => x.Count());
//counts = {2, 3, 3, 1, 1}
var items = batchedSequence.Select(x => x.First());
//items = {1, 2, 1, 2, 1}
var final = counts.Zip(items. (c, i) => {Item = i, Count = c});

除了我在自己的代码库中使用的私有方法及其重载之外,我还没有编译和测试任何这些,但这应该可以解决您的问题以及您遇到的任何类似问题。

You're looking to do something like the "Batch" operator in the morelinq project, then output the count of the groups.

Unfortunately, the batch operator from morelinq just takes a size and returns buckets batched by that size (or it did when I was looking at morelinq). To correct this deficiency, I had to write my own batch implementation.

private static IEnumerable<TResult> BatchImplementation<TSource, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, int, bool> breakCondition,
        Func<IEnumerable<TSource>, TResult> resultSelector
    )
{
    List<TSource> bucket = null;
    var lastItem = default(TSource);
    var count = 0;

    foreach (var item in source)
    {
        if (breakCondition(item, lastItem, count++))
        {
            if (bucket != null)
            {
                yield return resultSelector(bucket.Select(x => x));
            }

            bucket = new List<TSource>();
        }
        bucket.Add(item);
        lastItem = item;
    }

    // Return the last bucket with all remaining elements
    if (bucket.Count > 0)
    {
        yield return resultSelector(bucket.Select(x => x));
    }
}

This is the private version that I expose various public overloads which validate input parameters. You would want your breakCondition to be something of the form:

Func<int, int, int, bool> breakCondition = x, y, z => x != y;

This should give you, for your example sequence: {1, 1}, {2, 2, 2}, {1, 1, 1}, {2}, {1}

From here, grabbing the first item of each sequence and then counting the sequence are trivial.

Edit: To assist in implementation -

public static IEnumerable<IEnumerable<TSource>> Batch<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, int, bool> breakCondition
    )
{
    //Validate that source, breakCondition, and resultSelector are not null
    return BatchImplemenatation(source, breakCondition, x => x);
}

Your code would then be:

var sequence = {1, 1, 2, 2, 2, 1, 1, 1, 2, 1};
var batchedSequence = sequence.batch((x, y, z) => x != y);
//batchedSequence = {{1, 1}, {2, 2, 2}, {1, 1, 1}, {2}, {1}}
var counts = batchedSequence.Select(x => x.Count());
//counts = {2, 3, 3, 1, 1}
var items = batchedSequence.Select(x => x.First());
//items = {1, 2, 1, 2, 1}
var final = counts.Zip(items. (c, i) => {Item = i, Count = c});

I haven't compiled and tested any of this except the private method and its overloads that I use in my own codebase, but this should solve your problem and any similar problems you have.

征棹 2024-11-14 17:34:10

嗯...稍微短一点(注意处理偶数/奇数出现计数的双重分离调用):

    static void Main(string[] args)
    {
        string separatedDigits = Separate(Separate("1122211121"));

        foreach (var ano in separatedDigits.Split('|').Select(block => new { item = block.Substring(0, 1), count = block.Length }))
            Console.WriteLine(ano);

        Console.ReadKey();
    }

    static string Separate(string input)
    {
        return Regex.Replace(input, @"(\d)(?!\1)(\d)", "$1|$2");
    }
}

Wel... a bit shorter (notice the double Separate call to deal with even/odd occurrences counts) :

    static void Main(string[] args)
    {
        string separatedDigits = Separate(Separate("1122211121"));

        foreach (var ano in separatedDigits.Split('|').Select(block => new { item = block.Substring(0, 1), count = block.Length }))
            Console.WriteLine(ano);

        Console.ReadKey();
    }

    static string Separate(string input)
    {
        return Regex.Replace(input, @"(\d)(?!\1)(\d)", "$1|$2");
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文