按照特定顺序排序列表

6

我有一个 List,并且我有一个用 int[] 表示的新顺序,我想要将 List 中的项目按照 int[] 中的顺序重新排列。这是我实现的代码:

   class Program
    {
        static void Main(string[] args)
        {
            List<Test> tests = new List<Test>() { 
                new Test(){ No = 201 },
                new Test(){ No = 101 },
                new Test(){ No = 300 },
                new Test(){ No = 401 },
                new Test(){ No = 500 },
                new Test(){ No = 601 }
            };


            int[] newOrder = new int[6] { 201, 401, 300, 101, 601, 500 };

            //after the opration the List should contain items in order 201, 401, 300, 101, 601, 500

            List<Test> newTests = new List<Test>();

            foreach(var order in newOrder)
            {
                var item = tests.SingleOrDefault(t => t.No == order);

                if (item != null)
                    newTests.Add(item);
            }


        }

    }

这个方法可以正常运行。但它会创建一个单独的List并对其执行操作。是否有更好的方法,可以使用 .Net 内置的操作或在同一个List上执行操作,而不需要创建这些临时List等?

谢谢。


请查看此链接:https://dev59.com/knA75IYBdhLWcg3w3NHf 或者您可以查看交集 - Kaushik
如果您想在同一列表中进行操作,则可以根据顺序数组的索引开始交换原始列表中的元素。没有默认机制。 - Mrinal Kamboj
你可以将数组和列表联合起来,然后使用无排序的 Select - Vahid
@ 未知用户,那样行不通,因为我们需要特定的比较逻辑来创建IComparer,在这里似乎没有这样的系统。 - Mrinal Kamboj
@MrinalKamboj 我认为交集可以解决这个问题。 - Kaushik
4个回答

9

在执行这样的排序时,您需要考虑性能。

如果您只期望有少量元素,则Pedro的解决方案是可以的。

如果您期望有许多元素(比如100个或1000个),那么每次在newOrder中搜索元素时遍历整个tests集合并不是一个好主意。在这种情况下,使用Dictionary进行所有索引/排序顺序查找将非常有帮助。尝试像这样做:

List<Test> tests = new List<Test>() { 
    new Test(){ No = 101 },
    new Test(){ No = 201 },
    new Test(){ No = 300 },
    new Test(){ No = 401 },
    new Test(){ No = 500 },
    new Test(){ No = 601 }
};


int[] newOrder = new int[6] { 201, 401, 300, 101, 601, 500 };

// Create a Dictionary/hashtable so we don't have to search in newOrder repeatedly
// It will look like this: { {201,0}, {401,1}, {300,2}, {101,3}, {601,4}, {500,5} }
Dictionary<int, int> newOrderIndexedMap = Enumerable.Range(0, newOrder.Length - 1).ToDictionary(r => newOrder[r], r => r);

// Order using 1 CPU
var orderedTests = tests.OrderBy(test => newOrderIndexedMap[test.No]);
// Order using multi-threading
var orderedInParallelTests = tests.AsParallel().OrderBy(test => newOrderIndexedMap[test.No]);

// Order using 1 CPU, when it's possible that a match will not be found in newOrder
var orderedTestsSafe = tests.OrderBy(test => 
    {
        int index;
        bool foundIndex = newOrderIndexedMap.TryGetValue(test.No, out index);
        return foundIndex ? index : Int32.MaxValue;
    });

请注意,本答案和Pedro的答案都假定newOrder包含在tests元素中包含的所有值,反之亦然。

您可能需要处理列表和数组值之间的不匹配,通过使用默认值将数字放在末尾。假设它们总是相同的可能在实际情况下失败,事实上,对于具有更多元素的列表,它会失败。 - Mrinal Kamboj
如果newOrder可能会缺少某些元素,那么@MrinalKamboj的建议是正确的。在这种情况下,您应该使用类似于orderedTestsSafe的东西。 - Serge
使用索引映射是很好的,但任何未匹配到新顺序列表的项目将按随机顺序排序,这是由于 Int32.MaxValue,但可能不是 OP 所需的。好的答案。 - Eric

2

使用左连接可以是使用自定义排序的方法之一。对于任何未匹配到自定义排序的项目,使用列表原始顺序从末尾开始。

var results = from a in tests.Select((r, i) => new {item = r, Index = i})
              // left join
              from b in newOrder.Select((r, i) => new { item = r, Index = i })
                                .Where(b => a.item.No == b.item).DefaultIfEmpty() 
              // Not in order list then use original ordering
              orderby (b == null ? tests.Count() + a.Index : b.Index) 
              select a.item;

.Net Fiddle


左外连接,就这样,它处理了所有场景。 - Mrinal Kamboj

2
var newTesties= newOrder.Select(o => tests.First(t => t.No == o));

基本上,我正在选择newOrder中的每个数字'o',并使用它来选取相应的测试。但你最终将得到一个新列表。


1

尝试使用以下方式将您的数组和列表连接起来:

newOrder.Join(tests, no => no, tst => tst.No, (no, tst) => tst)

非常好的解决方案,因为它通过连接运作。但是如果原始列表中包含不属于数组的数字,左外连接是否是更好的选择? - Mrinal Kamboj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接