3

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

我最近不得不做的一个任务基本上是在一组元素中找到为空的元素,然后找到下一个非空元素,然后平均这些空记录之间的差异。也就是说,假设我们有数据集 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 元素,然后将所有连续的 null 存储在 a 中List<T>,并在找到下一个非 null 后,通过遍历 said 来分配所有变量List<T>。它可以完成工作,但看起来很讨厌。

那么,为了自恋,有没有办法巧妙地做到这一点(=减少代码混乱)?

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()
4

6 回答 6

2

如果我正确理解您的问题,您希望将null列表中的值替换为基于第一个非值的null值。我不明白你为什么需要第二个nulls 列表。这是一个尝试就地修改列表的尝试,尽管它并不比你已经拥有的短很多:

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 }

代码重复搜索列表中的nulls(从之前找到 a 时停止的地方继续null),计算null彼此相邻的 s 的数量,然后将第一个非null值分配到间隙中。该代码假定null在每个间隙之后总是有一个非值。

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

于 2013-10-15T14:30:43.713 回答
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()));
}
于 2013-10-15T14:37:21.953 回答
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();

null末尾的 ssource将被简单地删除。

于 2013-10-15T14:42:23.397 回答
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))。

于 2013-10-15T14:52:07.200 回答
1

纯 LINQ 版本Aggregate供您娱乐:

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;

我不会在生产代码中使用它,因为普通开发人员的头脑会爆炸AggregateTuple在 C# 中使用总是看起来有点难看,命令式解决方案效果很好,比这更容易理解。

于 2013-10-15T15:07:59.653 回答
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;
    }
}
}
于 2013-10-15T14:40:24.697 回答