F#如何百分比排名一双双打?

时间:2010-02-10 19:40:44

标签: f#

我正在尝试在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 个答案:

答案 0 :(得分:5)

这是我将如何做到这一点,虽然这不是您的代码的直接翻译。我已经完成了功能性的工作,将结果从一个转换到另一个转换。

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

或者复制Mehrdad的风格:

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 :(得分:1)

如果你以声明的方式而不是以强制方式重写上述内容,我认为你可能会发现这个问题在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);
}

请注意,我不需要订购输入序列。如果对输入序列强制执行此类要求,则生成的代码更简单。另请注意,我们不会改变输入序列,也不会改变输入项。这让F#很开心。

从这里你应该可以轻松地在F#中重写它。

答案 2 :(得分:0)

这不是一个非常有效的算法(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|]

答案 3 :(得分:0)

Mehrdad的解决方案非常好,但对我来说有点慢。初始排序可以进行一次。而不是每次遍历列表以获得项目的数量&lt;或者&lt; =目标,我们可以使用计数器。这更为迫切(可以使用折叠):

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