Я думаю, что вы, вероятно, найдете эту проблему гораздо проще решить в F #, если переписать вышесказанное декларативным, а не обязательным образом. Вот мой необычный подход к декларативному переписыванию вышеупомянутого:
Сначала нам нужен класс-обертка для украшения наших предметов свойством, имеющим ранг.
class Ranked<T> {
public T Value { get; private set; }
public double Rank { get; private set; }
public Ranked(T value, double rank) {
this.Value = value;
this.Rank = rank;
}
}
Здесь ваш алгоритм декларативным образом. Обратите внимание, что elements
является вашей входной последовательностью, а результирующая последовательность находится в том же порядке, что и elements
. Делегат func
- это значение, которое вы хотите ранжировать elements
.
static class IEnumerableExtensions {
public static IEnumerable<Ranked<T>> Rank<T, TRank>(
this IEnumerable<T> elements,
Func<T, TRank> func
) {
var groups = elements.GroupBy(x => func(x));
var ranks = groups.OrderBy(g => g.Key)
.Aggregate(
(IEnumerable<double>)new List<double>(),
(x, g) =>
x.Concat(
Enumerable.Repeat(
Enumerable.Range(x.Count() + 1, g.Count()).Sum() / (double)g.Count(),
g.Count()
)
)
)
.GroupBy(r => r)
.Select(r => r.Key)
.ToArray();
var dict = groups.Select((g, i) => new { g.Key, Index = i })
.ToDictionary(x => x.Key, x => ranks[x.Index]);
foreach (T element in elements) {
yield return new Ranked<T>(element, dict[func(element)]);
}
}
}
Использование:
class MyClass {
public double Score { get; private set; }
public MyClass(double score) { this.Score = score; }
}
List<MyClass> list = new List<MyClass>() {
new MyClass(1.414),
new MyClass(2.718),
new MyClass(2.718),
new MyClass(2.718),
new MyClass(1.414),
new MyClass(3.141),
new MyClass(3.141),
new MyClass(3.141),
new MyClass(1.618)
};
foreach(var item in list.Rank(x => x.Score)) {
Console.WriteLine("Score = {0}, Rank = {1}", item.Value.Score, item.Rank);
}
Выход:
Score = 1.414, Rank = 1.5
Score = 2.718, Rank = 3
Score = 2.718, Rank = 3
Score = 2.718, Rank = 3
Score = 1.414, Rank = 1.5
Score = 3.141, Rank = 5
Score = 3.141, Rank = 5
Score = 3.141, Rank = 5
Score = 1.618, Rank = 8
Обратите внимание, что мне не требуется заказывать последовательность ввода. Результирующий код становится проще, если вы применяете такое требование к входной последовательности. Обратите внимание, что мы не изменяем последовательность ввода и не изменяем элементы ввода. Это делает F # счастливым.
Отсюда вы сможете легко переписать это на F #.