F # Как Процентильно Ранжировать Массив Двойников? - PullRequest
1 голос
/ 10 февраля 2010

Я пытаюсь взять числовой массив в F # и ранжировать все элементы так, чтобы связи получили одинаковый ранг.В основном я пытаюсь повторить алгоритм, который у меня есть ниже в C #, но только для массива пар.Помогите?

rankMatchNum = 0;rankMatchSum = 0;previousScore = -999999999;

        for (int i = 0; i < factorStocks.Count; i++)
        {
            //The 1st time through it won't ever match the previous score...
            if (factorStocks[i].factors[factorName + "_R"] == previousScore)
            {
                rankMatchNum = rankMatchNum + 1;     //The count of matching ranks
                rankMatchSum = rankMatchSum + i + 1; //The rank itself...
                for (int j = 0; j <= rankMatchNum; j++)
                {
                    factorStocks[i - j].factors[factorName + "_WR"] = rankMatchSum / (rankMatchNum + 1);
                }
            }
            else
            {
                rankMatchNum = 0;
                rankMatchSum = i + 1;
                previousScore = factorStocks[i].factors[factorName + "_R"];
                factorStocks[i].factors[factorName + "_WR"] = i + 1;
            }
        }

Ответы [ 4 ]

5 голосов
/ 10 февраля 2010

Вот как я бы это сделал, хотя это не прямой перевод вашего кода. Я сделал все в функциональном стиле, передавая результаты от одного преобразования к другому.

let rank seq =
  seq
  |> Seq.countBy (fun x -> x)     // count repeated numbers
  |> Seq.sortBy (fun (k,v) -> k)  // order by key
  |> Seq.fold (fun (r,l) (_,n) -> // accumulate the number of items seen and the list of grouped average ranks
      let r'' = r + n             // get the rank after this group is processed
      let avg = List.averageBy float [r+1 .. r''] // average ranks for this group
      r'', ([for _ in 1 .. n -> avg]) :: l)       // add a list with avg repeated
      (0,[])                          // seed the fold with rank 0 and an empty list 
      |> snd                          // get the final list component, ignoring the component storing the final rank
      |> List.rev                     // reverse the list
      |> List.collect (fun l -> l)    // merge individual lists into final list

Или скопировать стиль Мехрдада:

let rank arr =
  let lt item = arr |> Seq.filter (fun x -> x < item) |> Seq.length
  let lte item = arr |> Seq.filter (fun x -> x <= item) |> Seq.length
  let avgR item = [(lt item) + 1 .. (lte item)] |> List.averageBy float
  Seq.map avgR arr
1 голос
/ 10 февраля 2010

Я думаю, что вы, вероятно, найдете эту проблему гораздо проще решить в 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 #.

0 голосов
/ 07 марта 2012

Решение Мехрдада очень хорошее, но немного медленное для моих целей. Первоначальная сортировка может быть выполнена 1 раз. Вместо того, чтобы каждый раз просматривать списки, чтобы получить количество предметов <или <= цель, мы можем использовать счетчики. Это более важно (можно было использовать фолд): </p>

let GetRanks2 ( arr ) =
    let tupleList = arr |> Seq.countBy( fun x -> x ) |> Seq.sortBy( fun (x,count) -> x )
    let map = new System.Collections.Generic.Dictionary<int,float>()
    let mutable index = 1
    for (item, count) in tupleList do
        let c = count
        let avgRank = 
            let mutable s = 0
            for i = index to index + c - 1 do 
                s <- s + i
            float s / float c
        map.Add( item, avgRank )
        index <- index + c
    //
    map
0 голосов
/ 10 февраля 2010

Это не очень эффективный алгоритм (O (n 2 )), но он довольно короткий и читаемый:

let percentile arr =
   let rank item = ((arr |> Seq.filter (fun i -> i < item) 
                         |> Seq.length |> float) + 1.0) 
                   / float (Array.length arr) * 100.0
   Array.map rank arr

Вы можете связываться с выражением fun i -> i < e (или выражением + 1.0), чтобы получить желаемый способ ранжирования результатов:

let arr = [|1.0;2.0;2.0;4.0;3.0;3.0|]
percentile arr |> print_any;;

[|16.66666667; 33.33333333; 33.33333333; 100.0; 66.66666667; 66.66666667|]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...