Linq是否有办法在不知道值的顺序的情况下对一组值(本例中是字符串)执行OrderBy?
考虑这些数据:
A
B
A
C
B
C
D
E
这些变量:
string firstPref,secondPref,thirdPref;
当值设置如下:
firstPref = 'A';
secondPref = 'B';
thirdPref = 'C';
是否可以像这样订购数据:
A
A
B
B
C
C
D
E
答案 0 :(得分:98)
如果您将首选项放入列表中,可能会更容易。
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));
这会将所有项目都放在preferences
中,因为IndexOf()
会返回-1
。特别的解决方法可能是逆转preferences
并将结果降序排序。这变得相当丑陋,但有效。
IEnumerable<String> orderedData = data.OrderByDescending(
item => Enumerable.Reverse(preferences).ToList().IndexOf(item));
如果你结束preferences
和data
,解决方案会变得更好。
IEnumerable<String> orderedData = data.OrderBy(
item => preferences.Concat(data).ToList().IndexOf(item));
我不喜欢Concat()
和ToList()
。但就目前而言,我没有什么好办法。我正在寻找一个很好的技巧来将第一个例子的-1
变成一个大数字。
答案 1 :(得分:16)
除了@DanielBrückneranswer以及最后定义的问题:
我不喜欢那里的Concat()和ToList()。但就目前而言,我并没有真正的好方法。我正在寻找一个很好的技巧,将第一个&gt;示例的-1变成一个大数字。
我认为解决方案是使用语句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;
});
有序数据是:
foo
bar
baz
corge
qux
quux
答案 2 :(得分:4)
将首选值放在字典中。查找字典中的键是O(1)操作与查找列表中的值(与O(n)操作相比),因此它可以更好地扩展。
为每个首选值创建一个排序字符串,以便将它们放在其他值之前。对于其他值,值本身将用作排序字符串,以便实际对它们进行排序。 (使用任意高值只会将它们放在未排序的列表末尾。)
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
);
答案 3 :(得分:1)
是的,您必须实现自己的IComparer<string>
,然后将其作为LINQ的OrderBy方法的第二个参数传递。
可以在这里找到一个例子: Ordering LINQ results
答案 4 :(得分:1)
将所有答案(以及更多)组合到一个支持缓存的通用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;
}
}
使用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");
答案 5 :(得分:0)
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);
}
}
答案 6 :(得分:0)
对于大型列表不是很有效,但相当容易阅读:
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);
}
}
用法:
var orderedData = data.OrderBy(x => x, new FixedOrderComparer<string>("A", "B", "C"));
注意:Array.IndexOf<T>(....)
使用EqualityComparer<T>.Default
来查找目标索引。
答案 7 :(得分:0)
我用这些。我喜欢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()));
}