简体   繁体   English

C# LINQ 来自另一个列表的 OrderBy 值(IEnumerable)

[英]C# LINQ OrderBy values from another List(IEnumerable)

Let's imagine we have a list of strings var listA = new List<string> {"B", "C", "A"} , and it is sorted in the desired manner.假设我们有一个字符串列表var listA = new List<string> {"B", "C", "A"} ,并且它以所需的方式排序。 And, there is an object defined like:并且,有一个 object 定义如下:

public class Sample {
   public string Letter {get;set;} // "A","B","C","D","#"
   public string Number {get;set;} // not relevant
}

Also, there is another list, List<Sample> listB , and I want to sort it in a way that first comes objects with Letter values "B", then "C" and on the end, "A", so to respect same order as it is in the listA.此外,还有另一个列表List<Sample> listB ,我想对它进行排序,首先是字母值为“B”的对象,然后是“C”,最后是“A”,所以尊重相同按照 listA 中的顺序排列。 Any ideas?有任何想法吗? :) :)

EDIT: Letter property can have a wider range of characters, unlike listA which can have just those 3. Expected order "B","C", "A"..."all the rest, order not relevant"编辑:字母属性可以有更广泛的字符,不像 listA 只能有那些 3. 预期顺序“B”,“C”,“A”......“所有 rest,顺序不相关”

Assume value of Letter are within listA .假设Letter的值在listA内。 It's worth considering redesigning the algorithm if the assumption is false.如果假设不成立,则值得考虑重新设计算法。 And it may depend on requirement of expected order the out of range ones.并且它可能取决于超出范围的预期订单的要求。

   var sortedSamples = listB.OrderBy(x=>listA.IndexOf(x.Letter));

For better performance, cache the orders into a dictionary:为了获得更好的性能,将订单缓存到字典中:

   var orders = new Dictionary<string, int>();
   for (int i = 0; i < listA.Count; i++)
   {
      orders.Add(listA[i], i);
   }
   var sortedSamples = listB.OrderBy(x=>orders[x.Letter]);

If possible values range of Letter is much smaller than listA , then better consturct orders like this:如果Letter的可能值范围比listA ,那么最好像这样构造orders

   foreach (string letter in listB.Select(x=>x.Letter))
   {
      orders.Add(letter, listA.IndexOf(letter));
   }

Update更新

If letters ' range are out of listA , and since OP want the out ones to be at the end:如果letters的范围超出listA ,并且由于 OP 希望 out 的位于末尾:

   var orders = new Dictionary<string, int>();
   for (int i = 0; i < listA.Count; i++)
   {
      orders.Add(listA[i], i);
   }
   int lastIndex = listA.Count;
   foreach (string letter in listB.Select(x=>x.Letter)
                                  .Distinct().Except(listA))
   {
      orders.Add(letter, lastIndex);
   }

Here is a specific implementation of the method body of @nannanas's answer , which also works when listA does not contain all letters present in listB :这是@nannanas answer的方法体的具体实现,当listA不包含listB中存在的所有字母时,它也有效:

listB = listA.Union(listB.Select(entry => entry.Letter))
    .Join(listB,
         letter => letter,
         sample => sample.Letter,
         ( _, sample ) => sample)
    .ToList();

Example fiddle here .示例小提琴在这里


Walk-through演练

Let's say we have the following lists:假设我们有以下列表:

var listA = new List<string> { "B", "C", "A" };

var listB = new List<Sample>
{
    new Sample { Letter = "E", Number = "1" },
    new Sample { Letter = "C", Number = "2" },
    new Sample { Letter = "A", Number = "3" },
    new Sample { Letter = "B", Number = "4" },
    new Sample { Letter = "D", Number = "5" }
};

The first line:第一行:

listB.Select(entry => entry.Letter) will extract each Letter value in listB , resulting in: listB.Select(entry => entry.Letter)将提取listB中的每个Letter值,导致:

{ "E", "C", "A", "B", "D" } { "E", "C", "A", "B", "D" }

listA.Union(listB.Select(entry => entry.Letter)) uses .Union() to create the set union of listA and listB 's Letter values by first yielding each distinct item in the set of listA ( "B", "C", "A" ), then yielding each distinct item in the set of Letter s from listB that are not present in listA ( "E", "D" ); listA.Union(listB.Select(entry => entry.Letter))使用.Union()创建listAlistBLetter值的集合并集,方法是首先生成listA集合中的每个不同项目( "B", "C", "A" ), 然后从listB中产生Letter集合中的每个不同的项目,这些项目不存在于listA ( "E", "D" ) 中; resulting in:导致:

{ "B", "C", "A", "E", "D" } {“B”,“C”,“A”,“E”,“D”}

We now have the order of the letters that we want listB to reflect: First, the order given by listA , then, the remaning letters present in listB .我们现在有了希望listB反映的字母顺序:首先是listA给出的顺序,然后是listB中出现的剩余字母。

