简体   繁体   English

如何订购乱序

[英]How to Order an out of sequence sequence

Consider this list of elements which has three properties, Value and Sequence Number and Group: 考虑以下具有三个属性的元素列表:“值”,“序列号”和“组”:

Value    Sequence Number   Group   

Header,       1,           null
Invoice,      2,           1
Invoice Line, 3,           1
Trailer,      4,           null

The goal is only to sort by the sequence number. 目的只是按序列号排序。 The value is irrelevant. 该值无关紧要。

In the above, the obvious answer is to order by sequence number. 在上面,明显的答案是按序列号排序。

However, elements can repeat: 但是,元素可以重复:

Header,     1,   null
InvoiceA,   2,   1
Line Item,  3,   1
InvoiceB,   2,   2
Line Item,  3,   2
Trailer,  4,   null

The above is the desired sequence. 以上是所需的顺序。 What Linq statement will produce the above? 什么Linq语句将产生以上结果?

Sorting by Sequence no longer works. 按序列排序不再起作用。 Sorting by Group, then Sequence does not work. 按组排序,则序列不起作用。

The application of this is in EDI where the order of the data is significant. 此方法在数据顺序重要的EDI中使用。

So the first "trick" here is that you want all items with a null group to be separate groups, rather than having all null items combined into a single group. 因此,这里的第一个“技巧”是,您希望将具有null组的所有项目都设为单独的组,而不是将所有null项目组合为一个组。

This is actually fairly easy. 这实际上很容易。 We can just create an IEqualityComparer that compares items based on some other comparer, but that always considers two null items to be different, instead of being the same (typically two null items would be considered "equal"). 我们可以创建一个IEqualityComparer ,该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);
    }
}

We can now group the items using this comparer so that all non-null items will be grouped together, whereas all of the null items will have their own groups. 现在,我们可以使用此比较器对项目进行分组,以便将所有非空项目归为一组,而所有null项目都将具有自己的组。

Now how do we order the groups? 现在我们如何订购组? We need to order these groups based on their sequence numbers, but we have a sequence of them, not just one, so we need a way of comparing two sequences to see which sequence comes first. 我们需要根据它们的序列号对这些组进行排序,但是我们有一个序列,而不仅仅是一个序列,因此我们需要一种比较两个序列以查看哪个序列先出现的方法。 We do this by checking the first item in each sequence, and then continually checking the next until one comes first or one ends and the other doesn't: 我们通过检查每个序列中的第一项来执行此操作,然后连续检查下一项直到一个出现在第一个或一个结束而另一个没有出现:

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;
            }
        }
    }
}

Combine that with flattening all of the groups back out when we're done, and we just need to put it all together: 完成后,将其与平整所有组放在一起,我们只需要将它们放在一起:

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);

You can also rely on the fact that GroupBy maintains the original order of items within groups, allowing you to order the data by SequenceNumber before grouping, instead of after. 您还可以依靠GroupBy维护组内项目的原始顺序这一事实,允许您在分组之前而不是之后通过SequenceNumber对数据进行SequenceNumber It'll do basically the same thing. 基本上会做同样的事情。 It turns out to be a prettier query, but you just need to "know" that GroupBy maintains the proper ordering: 事实证明这是一个更漂亮的查询,但是您只需要“知道” 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);

If it doesn't have to be a linq query, you could write a single comparer that looks like this: 如果不必是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);
    }
}

Then using it like this: 然后像这样使用它:

[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);
    }
}

You can achieve this by sorting the elements by Sequence Number ( OrderBy(x => x.SequenceNumber) ). 您可以通过按序列号( OrderBy(x => x.SequenceNumber) )对元素进行排序来实现此OrderBy(x => x.SequenceNumber)

After that you can sort elements with exising group number ( .Where(x => x.Group != null).OrderBy(x => x.Group) ) 之后,您可以使用存在的组号对元素进行排序( .Where(x => x.Group != null).OrderBy(x => x.Group)

In the end you have to insert null elements in list at the corresponding index. 最后,您必须在列表中的相应索引处插入空元素。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM