1. 程式人生 > >.Net中集合排序還可以這麽玩

.Net中集合排序還可以這麽玩

true ora 表達式 str nbsp static gen AS pan

背景:

public class StockQuantity
    {
        public StockQuantity(string status, DateTime dateTime, int quantity)
        {
            Status = status;
            DateTime = dateTime;
            Quantity = quantity;
        }

        public string Status { get; set; }
        public
DateTime DateTime { get; set; } public int Quantity { get; set; } }


該對象,主要有三個字段,現在的業務需求是,取到了一個類型為List<StockQuantity>集合StockQuantities,需要對該集合進行三次排序,排序規則及優先級如下:
1. Status為空的排在後面,不為空的排在前面,不關心Status的內容,只關心Status是否為空。
2. DateTime升序排序。
3. Quantity升序排序。

小白我的做法:

我只知道可以對集合用OderBy排序,對以上三條規則,所以設計思路如下。

1. StockQuantities.OrderBy(u=>u.Status)
錯誤,
該排序得規則不僅僅會考慮Status是否為空,還會考慮Status的內容。
如果Status是[“b”,”c”,null,”d”],那麽排序結果是[null,“b”,”c”,”d”]。
而我們要的結果是[“b”,”c”,”d” ,null] (直接把null的丟到最後,別的不動)
怎麽辦?

暫時不知道,先不管

2. 對DateTime進行升序排序,這簡單
StockQuantities.OrderBy(u=>u.DateTime)
半對!
為什麽半對,看下面

3. 在排序2的前提下,用OrderBy,也就是StockQuantities.OrderBy(u=>u.DateTime).OrderBy(u=>u.Quantity)
錯誤!
以上表達式等同於下面兩條的表達式:

StockQuantities = StockQuantities.OrderBy(u=>u.DateTime)
StockQuantities = StockQuantities.OrderBy(u=>u.Quantity)

所以第一條代碼就是廢代碼,最終排序還是以Quantity進行排序的。
雖然我是小白,但我還是明白這樣是錯誤的,所以我的做法是

stockQuantities = stockQuantities.OrderBy(u => u.DateTime).ToList();

            foreach (var dateOrder in stockQuantities)
            {
                var datetimeOrderBy = stockQuantities.Where(u => u.DateTime.Date == dateOrder.DateTime.Date) .OrderBy(u => u.Count);

                foreach (var countOrder in datetimeOrderBy)
                {
                    if (countOrder.OutPut == false)
                    {
                        Console.WriteLine($"{countOrder.Status}-{countOrder.DateTime}-{countOrder.Count}");
                        countOrder.OutPut = true;
                    }
                    
                }
            }
            Console.ReadKey();


采用雙層循環,先取到按時間排序的數據 dateOrder,再去和該條數據在同一天的所有數據並對Quantity進行排序,為了防止重復的輸出,我同時給StockQuantity對象加上了Output屬性,當該屬性為false為,則輸出該對象的內容,並把Output屬性設為true,這樣就不會重復輸出了,而且實現了先對DateTime排序,再對Quantity進行排序。
So Easy!!
然而,當開心地把這樣的代碼提交之後,卻被同事狠狠地鄙視了,說到:“什麽爛代碼啊!”然道還有比這更好的代碼?

給同事倒了一杯茶,點了一根煙,虛心請教。

大佬做法:


同事給我講了兩招,分別是條件排序、多級排序。

什麽是條件排序,怎麽用?

1. StockQuantities.OrderBy(u=>u.Status==null)
這就是條件排序,可是咋一看,給人一種是把Status為空的排前面,不為空的排後面的錯覺。
其實不然,我們看到OrderBy裏面的一個返回值為bool類型的表達式,該排序先排結果為0(false)的,再排結果為1(true)的。這種排序只考慮返回的bool值,不考慮參數的具體值,所以姑且稱它為條件排序。
完全符合排序規則1的要求。

什麽是多級排序,怎麽用?


2. 利用我上面我的代碼排序雖然可以實現先排DateTime,再排Quantity,但是該算法的時間復雜度的n*n,而且給StockQuantity添加了output字段,明顯是不科學的。
然而,連續地使用多個OrderBy最終只會生效最後一個OrderBy,天無絕人之路,所以這個時候應該使用ThenBy!!
使用ThenBy可以講以上的三條排序規則簡化如下:
stockQuantities = stockQuantities.OrderBy(u => u.Status==null).ThenBy(u => u.DateTime).ThenBy(u => u.Quantity).ToList();
即可完美地實現再前一個排序前提下進行二級排序。

優化後的完整代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;

namespace OrderBy
{
    class Program
    {
        static void Main(string[] args)
        {
            var stockQuantities = new List<StockQuantity>()
            {
                new StockQuantity("正常品",new DateTime(2017,4,16),12 ),
                new StockQuantity("正常品",new DateTime(2017,4,17),15 ),
                new StockQuantity("殘次品",new DateTime(2017,4,16),10 ),
                new StockQuantity("殘次品",new DateTime(2017,4,17),8 ),
                new StockQuantity(null,new DateTime(2017,4,18),8 ),
            };

            stockQuantities = stockQuantities.OrderBy(u => u.Status==null).ThenBy(u => u.DateTime).ThenBy(u => u.Quantity).ToList();

            foreach (var stockQuantity in stockQuantities)
            {
                Console.WriteLine($"{stockQuantity.Status}-{stockQuantity.DateTime}-{stockQuantity.Quantity}");
            }

            Console.ReadKey();
        }
    }

    public class StockQuantity
    {
        public StockQuantity(string status, DateTime dateTime, int quantity)
        {
            Status = status;
            DateTime = dateTime;
            Quantity = quantity;
        }

        public string Status { get; set; }
        public DateTime DateTime { get; set; }
        public int Quantity { get; set; }

    }
}

簡單的一個排序優化,就把程序的時間復雜度從N*N降低到了N,所以在這裏把這兩種排序技巧分享出來,希望對不會的同學有所幫助。

.Net中集合排序還可以這麽玩