LINQ:按索引和值分组

时间:2018-03-15 14:46:11

标签: c# linq c#-7.2

假设我有一个包含以下值的字符串列表:

  

[ “一”, “一”, “B”, “一”, “一”, “一个”, “C”, “C”]

我想执行一个linq查询,该查询将分为4组:

  

第1组:[“a”,“a”]第2组:[“b”]第3组:[“a”,“a”,“a”]第4组:   [ “C”, “C”]

基本上我想为值“a”创建2个不同的组,因为它们不是来自相同的“索引序列”。

任何人都有LINQ解决方案吗?

4 个答案:

答案 0 :(得分:1)

你只需要除数组项目之外的其他键

var x = new string[] { "a", "a", "a", "b", "a", "a", "c" };


int groupId = -1;
var result = x.Select((s, i) => new
{
    value = s,
    groupId = (i > 0 && x[i - 1] == s) ? groupId : ++groupId
}).GroupBy(u => new { groupId });


foreach (var item in result)
{
    Console.WriteLine(item.Key);
    foreach (var inner in item)
    {
        Console.WriteLine(" => " + inner.value);
    }
}

结果如下:Link

答案 1 :(得分:0)

计算"索引序列"首先,然后做你的小组。

private class IndexedData
{
    public int Sequence;
    public string Text;
} 

string[] data = [ "a", "a", "b" ... ]

// Calculate "index sequence" for each data element.
List<IndexedData> indexes = new List<IndexedData>();

foreach (string s in data)
{
    IndexedData last = indexes.LastOrDefault() ?? new IndexedData();

    indexes.Add(new IndexedData
    {
        Text = s,
        Sequence = (last.Text == s
                      ? last.Sequence 
                      : last.Sequence + 1)
    });
}

// Group by "index sequence"
var grouped = indexes.GroupBy(i => i.Sequence)
                     .Select(g => g.Select(i => i.Text));

答案 2 :(得分:0)

这是一个天真的foreach实现,其中整个数据集最终在内存中(因为你GroupBy可能不是问题):

public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
    var result = new List<List<string>>();
    foreach (var value in values)
    {
        var currentGroup = result.LastOrDefault();
        if (currentGroup?.FirstOrDefault()?.Equals(value) == true)
        {
            currentGroup.Add(value);
        }
        else
        {
            result.Add(new List<string> { value });
        }
    }

    return result;
}

这是一个稍微复杂的实现,foreachyield return枚举器状态机只保留内存中的当前组 - 这可能是在框架级别实现的:

编辑:这显然也是MoreLINQ的方式。

public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
    var currentValue = default(string);
    var group = (List<string>)null;

    foreach (var value in values)
    {
        if (group == null)
        {
            currentValue = value;
            group = new List<string> { value };
        }
        else if (currentValue.Equals(value))
        {
            group.Add(value);
        }
        else
        {
            yield return group;
            currentValue = value;
            group = new List<string> { value };
        }
    }

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

这是一个仅使用LINQ的笑话版本,它基本上与第一个版本相同,但稍微难以理解(特别是因为Aggregate不是最常用的LINQ方法):

public static IEnumerable<List<string>> Split(IEnumerable<string> values)
{
    return values.Aggregate(
        new List<List<string>>(),
        (lists, str) =>
        {
            var currentGroup = lists.LastOrDefault();
            if (currentGroup?.FirstOrDefault()?.Equals(str) == true)
            {
                currentGroup.Add(str);
            }
            else
            {
                lists.Add(new List<string> { str });
            }

            return lists;
        },
        lists => lists);
}

答案 3 :(得分:0)

使用基于APL扫描操作符的扩展方法,类似于Aggregate,但返回与源值配对的中间结果:

public static IEnumerable<KeyValuePair<TKey, T>> ScanPair<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<KeyValuePair<TKey, T>, T, TKey> combine) {
    using (var srce = src.GetEnumerator()) {
        if (srce.MoveNext()) {
            var prevkv = new KeyValuePair<TKey, T>(seedKey, srce.Current);

            while (srce.MoveNext()) {
                yield return prevkv;
                prevkv = new KeyValuePair<TKey, T>(combine(prevkv, srce.Current), srce.Current);
            }
            yield return prevkv;
        }
    }
}

您可以通过一致运行创建分组扩展方法:

public static IEnumerable<IGrouping<int, TResult>> GroupByRuns<TElement, TKey, TResult>(this IEnumerable<TElement> src, Func<TElement, TKey> key, Func<TElement, TResult> result, IEqualityComparer<TKey> cmp = null) {
    cmp = cmp ?? EqualityComparer<TKey>.Default;
    return src.ScanPair(0,
                        (kvp, cur) => cmp.Equals(key(kvp.Value), key(cur)) ? kvp.Key : kvp.Key + 1)
              .GroupBy(kvp => kvp.Key, kvp => result(kvp.Value));
}

public static IEnumerable<IGrouping<int, TElement>> GroupByRuns<TElement, TKey>(this IEnumerable<TElement> src, Func<TElement, TKey> key) => src.GroupByRuns(key, e => e);
public static IEnumerable<IGrouping<int, TElement>> GroupByRuns<TElement>(this IEnumerable<TElement> src) => src.GroupByRuns(e => e, e => e);

public static IEnumerable<IEnumerable<TResult>> Runs<TElement, TKey, TResult>(this IEnumerable<TElement> src, Func<TElement, TKey> key, Func<TElement, TResult> result, IEqualityComparer<TKey> cmp = null) =>
    src.GroupByRuns(key, result).Select(g => g.Select(s => s));
public static IEnumerable<IEnumerable<TElement>> Runs<TElement, TKey>(this IEnumerable<TElement> src, Func<TElement, TKey> key) => src.Runs(key, e => e);
public static IEnumerable<IEnumerable<TElement>> Runs<TElement>(this IEnumerable<TElement> src) => src.Runs(e => e, e => e);

使用最简单的版本,您可以获得IEnumerable<IGrouping>>

var ans1 = src.GroupByRuns();

或者为IGrouping转储Key(及其IEnumerable)的版本:

var ans2 = src.Runs();