ordering (L, [optional]) :=
    block ([P, P1: if optional = [] then 'orderlessp else optional[1]],
           P: buildq ([P1], lambda ([a, b], P1 (first (a), first (b)))),
           map (second, sort (makelist ([L[i], i], i, 1, length (L)), P)));

ranks (L, [optional]) :=
    block ([P: if optional = [] then 'orderlessp else optional[1],
            ties_method: if length (optional) < 2 then 'mean_rank else optional[2]],
           ranks1 (L, P, ties_method));

ranks1 (L, P, ties_method) :=
    block ([order: ordering (L, P),
            eq: buildq ([P], lambda ([a, b], not P(a, b) and not P(b, a)))],
           %ranks: perm_inverse (order),
           if ties_method # 'distinct_ranks
               then block ([sorted: permute (order, L), runs],
                           runs: find_runs_nontrivial (sorted, eq),
                           for e in runs do block ([tied_rank],
                               if ties_method = 'mean_rank
                                   then tied_rank : (1/second(e)) * sum (%ranks [order[i]], i, first(e), first(e) + second(e) - 1)
                               elseif ties_method = 'min_rank
                                   then tied_rank : %ranks[ order[ first(e) ] ]
                               elseif ties_method = 'max_rank
                                   then tied_rank : %ranks[ order[ first(e) + second(e) - 1 ] ]
                               else error ("ranks: ties_method must be distinct_ranks, mean_rank, min_rank, or max_rank; found:", ties_method),
                               for i : first(e) thru first(e) + second(e) - 1 do %ranks [order[i]] : tied_rank)),
               /* else: return %ranks without modification */
           %ranks);

/* find_runs -- find consecutive identical values in an array or list.
 * Returns a structure runs(...) with fields lengths and values.
 */

defstruct (runs (lengths, values));

find_runs (x, [optional]) :=
    block ([eq: if optional = [] then "=" else optional[1]],
           if x = [] then runs ([], [])
               else block ([dx : map (lambda ([a, b], is(not eq (a, b))), rest(x), rest(x, -1)), ii0, ii, dii],
                           ii0 : sublist_indices (dx, identity),
                           ii : append ([0], ii0, [length (x)]),
                           dii : rest(ii) - rest(ii, -1),
                           runs (dii, makelist (x[i], i, rest (ii)))));

find_runs_inverse (r) :=
  block ([v: r@values, n: r@lengths],
         apply (append, makelist (makelist (v[i], n[i]), i, 1, length(v))));

find_runs_nontrivial (x, eq) :=
    if x = [] then []
        else block ([runs: find_runs (x, eq), by_twos],
                    /* rearrange runs(...) to list of pairs as expected in this function */
                    map (lambda ([a, b], [a, b]), cons (1, 1 + accumulate ("+", rest (runs@lengths, -1))), runs@lengths),
                    sublist (%%, lambda ([e], second (e) > 1)));
