我一直在想;在我工作的公司,我们管理大量数据,但由于它是由客户有效地提供给我们的,我们并不一定信任它 - 有充分的理由。很多都有错误的时间戳,或者有些时间戳丢失了,或者其他任何有你的时间戳。
我最近必须完成的任务之一基本上是在一组元素中找到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()
答案 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;
}
}
}