The.Join() operation .Join()操作

.Join() associates an outer sequence with an inner sequence based on association keys defined by a key selector for each sequence ; .Join()根据每个序列键选择器定义的关联键将外部序列内部序列相关联; and then returns an IEnumerable<T> from the associations based on the result selector :然后根据结果选择器从关联中返回一个IEnumerable<T>

outerSequence.Join(innerSequence,
    outerItem => /*  */, // key selector (outerItem is a string)
    innerItem => /*  */, // key selector (innerItem is a Sample)
    ( associatedOuterItem, associatedInnerItem ) => /*  */) // result selector

{ "B", "C", "A", "E", "D" } is thereby the outer sequence in the .Join() operation. { "B", "C", "A", "E", "D" }因此是.Join()操作中的外部序列 The inner sequence of the .Join() operation is listB . .Join()操作的内部序列listB By using .Join() , we can associate items in the outer sequence with items in the inner sequence by the sequences' matching key selectors .通过使用.Join() ,我们可以通过序列的匹配键选择器外部序列中的项目与内部序列中的项目相关联。

For the outer sequence , we define the key selector as being the whole item:对于外部序列,我们将键选择器定义为整个项目:

letter => letter // alternatively: outerItem => outerItem

As an example, the two first items in the outer sequence are "B" and "C" ;例如,外部序列中的前两个项目是"B""C" so, the association key for the first two items in the outer sequence are "B" and "C" .因此,外部序列中前两项的关联键是"B""C"

For the inner sequence , we define the key selector as being the Letter property of the Sample object:对于内部序列,我们将键选择器定义为Sample object 的Letter属性:

sample => sample.Letter // alternatively: innerItem => innerItem.Letter

As an example, the two first items in the inner sequence are Sample { Letter = "E", Number = "1" } and Sample { Letter = "C", Number = "2" } ;例如,内部序列中的前两个项目是Sample { Letter = "E", Number = "1" }Sample { Letter = "C", Number = "2" } ; so, the association key for the first two items in the inner sequence are each item's Letter value: "E" and "C" .因此,内部序列中前两个项目的关联键是每个项目的Letter值: "E""C"

Hence, the full set of key selectors for the outer and inner sequences are, respectively:因此,外部内部序列的全套密钥选择器分别是:

{ "B", "C", "A", "E", "D" } {“B”,“C”,“A”,“E”,“D”}
{ "E", "C", "A", "B", "D" } { "E", "C", "A", "B", "D" }

The .Join() operation now associates the items in each sequence based on the key selectors . .Join()操作现在根据键选择器关联每个序列中的项目。 The associations keep the order provided by the outer sequence , and can be illustrated as follows:关联保持外部序列提供的顺序,并且可以说明如下:

Item from outer sequence来自外部序列的项目 Item from inner sequence来自内部序列的项目
"B" Sample { Letter = "B", Number = "4" }
"C" Sample { Letter = "C", Number = "2" }
"A" Sample { Letter = "A", Number = "3" }
"E" Sample { Letter = "E", Number = "1" }
"D" Sample { Letter = "D", Number = "5" }

The last .Join() input parameter is the result selector , which defines what should be returned from each association.最后一个.Join()输入参数是结果选择器,它定义了每个关联应返回的内容。 Seeing as we only need the item from the inner sequence , we specify that in the result selector :鉴于我们只需要内部序列中的项目,我们在结果选择器中指定:

( _, sample ) => sample

(The result selector could have been rewritten to eg ( letter, sample ) => sample , but seeing as the items from the outer sequence (referred to as letter ) are not of interest, I have simply discarded ( _ ) that object in my example.) 结果选择器可以重写为例如( letter, sample ) => sample ,但是由于外部序列(称为letter )中的项目不感兴趣,我只是简单地丢弃了 ( _ ) 我的 object例子。)

You should using something like this你应该使用这样的东西

            var listA = new List<string> { "B", "C", "A" };
            var listB = new List<string> { "A", "B", "C" };
            listB = listB.OrderBy(d => listA.IndexOf(d)).ToList();
            // listB: "B", "C", "A"

        

You could use this extension which also supports comparing the entries in the lists by a specific property (eg id instead of reference)您可以使用此扩展,它还支持通过特定属性(例如 id 而不是引用)比较列表中的条目

public static IEnumerable<TFirst> OrderBy<TFirst, TSecond, TKey>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TKey> firstKeySelector, Func<TSecond, TKey> secondKeySelector) =>
            second
                .Join(
                    first,
                    secondKeySelector,
                    firstKeySelector,
                    (_, firstItem) => firstItem);

this will sort the items in second the same way as they are already sorted in first comparing them using both key selectors.这将在second项目中对项目进行排序,就像在first使用两个键选择器比较它们时已经对它们进行排序一样。

Be aware that this only works if ALL the keys in first are also present in second .请注意,这仅在first中的所有键也存在于second中时才有效。

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

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