我正在寻找一些快速有效的方法来合并数组中的项目。这是我的情景。该集合按From排序。相邻元素不一定相差1,即最后一个To和下一个From之间可能存在间隙,但它们从不重叠。
var list = new List<Range>();
list.Add(new Range() { From = 0, To = 1, Category = "AB" });
list.Add(new Range() { From = 2, To = 3, Category = "AB" });
list.Add(new Range() { From = 4, To = 5, Category = "AB" });
list.Add(new Range() { From = 6, To = 8, Category = "CD" });
list.Add(new Range() { From = 9, To = 11, Category = "AB" }); // 12 is missing, this is ok
list.Add(new Range() { From = 13, To = 15, Category = "AB" });
我希望上面的集合以这样的方式合并:前三个(这个数字可以变化,从至少2个元素变化到满足条件的数量)元素成为一个元素。无法合并具有不同类别的元素。
new Range() { From = 0, To = 5, Category = "AB" };
这样得到的集合总共有4个元素。
0 - 5 AB
6 - 8 CD
9 - 11 AB // no merging here, 12 is missing
13 - 15 AB
我有一个非常大的集合,有超过2.000.000项目,我希望尽可能高效。
答案 0 :(得分:5)
这是一个通用的,可重复使用的解决方案,而不是特定的特定解决方案。 (根据评论更新)
IEnumerable<T> Merge<T>(this IEnumerable<T> coll,
Func<T,T,bool> canBeMerged, Func<T,T,T>mergeItems)
{
using(IEnumerator<T> iter = col.GetEnumerator())
{
if (iter.MoveNext())
{
T lhs = iter.Current;
while(iter.MoveNext())
{
T rhs = iter.Current;
if (canBeMerged(lhs, rhs)
lhs=mergeItems(lhs, rhs);
else
{
yield return lhs;
lhs= rhs;
}
}
yield return lhs;
}
}
}
您必须提供方法来确定项目是否可以合并,以及合并它们。 这些应该是Range类的一部分,所以它会像它们一样被调用:
list.Merge((l,r)=> l.IsFollowedBy(r), (l,r)=> l.CombineWith(r));
如果您没有这些方法,那么您必须将其称为:
list.Merge((l,r)=> l.Category==r.Category && l.To +1 == r.From,
(l,r)=> new Range(){From = l.From, To=r.To, Category = l.Category});
答案 1 :(得分:2)
嗯,从问题陈述中我认为很明显你无法避免遍历200万件物品的原始集合:
var output = new List<Range>();
var currentFrom = list[0].From;
var currentTo = list[0].To;
var currentCategory = list[0].Category;
for (int i = 1; i < list.Count; i++)
{
var item = list[i];
if (item.Category == currentCategory && item.From == currentTo + 1)
currentTo = item.To;
else
{
output.Add(new Range { From = currentFrom, To = currentTo,
Category = currentCategory });
currentFrom = item.From;
currentTo = item.To;
currentCategory = item.Category;
}
}
output.Add(new Range { From = currentFrom, To = currentTo,
Category = currentCategory });
我有兴趣看看是否有更优化的性能解决方案。
编辑:我认为输入列表已排序。如果不是,我建议先将其排序,而不是试图将其转换为算法。排序只是O( n log n ),但是如果你试图将其搞砸,你很容易得到O( n ²),更糟糕的是。
list.Sort((a, b) => a.From < b.From ? -1 : a.From > b.From ? 1 : 0);
除此之外,我写了这个解决方案,因为你要求一个性能优化的解决方案。为此,我没有使它成为泛型,我没有使用委托,我没有使用Linq扩展方法,我使用了原始类型的局部变量,并试图尽可能避免访问对象字段。
答案 2 :(得分:1)
这是另一个:
IEnumerable<Range> Merge(IEnumerable<Range> input)
{
input = input.OrderBy(r => r.Category).ThenBy(r => r.From).ThenBy(r => r.To).ToArray();
var ignored = new HashSet<Range>();
foreach (Range r1 in input)
{
if (ignored.Contains(r1))
continue;
Range tmp = r1;
foreach (Range r2 in input)
{
if (tmp == r2 || ignored.Contains(r2))
continue;
Range merged;
if (TryMerge(tmp, r2, out merged))
{
tmp = merged;
ignored.Add(r1);
ignored.Add(r2);
}
}
yield return tmp;
}
}
bool TryMerge(Range r1, Range r2, out Range merged)
{
merged = null;
if (r1.Category != r2.Category)
return false;
if (r1.To + 1 < r2.From || r2.To + 1 < r1.From)
return false;
merged = new Range
{
From = Math.Min(r1.From, r2.From),
To = Math.Max(r1.To, r2.To),
Category = r1.Category
};
return true;
}
您可以直接使用它:
var mergedList = Merge(list);
但是,由于复杂度为O(n²),因此您有很多项目效率非常低。但是,由于只能合并同一类别中的项目,您可以按类别对它们进行分组并合并每个组,然后展平结果:
var mergedList = list.GroupBy(r => r.Category)
.Select(g => Merge(g))
.SelectMany(g => g);
答案 3 :(得分:0)
假设列表已排序 - 并且 - 范围不重叠,正如您在问题中所述,这将在O(n)时间内运行:
var flattenedRanges = new List<Range>{new Range(list.First())};
foreach (var range in list.Skip(1))
{
if (flattenedRanges.Last().To + 1 == range.From && flattenedRanges.Last().Category == range.Category)
flattenedRanges.Last().To = range.To;
else
flattenedRanges.Add(new Range(range));
}
假设您有Range
编辑: 这是一个就地算法:
for (int i = 1; i < list.Count(); i++)
{
if (list[i].From == list[i - 1].To+1 && list[i-1].Category == list[i].Category)
{
list[i - 1].To = list[i].To;
list.RemoveAt(i--);
}
}
编辑:
添加了类别检查,并修复了原位版本。