如何订购无序序列

时间:2015-03-12 17:35:38

标签: c# linq

考虑这个元素列表,它有三个属性,值和序列号和组:

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中,数据的顺序很重要。

3 个答案:

答案 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);
}