C# Linq OrderBy 针对特定值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/728319/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Linq OrderBy against specific values
提问by Jonathan Parker
Is there a way in Linq to do an OrderBy against a set of values (strings in this case) without knowing the order of the values?
在 Linq 中有没有办法在不知道值的顺序的情况下对一组值(在这种情况下是字符串)执行 OrderBy?
Consider this data:
考虑这个数据:
A
B
A
C
B
C
D
E
And these variables:
而这些变量:
string firstPref, secondPref, thirdPref;
字符串 firstPref、secondPref、thirdPref;
When the values are set like so:
当值设置如下:
firstPref = 'A';
secondPref = 'B';
thirdPref = 'C';
Is it possible to order the data like so:
是否可以像这样对数据进行排序:
A
A
B
B
C
C
D
E
采纳答案by Daniel Brückner
If you put your preferences into a list, it might become easier.
如果你把你的偏好放在一个列表中,它可能会变得更容易。
List<String> data = new List<String> { "A","B","A","C","B","C","D","E" };
List<String> preferences = new List<String> { "A","B","C" };
IEnumerable<String> orderedData = data.OrderBy(
item => preferences.IndexOf(item));
This will put all items not appearing in preferences
in front because IndexOf()
returns -1
. An ad hoc work around might be reversing preferences
and order the result descending. This becomes quite ugly, but works.
这会将所有没有出现preferences
在前面的项目,因为IndexOf()
返回-1
。临时解决方法可能会反转preferences
并按降序排列结果。这变得很丑陋,但有效。
IEnumerable<String> orderedData = data.OrderByDescending(
item => Enumerable.Reverse(preferences).ToList().IndexOf(item));
The solution becomes a bit nicer if you concat preferences
and data
.
如果您连接preferences
和data
.
IEnumerable<String> orderedData = data.OrderBy(
item => preferences.Concat(data).ToList().IndexOf(item));
I don't like Concat()
and ToList()
in there. But for the moment I have no really good way around that. I am looking for a nice trick to turn the -1
of the first example into a big number.
我不喜欢Concat()
和ToList()
在那里。但目前我没有很好的方法来解决这个问题。我正在寻找一个很好的技巧来将-1
第一个例子的 变成一个大数字。
回答by Ben Hoffstein
Yes, you must implement your own IComparer<string>
and then pass it in as the second argument of LINQ's OrderBy method.
是的,您必须实现自己的IComparer<string>
,然后将其作为 LINQ 的 OrderBy 方法的第二个参数传入。
An example can be found here: Ordering LINQ results
可以在此处找到示例: 订购 LINQ 结果
回答by James
Danbrucs solution is more elegant, but here is a solution using a custom IComparer. This might be useful if you need more advanced conditions for your sort ordering.
Danbrucs 解决方案更优雅,但这里有一个使用自定义 IComparer 的解决方案。如果您需要更高级的排序条件,这可能很有用。
string[] svals = new string[] {"A", "B", "A", "C", "B", "C", "D", "E"};
List<string> list = svals.OrderBy(a => a, new CustomComparer()).ToList();
private class CustomComparer : IComparer<string>
{
private string firstPref = "A";
private string secondPref = "B";
private string thirdPref = "C";
public int Compare(string x, string y)
{
// first pref
if (y == firstPref && x == firstPref)
return 0;
else if (x == firstPref && y != firstPref)
return -1;
else if (y == firstPref && x != firstPref)
return 1;
// second pref
else if (y == secondPref && x == secondPref)
return 0;
else if (x == secondPref && y != secondPref)
return -1;
else if (y == secondPref && x != secondPref)
return 1;
// third pref
else if (y == thirdPref && x == thirdPref)
return 0;
else if (x == thirdPref && y != thirdPref)
return -1;
else
return string.Compare(x, y);
}
}
回答by Guffa
Put the preferred values in a dictionary. Looking up keys in a dictionary is a O(1) operation compared to finding values in a list which is a O(n) operation, so it scales much better.
将首选值放入字典中。与在列表中查找值的 O(n) 操作相比,在字典中查找键是 O(1) 操作,因此它的扩展性要好得多。
Create a sort string for each preferred value so that they are placed before the other values. For the other values the value itself will be used as sorting string so that they are actually sorted. (Using any arbitrary high value would only place them at the end of the list unsorted).
为每个首选值创建一个排序字符串,以便将它们放在其他值之前。对于其他值,值本身将用作排序字符串,以便对它们进行实际排序。(使用任意高值只会将它们放在未排序列表的末尾)。
List<string> data = new List<string> {
"E", "B", "D", "A", "C", "B", "A", "C"
};
var preferences = new Dictionary<string, string> {
{ "A", " 01" },
{ "B", " 02" },
{ "C", " 03" }
};
string key;
IEnumerable<String> orderedData = data.OrderBy(
item => preferences.TryGetValue(item, out key) ? key : item
);
回答by alexqc
In addition to @Daniel Brückner answerand problem defined at the end of it:
除了@Daniel Brückner 的回答和最后定义的问题:
I don't like Concat() and ToList() in there. But for the moment I have no really >good way around that. I am looking for a nice trick to turn the -1 of the first >example into a big number.
我不喜欢里面的 Concat() 和 ToList()。但目前我没有真正>好的方法来解决这个问题。我正在寻找一个很好的技巧来将第一个 >example 的 -1 变成一个大数字。
I think that the solution is to use a statement lambda instead of an expression lambda.
我认为解决方案是使用语句 lambda 而不是表达式 lambda。
var data = new List<string> { "corge", "baz", "foo", "bar", "qux", "quux" };
var fixedOrder = new List<string> { "foo", "bar", "baz" };
data.OrderBy(d => {
var index = fixedOrder.IndexOf(d);
return index == -1 ? int.MaxValue : index;
});
The ordered data is:
有序数据为:
foo
bar
baz
corge
qux
quux
回答by springy76
Combined all answers (and more) into a generic LINQ extension supporting caching which handles any data type, can be case-insensitive and allows to be chained with pre- and post-ordering:
将所有答案(以及更多)组合到一个支持缓存的通用 LINQ 扩展中,该扩展可以处理任何数据类型,可以不区分大小写,并允许使用前序和后序链接:
public static class SortBySample
{
public static BySampleSorter<TKey> Create<TKey>(IEnumerable<TKey> fixedOrder, IEqualityComparer<TKey> comparer = null)
{
return new BySampleSorter<TKey>(fixedOrder, comparer);
}
public static BySampleSorter<TKey> Create<TKey>(IEqualityComparer<TKey> comparer, params TKey[] fixedOrder)
{
return new BySampleSorter<TKey>(fixedOrder, comparer);
}
public static IOrderedEnumerable<TSource> OrderBySample<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, BySampleSorter<TKey> sample)
{
return sample.OrderBySample(source, keySelector);
}
public static IOrderedEnumerable<TSource> ThenBySample<TSource, TKey>(this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, BySampleSorter<TKey> sample)
{
return sample.ThenBySample(source, keySelector);
}
}
public class BySampleSorter<TKey>
{
private readonly Dictionary<TKey, int> dict;
public BySampleSorter(IEnumerable<TKey> fixedOrder, IEqualityComparer<TKey> comparer = null)
{
this.dict = fixedOrder
.Select((key, index) => new KeyValuePair<TKey, int>(key, index))
.ToDictionary(kv => kv.Key, kv => kv.Value, comparer ?? EqualityComparer<TKey>.Default);
}
public BySampleSorter(IEqualityComparer<TKey> comparer, params TKey[] fixedOrder)
: this(fixedOrder, comparer)
{
}
public IOrderedEnumerable<TSource> OrderBySample<TSource>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
return source.OrderBy(item => this.GetOrderKey(keySelector(item)));
}
public IOrderedEnumerable<TSource> ThenBySample<TSource>(IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
return source.CreateOrderedEnumerable(item => this.GetOrderKey(keySelector(item)), Comparer<int>.Default, false);
}
private int GetOrderKey(TKey key)
{
int index;
return dict.TryGetValue(key, out index) ? index : int.MaxValue;
}
}
Sample usage using LINQPad-Dump():
使用 LINQPad-Dump() 的示例用法:
var sample = SortBySample.Create(StringComparer.OrdinalIgnoreCase, "one", "two", "three", "four");
var unsorted = new[] {"seven", "six", "five", "four", "THREE", "tWo", "One", "zero"};
unsorted
.OrderBySample(x => x, sample)
.ThenBy(x => x)
.Dump("sorted by sample then by content");
unsorted
.OrderBy(x => x.Length)
.ThenBySample(x => x, sample)
.Dump("sorted by length then by sample");
回答by Mike Rowley
Not really efficient for large lists but fairly easy to read:
对于大型列表不是很有效,但很容易阅读:
public class FixedOrderComparer<T> : IComparer<T>
{
private readonly T[] fixedOrderItems;
public FixedOrderComparer(params T[] fixedOrderItems)
{
this.fixedOrderItems = fixedOrderItems;
}
public int Compare(T x, T y)
{
var xIndex = Array.IndexOf(fixedOrderItems, x);
var yIndex = Array.IndexOf(fixedOrderItems, y);
xIndex = xIndex == -1 ? int.MaxValue : xIndex;
yIndex = yIndex == -1 ? int.MaxValue : yIndex;
return xIndex.CompareTo(yIndex);
}
}
Usage:
用法:
var orderedData = data.OrderBy(x => x, new FixedOrderComparer<string>("A", "B", "C"));
Note: Array.IndexOf<T>(....)
uses EqualityComparer<T>.Default
to find the target index.
注意:Array.IndexOf<T>(....)
用于EqualityComparer<T>.Default
查找目标索引。
回答by solublefish
I use these. I like the IEnumerable overload for cleanliness, but the priority map version should have better performance on repeated calls.
我用这些。我喜欢 IEnumerable 重载的清洁度,但优先级映射版本在重复调用时应该有更好的性能。
public static IEnumerable<T> OrderByStaticList<T>(this IEnumerable<T> items, IReadOnlyDictionary<T, double> priorityMap)
{
return items.OrderBy(x => priorityMap.GetValueOrDefault(x, double.MaxValue));
}
public static IEnumerable<T> OrderByStaticList<T>(this IEnumerable<T> items, IEnumerable<T> preferenceOrder)
{
int priority = 0;
var priorityMap = preferenceOrder.ToDictionary(x => x, x => (double) priority++);
return OrderByStaticList(items, priorityMap);
}
[TestMethod]
public void PriorityMap_DeterminesSort()
{
var map = new Dictionary<char, double>()
{
{'A', 1},
{'B', 7},
{'C', 3},
{'D', 42},
{'E', -1},
};
Assert.AreEqual("EACBD", new string("ABCDE".OrderByStaticList(map).ToArray()));
}
[TestMethod]
public void PriorityMapMissingItem_SortsLast()
{
var map = new Dictionary<char, double>()
{
{'A', 1},
{'B', 7},
{'D', 42},
{'E', -1},
};
Assert.AreEqual("EABDC", new string("ABCDE".OrderByStaticList(map).ToArray()));
}
[TestMethod]
public void OrderedList_DeterminesSort()
{
Assert.AreEqual("EACBD", new string("ABCDE".OrderByStaticList("EACBD").ToArray()));
}
[TestMethod]
public void OrderedListMissingItem_SortsLast()
{
Assert.AreEqual("EABDC", new string("ABCDE".OrderByStaticList("EABD").ToArray()));
}