考虑这个元素列表,它有三个属性,值和序列号和组:
Value Sequence Number Group
Header, 1, null
Invoice, 2, 1
Invoice Line, 3, 1
Trailer, 4, null
目标只是按序列号排序。价值无关紧要。
在上面,明显的答案是按序列号排序。
但是,元素可以重复:
Header, 1, null
InvoiceA, 2, 1
Line Item, 3, 1
InvoiceB, 2, 2
Line Item, 3, 2
Trailer, 4, null
以上是所需的序列。 Linq的声明会产生什么?
按顺序排序不再有效。按组排序,然后序列不起作用。
这个应用程序在EDI中,数据的顺序很重要。
答案 0 :(得分:2)
所以第一个"技巧"这里是您希望null
组中的所有项目都是单独的组,而不是将所有null
个项目组合到一个组中。
这实际上相当容易。我们可以创建一个IEqualityComparer
来比较基于其他比较器的项目,但总是认为两个null
项目不同,而不是相同(通常两个null
项目将是考虑"平等")。
public class SeparateNullComparer<T> : IEqualityComparer<T>
{
private IEqualityComparer<T> comparer;
public SeparateNullComparer(IEqualityComparer<T> comparer = null)
{
this.comparer = comparer ?? EqualityComparer<T>.Default;
}
public bool Equals(T x, T y)
{
if (x == null || y == null)
return false;
return comparer.Equals(x, y);
}
public int GetHashCode(T obj)
{
return comparer.GetHashCode(obj);
}
}
我们现在可以使用此比较器对项目进行分组,以便将所有非空项目组合在一起,而所有null
项目都将拥有自己的组。
现在我们如何订购这些团体?我们需要根据它们的序列号对这些组进行排序,但是我们有一个序列,而不仅仅是一个,因此我们需要一种比较两个序列的方法来查看哪个序列首先出现。我们通过检查每个序列中的第一个项目来执行此操作,然后继续检查下一个项目,直到一个项目首先或一个结束,另一个不结束:
public class SequenceComparer<T> : IComparer<IEnumerable<T>>
{
private IComparer<T> comparer;
public SequenceComparer(IComparer<T> compareer = null)
{
this.comparer = comparer ?? Comparer<T>.Default;
}
public int Compare(IEnumerable<T> x, IEnumerable<T> y)
{
using (var first = x.GetEnumerator())
using (var second = x.GetEnumerator())
{
while (true)
{
var firstHasMore = first.MoveNext();
var secondHasMore = second.MoveNext();
if (!firstHasMore && !secondHasMore)
return 0;
var lengthComparison = firstHasMore.CompareTo(secondHasMore);
if (lengthComparison != 0)
return lengthComparison;
var nextComparison = comparer.Compare(first.Current, second.Current);
if (nextComparison != 0)
return nextComparison;
}
}
}
}
在我们完成任务时,将所有组合展平,并且我们只需将它们放在一起就可以了:
var query = data.GroupBy(item => item.Group, new SeparateNullComparer<int?>())
.Select(group => group.OrderBy(item => item.SequenceNumber)
.ToList())
.OrderBy(group => group, new SequenceComparer<Foo>())
.ThenBy(group => group.First().Group)
.SelectMany(x => x);
您还可以依赖GroupBy
维护组内项目的原始顺序这一事实,允许您在分组之前按SequenceNumber
排序数据,而不是之后。它基本上会做同样的事情。事实证明这是一个更漂亮的查询,但你只需要知道&#34; GroupBy
保持正确的排序:
var query = data.OrderBy(item => item.SequenceNumber)
.GroupBy(item => item.Group, new SeparateNullComparer<int?>())
.OrderBy(group => group, new SequenceComparer<Foo>())
.ThenBy(group => group.Key)
.SelectMany(x => x);
答案 1 :(得分:1)
如果它不必是一个linq查询,你可以编写一个如下所示的比较器:
public class ValSeqGroupComparer : IComparer<ValSeqGroup>
{
public int Compare(ValSeqGroup x, ValSeqGroup y)
{
if (x == y) return 0;
// If only one has a group or there is no group in either
if (x.Group.HasValue ^ y.Group.HasValue || !x.Group.HasValue)
return x.Seq.CompareTo(y.Seq);
if (x.Group.Value != y.Group.Value)
return x.Group.Value.CompareTo(y.Group.Value);
return x.Seq.CompareTo(y.Seq);
}
}
然后像这样使用它:
[TestMethod]
public void One()
{
List<ValSeqGroup> items = new List<ValSeqGroup>()
{
new ValSeqGroup("x", 1, null),
new ValSeqGroup("x", 4, null),
new ValSeqGroup("x", 2, 1),
new ValSeqGroup("x", 2, 2),
new ValSeqGroup("x", 3, 1),
new ValSeqGroup("x", 3, 2)
};
items.Sort(new ValSeqGroupComparer());
foreach (var item in items)
{
Console.WriteLine("{0} {1} {2}", item.Value, item.Seq,item.Group);
}
}
答案 2 :(得分:1)
您可以通过按序列号(OrderBy(x => x.SequenceNumber)
)对元素进行排序来实现此目的。
之后,您可以使用现有的组号(.Where(x => x.Group != null).OrderBy(x => x.Group)
)
最后,您必须在相应索引处的列表中插入空元素。
var elements = new List<Element>
{
new Element{SequenceNumber = 1, Group = null}, new Element{SequenceNumber = 4, Group = null},new Element{SequenceNumber = 3, Group = 1},new Element{SequenceNumber = 3, Group = 3}, new Element{SequenceNumber = 3, Group = 2},new Element{SequenceNumber = 2, Group = 3},new Element{SequenceNumber = 2, Group = 1},new Element{SequenceNumber = 2, Group = 2}
};
// first sort
var sortedElements = elements.OrderBy(x => x.SequenceNumber).ToList();
// save null elements
var elementsWithNull = sortedElements
.Where(x => x.Group == null).ToList();
// group sorting
sortedElements = sortedElements
.Where(x => x.Group != null)
.OrderBy(x => x.Group).ToList();
// insert elements with null in sorted list
foreach (var element in elementsWithNull)
{
var firstIndexOfSequence = 0;
for (firstIndexOfSequence = 0;firstIndexOfSequence < sortedElements.Count && sortedElements[firstIndexOfSequence].SequenceNumber >= element.SequenceNumber; firstIndexOfSequence++)
{
// just to get index of the element with null group to know where to insert
}
sortedElements.Insert(firstIndexOfSequence, element);
}