是否可以根据序列对LINQ元素进行分组?

时间:2013-10-15 14:19:28

标签: c# linq

我一直在想;在我工作的公司,我们管理大量数据,但由于它是由客户有效地提供给我们的,我们并不一定信任它 - 有充分的理由。很多都有错误的时间戳,或者有些时间戳丢失了,或者其他任何有你的时间戳。

我最近必须完成的任务之一基本上是在一组元素中找到null的元素,然后找到下一个非null元素,然后平均这些空记录之间的差异。也就是说,我们有数据集A:

A = { 0f, 1f, 2f, 5f, Null, Null, Null, 7f, Null, 8f }

重要的是要注意我们必须区分0和Null。差别很明显是0是0,而Null根本就没有数据。

使用LINQ,我们可以基本上访问A的以下小节:

Subsection { Null, Null, Null, 7f }

并将它放在一个集合中,以便我们可以将它转换为(7 / 4f)四个记录。

Subsection { 1.75f, 1.75f, 1.75f, 1.75f }

这样当再次迭代A时,我们得到以下输出:

{ 0f, 1f, 2f, 5f, 1.75f, 1.75f, 1.75f, 1.75f, 4f, 4f }

目前我这样做的方法是使用数字for进行传递,查找null元素,然后将所有连续的空值存储在List<T>中,并在找到下一个非null后,通过以下方式分配所有变量:迭代说List<T>。它完成了这项工作,但看起来很糟糕。

所以,为了narcicissim,有没有办法整齐地做这个(=更少的代码混乱)?

a = { 0, 1, 2, 5, null, null, null, 7, null, 0 }


nullList = new List()
for i = 0, a.length
    if i == null
        nullList.add(i)
    else
        if nullList.length > 0
            nullList.add(i)
            int avg = nullList.Aggregate(x => x)
            foreach element in nullList
                element = avg
            nullList.clear()

6 个答案:

答案 0 :(得分:2)

如果我正确理解了您的问题,您希望使用基于第一个非null值的值替换列表中的null值。我不明白为什么你需要第二个null列表。这是尝试只是就地修改列表,虽然它没有你已经拥有的那么短:

var A = new List<float?> { 0f, 1f, 2f, 5f, null, null, null, 7f, null, 8f };

for (int i = A.IndexOf(null); i != -1; i = A.IndexOf(null, i))
{
    int j = 0;
    do { j++; } while (A[i + j] == null);
    float f = A[i + j].Value / (j + 1);
    do { A[i++] = f; } while (j --> 0);
}

// A == { 0f, 1f, 2f, 5f, 1.75f, 1.75f, 1.75f, 1.75f, 4f, 4f }

代码重复搜索列表中的null s(在之前发现null时继续停止的位置),计算彼此相邻的null个数,然后在差距中分配第一个非null值。该代码假定每个间隙后总是存在非null值。

正如许多评论所指出的,LINQ的使用在这里没有提供任何真正的优势。

答案 1 :(得分:2)

首先,我们将使用名为GroupWhile的辅助方法。它将采用一个序列和一个函数;该函数将被赋予前一项和当前项,并基于此将确定当前项应该是新组的一部分还是前一组的一部分。它允许我们在满足某些条件时对项目进行分组:

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(
    this IEnumerable<T> source, Func<T, T, bool> predicate)
{
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            yield break;

        List<T> list = new List<T>() { iterator.Current };

        T previous = iterator.Current;

        while (iterator.MoveNext())
        {
            if (predicate(previous, iterator.Current))
            {
                list.Add(iterator.Current);
            }
            else
            {
                yield return list;
                list = new List<T>() { iterator.Current };
            }

            previous = iterator.Current;
        }
        yield return list;
    }
}

使用此功能,我们可以在前一项为空时对项目进行分组。然后,我们将每个组重复该组的平均值group.Count()次,然后再次将序列展平:

public static IEnumerable<float> ConsolodateNulls<T>(IEnumerable<float?> source)
    where T : struct
{
    return source.GroupWhile((prev, curr) => prev == null)
        .SelectMany(group => Enumerable.Repeat(
            group.LastOrDefault(item => item != null) ?? 0 / group.Count(),
            group.Count()));
}

答案 2 :(得分:1)

您可以创建执行此操作的扩展方法。然后可以在普通的LINQ语句中使用此扩展方法:

public static IEnumerable<float> SmoothGaps(this IEnumerable<float?> source)
{
    int numberOfNulls = 0;
    foreach(var item in source)
    {
        if(item == null)
        {
            ++numberOfNulls;
        }
        else
        {
            if(numberOfNulls != 0)
            {
                for(int i=0; i <= numberOfNulls; ++i)
                    yield return item.Value / (numberOfNulls + 1);
            }
            else
                yield return item.Value;
            numberOfNulls = 0;
        }
    }
}

用法很简单:

var result = a.SmoothGaps();
<{1}}末尾的

null将被删除。

答案 3 :(得分:1)

以下是如何在LINQ中完成它:

var data = new List<float?> { 0f, 1f, 2f, 5f, null, null, null, 7f, null, 8f };
var corrected = data
    .Select((v,i) => new {
        Index = i
        // Find the index of the next non-null item in the list
    ,   NextNonNull = i + data
            .Skip(i)
            .Select((vv,j) => new {j,vv})
            .First(p => p.vv.HasValue).j
    ,   Value = v
    })
    .GroupBy(p => p.NextNonNull)
    // For each group, insert its average g.Count() times
    .SelectMany(g => g.Select(e => data[g.Key]/g.Count()))
    .ToList();
for (var i = 0 ; i != data.Count ; i++ ) {
    Console.WriteLine("{0} - {1}", data[i], corrected[i]);
}

免责声明:此解决方案仅供娱乐之用。它会比基于for循环的解决方案慢,可能会为复杂性添加额外的顺序(即使其O(n^2)而不是O(n))。

答案 4 :(得分:1)

使用Aggregate进行娱乐的纯LINQ版本:

float?[] A = { 0f, 1f, 2f, 5f, null, null, null, 7f, null, 8f };
var result = A.Aggregate(Tuple.Create(new List<float>(), 0), 
 (items, current) => 
 {
    if(current.HasValue)
    {
        if(items.Item2 == 0)
            items.Item1.Add(current.Value);
        else
        {
            var avg = current.Value / (items.Item2 + 1);
            for(int i = 0; i <= items.Item2; i++)
                items.Item1.Add(avg);
        }
        return Tuple.Create(items.Item1, 0);
    }
    else
        return Tuple.Create(items.Item1, items.Item2 + 1);
 }).Item1;

我不会在生产代码中使用它,因为普通开发人员的负责人会在Aggregate上爆炸,在C#中使用Tuple总是看起来有点难看,而且必要的解决方案效果很好而且更容易理解比这个。

答案 5 :(得分:0)

我不认为可以使用纯linq(现有的linq方法)来完成,但我会编写一个迭代器来执行此操作:

public IEnumerable<float?> ProcessSequence(IEnumerable<float?> seq)
{
int nullCount = 0;
foreach(var x in seq)
{
    if (x == null)
    {
        nullCount++;
    }
    else if (nullCount > 0)
    {
        nullCount++;
        var mid = x / nullCount;
        for (var i = 0; i<nullCount; i++)
        {
            yield return mid;
        }
        nullCount = 0;
    }       
    else
    {
        yield return x;
    }
}
}