自定义 Lambda 排序扩展
Custom Lambda sort extension
我想使用我的自定义扩展方法来订购对象列表。它只是一个样本,所以它使用了冒泡排序。我目前的状态:
public static IOrderedQueryable<TSource> OrderByBubbleSort<TSource, TKey>(this IQueryable<TSource> source, Func<TSource, TKey> keySelector)
{
IComparer<TSource> comparer = Comparer<TSource>.Default;
List<TSource> Result = source.ToList();
for (int i = (source.Count() - 1); i >= 0; i--)
{
for (int j = 1; j <= i; j++)
{
if (comparer.Compare(Result[j - 1], Result[j]) > 0)
{
var temp = Result[j - 1];
Result[j - 1] = Result[j];
Result[j] = temp;
}
}
}
return (IOrderedQueryable<TSource>)Result;
}
所以我可以这样称呼它:
List<Person> pList = ...
List<Person> Result = pList.OrderByBubbleSort(x => x.Age).ThenBy(x => x.Name).ToList();
我坚持 keySelector
。我需要比较所选 属性 的项目(例如 Age
)
问题:我的比较器的正确语法是怎样的?
您正在拨打comparer.Compare(Result[j - 1], Result[j])
。这是错误的,因为它没有像您所说的那样考虑密钥。
您应该通过调用 Func keySelector
获取密钥,并匹配那个:
IComparer<TKey> comparer = Comparer<TKey>.Default;
var x = keySelector(Result[j - 1]);
var y = keySelector(Result[j]);
if (comparer.Compare(x, y) > 0)
{
...
}
现在...像其他人所说的那样正确地执行 OrderBy
很困难...这是我在 15 分钟内编写的实现...
public static class MyOrderedEnumerable
{
public static IOrderedEnumerable<TSource> MyOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
{
if (source == null)
{
throw new ArgumentNullException();
}
if (keySelector == null)
{
throw new ArgumentNullException();
}
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2 = (x, y) => comparer.Compare(keySelector(x), keySelector(y));
return new OrderedEnumerableImpl<TSource>(source, comparer2);
}
public static IOrderedEnumerable<TSource> MyOrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
{
if (source == null)
{
throw new ArgumentNullException();
}
if (keySelector == null)
{
throw new ArgumentNullException();
}
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2 = (x, y) => comparer.Compare(keySelector(y), keySelector(x));
return new OrderedEnumerableImpl<TSource>(source, comparer2);
}
private class OrderedEnumerableImpl<TSource> : IOrderedEnumerable<TSource>
{
private readonly IEnumerable<TSource> Source;
private readonly Comparison<TSource> Comparer;
public OrderedEnumerableImpl(IEnumerable<TSource> source, Comparison<TSource> comparer)
{
Source = source;
Comparer = comparer;
}
public IOrderedEnumerable<TSource> CreateOrderedEnumerable<TKey>(Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending)
{
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2;
if (descending)
{
comparer2 = (x, y) =>
{
int result = Comparer(x, y);
if (result == 0)
{
result = comparer.Compare(keySelector(y), keySelector(x));
}
return result;
};
}
else
{
comparer2 = (x, y) =>
{
int result = Comparer(x, y);
if (result == 0)
{
result = comparer.Compare(keySelector(x), keySelector(y));
}
return result;
};
}
return new OrderedEnumerableImpl<TSource>(Source, comparer2);
}
public IEnumerator<TSource> GetEnumerator()
{
var source = Source.ToArray();
// ** Here you do the sorting! **
Array.Sort(source, Comparer);
for (int i = 0; i < source.Length; i++)
{
yield return source[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
看到** Here you do the sorting! **
了吗?在那里你必须插入你的排序算法。如您所见,有一个 OrderedEnumerableImpl
class 执行实现。重点是 ThenBy
使用 CreateOrderedEnumerable
方法。此方法通过将其比较器链接到 OrderBy
的基本比较器来修改排序算法。 但是这里有一点:您不能真正修改顺序,因为可以:
var myorderedcoll = mycoll.OrderBy(x => x.Foo);
var mysubordered1 = myorderedcoll.ThenBy(x => x.Bar1);
var mysubordered2 = myorderedcoll.ThenBy(x => x.Bar2);
显然 mysubordered1
按 Foo+Bar1
排序,而 mysubordered2
按 Foo+Bar2
排序!
所以CreateOrderedEnumerable
创建一个新的OrderedEnumerableImpl
class,通过获取已经存在的比较器并添加新的比较器(参见CreateOrderedEnumerable
的最后一行)
请注意,您将此 class 用作:
var myorderedcoll = mycoll.MyOrderBy(x => x.Foo).ThenBy(x => x.Bar);
等等。
请注意,"real" 实现要复杂得多。如所写,此实现每次进行比较时都会重新计算 key
。实际实现会预先计算所有必要的键以加速比较。
排序,使用wiki:
public IEnumerator<TSource> GetEnumerator()
{
var source = Source.ToArray();
// Taken from http://en.wikipedia.org/wiki/Bubble_sort#Pseudocode_implementation
int n = source.Length;
do
{
int newn = 0;
for (int i = 1; i <= n - 1; i++)
{
if (Comparer(source[i - 1], source[i]) > 0)
{
TSource temp = source[i - 1];
source[i - 1] = source[i];
source[i] = temp;
newn = i;
}
}
n = newn;
}
while (n != 0);
for (int i = 0; i < source.Length; i++)
{
yield return source[i];
}
}
这也可以优化:如果我们反转冒泡排序并在数组 "top" 中累积有序元素,那么我们可以为每个循环的冒泡排序 yield return
一个元素。这将缩短拥有第一个元素所需的时间。所以如果你这样做 OrderBy(x => x.Foo).First()
,并不是所有的集合都会被排序。
我想使用我的自定义扩展方法来订购对象列表。它只是一个样本,所以它使用了冒泡排序。我目前的状态:
public static IOrderedQueryable<TSource> OrderByBubbleSort<TSource, TKey>(this IQueryable<TSource> source, Func<TSource, TKey> keySelector)
{
IComparer<TSource> comparer = Comparer<TSource>.Default;
List<TSource> Result = source.ToList();
for (int i = (source.Count() - 1); i >= 0; i--)
{
for (int j = 1; j <= i; j++)
{
if (comparer.Compare(Result[j - 1], Result[j]) > 0)
{
var temp = Result[j - 1];
Result[j - 1] = Result[j];
Result[j] = temp;
}
}
}
return (IOrderedQueryable<TSource>)Result;
}
所以我可以这样称呼它:
List<Person> pList = ...
List<Person> Result = pList.OrderByBubbleSort(x => x.Age).ThenBy(x => x.Name).ToList();
我坚持 keySelector
。我需要比较所选 属性 的项目(例如 Age
)
问题:我的比较器的正确语法是怎样的?
您正在拨打comparer.Compare(Result[j - 1], Result[j])
。这是错误的,因为它没有像您所说的那样考虑密钥。
您应该通过调用 Func keySelector
获取密钥,并匹配那个:
IComparer<TKey> comparer = Comparer<TKey>.Default;
var x = keySelector(Result[j - 1]);
var y = keySelector(Result[j]);
if (comparer.Compare(x, y) > 0)
{
...
}
现在...像其他人所说的那样正确地执行 OrderBy
很困难...这是我在 15 分钟内编写的实现...
public static class MyOrderedEnumerable
{
public static IOrderedEnumerable<TSource> MyOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
{
if (source == null)
{
throw new ArgumentNullException();
}
if (keySelector == null)
{
throw new ArgumentNullException();
}
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2 = (x, y) => comparer.Compare(keySelector(x), keySelector(y));
return new OrderedEnumerableImpl<TSource>(source, comparer2);
}
public static IOrderedEnumerable<TSource> MyOrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
{
if (source == null)
{
throw new ArgumentNullException();
}
if (keySelector == null)
{
throw new ArgumentNullException();
}
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2 = (x, y) => comparer.Compare(keySelector(y), keySelector(x));
return new OrderedEnumerableImpl<TSource>(source, comparer2);
}
private class OrderedEnumerableImpl<TSource> : IOrderedEnumerable<TSource>
{
private readonly IEnumerable<TSource> Source;
private readonly Comparison<TSource> Comparer;
public OrderedEnumerableImpl(IEnumerable<TSource> source, Comparison<TSource> comparer)
{
Source = source;
Comparer = comparer;
}
public IOrderedEnumerable<TSource> CreateOrderedEnumerable<TKey>(Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending)
{
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2;
if (descending)
{
comparer2 = (x, y) =>
{
int result = Comparer(x, y);
if (result == 0)
{
result = comparer.Compare(keySelector(y), keySelector(x));
}
return result;
};
}
else
{
comparer2 = (x, y) =>
{
int result = Comparer(x, y);
if (result == 0)
{
result = comparer.Compare(keySelector(x), keySelector(y));
}
return result;
};
}
return new OrderedEnumerableImpl<TSource>(Source, comparer2);
}
public IEnumerator<TSource> GetEnumerator()
{
var source = Source.ToArray();
// ** Here you do the sorting! **
Array.Sort(source, Comparer);
for (int i = 0; i < source.Length; i++)
{
yield return source[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
看到** Here you do the sorting! **
了吗?在那里你必须插入你的排序算法。如您所见,有一个 OrderedEnumerableImpl
class 执行实现。重点是 ThenBy
使用 CreateOrderedEnumerable
方法。此方法通过将其比较器链接到 OrderBy
的基本比较器来修改排序算法。 但是这里有一点:您不能真正修改顺序,因为可以:
var myorderedcoll = mycoll.OrderBy(x => x.Foo);
var mysubordered1 = myorderedcoll.ThenBy(x => x.Bar1);
var mysubordered2 = myorderedcoll.ThenBy(x => x.Bar2);
显然 mysubordered1
按 Foo+Bar1
排序,而 mysubordered2
按 Foo+Bar2
排序!
所以CreateOrderedEnumerable
创建一个新的OrderedEnumerableImpl
class,通过获取已经存在的比较器并添加新的比较器(参见CreateOrderedEnumerable
的最后一行)
请注意,您将此 class 用作:
var myorderedcoll = mycoll.MyOrderBy(x => x.Foo).ThenBy(x => x.Bar);
等等。
请注意,"real" 实现要复杂得多。如所写,此实现每次进行比较时都会重新计算 key
。实际实现会预先计算所有必要的键以加速比较。
排序,使用wiki:
public IEnumerator<TSource> GetEnumerator()
{
var source = Source.ToArray();
// Taken from http://en.wikipedia.org/wiki/Bubble_sort#Pseudocode_implementation
int n = source.Length;
do
{
int newn = 0;
for (int i = 1; i <= n - 1; i++)
{
if (Comparer(source[i - 1], source[i]) > 0)
{
TSource temp = source[i - 1];
source[i - 1] = source[i];
source[i] = temp;
newn = i;
}
}
n = newn;
}
while (n != 0);
for (int i = 0; i < source.Length; i++)
{
yield return source[i];
}
}
这也可以优化:如果我们反转冒泡排序并在数组 "top" 中累积有序元素,那么我们可以为每个循环的冒泡排序 yield return
一个元素。这将缩短拥有第一个元素所需的时间。所以如果你这样做 OrderBy(x => x.Foo).First()
,并不是所有的集合都会被排序。