TV新聞紙面で見るスポーツの結果、Golf など、同率順位を並べた後の順位は、
名前 | point | 順位 |
A | 100 | 1 |
B | 100 | 1 |
C | 80 | 3 |
で表されることが多いです。
名前 | point | 順位 |
A | 100 | 1 |
B | 100 | 1 |
C | 80 | 2 |
と、純粋にシーケンシャルに順位が振られているのを、スポーツの暫定結果では
見かけることが無いです。
数学的には純粋にシーケンシャルに順位が振られているのが見方によって正しいとは思います。
純粋にシーケンシャルに順位を振るケースは、
処理に置き換えた場合にロジックとしてそんなに難しくないですが、
同率順位を並べた後の順位は、同率順位の個数分カウントUPさせる順位を振るロジックは、
めんどうなことになります。
以下のサンプルで、Java Stream の処理で、順位を振る両方のケースを書いてみます。
<前提>
任意の User オブジェクトに、順位を決定する 値= point 属性、高いもの順に振る。
順位を採番して格納先は、User オブジェクト内で持つ。= rank 属性
だとして、、
public class User{ public int id; public String name; public int point; public int rank; public User(){} }
ソートをして、Stream の終端操作 collect メソッド
の accumulator で rank 属性を振ります。
簡単な方、、純粋にシーケンシャルに順位が振る方、
List<User> list ; // リスト格納されてから。。 list.stream() .sorted((a, b)->Integer.valueOf(b.point).compareTo(Integer.valueOf(a.point))) .collect(()->new ArrayList<User>(), (r, t)->{ if (r.isEmpty()){ t.rank = 1; }else{ User p = r.get(r.size()-1); if (p.point==t.point){ t.rank = p.rank; }else{ t.rank = p.rank + 1; } } r.add(t); }, (r, u)->{});
↑これはさほど難しくないロジックで誰でもわりと簡単に書くと思います。
同率順位の個数分カウントUPさせる順位を振るケース
List<User> list ; // リスト格納されてから。。 list.stream() .sorted((a, b)->Integer.valueOf(b.point).compareTo(Integer.valueOf(a.point))) .collect(()->new ArrayList<User>(), (r, t)->{ int rsize = r.size(); if (rsize==0){ t.rank = 1; }else{ User p = r.get(rsize-1); if (p.point==t.point){ t.rank = p.rank; }else{ int inc = 0; for(ListIterator<User> it=r.listIterator(rsize); it.hasPrevious();){ User pre = it.previous(); if (p.point != pre.point) break; inc++; } t.rank = p.rank + inc; } } r.add(t); }, (r, u)->{});
↑ ListIterator を使って遡った順位比較を行います。
これを毎回書くのは、嫌です。そこで Stream 終端操作、collect に渡す accumulator を
提供するメソッドを、用意します。
import java.util.List; import java.util.ListIterator; import java.util.function.BiConsumer; import java.util.function.Function; /** * ソート済のリストに順位を振る accumulator. */ public final class RankAccumulator{ /** private constructor. */ private RankAccumulator(){} /** * シーケンシャル順位付け. * @param getValue 比較値取得 Function<T, Integer> * @param setRank 順位設定 BiConsumer<T, Integer> * @return BiConsumer<R, T> */ public static <R extends List<T>, T> BiConsumer<R, T> seek(Function<T, Integer> getValue, BiConsumer<T, Integer> setRank){ return new BiConsumer<R, T>(){ int count = 1; @Override public void accept(R r, T t){ if (r.isEmpty()){ setRank.accept(t, 1); }else{ T pre = r.get(r.size()-1); if (getValue.apply(pre).intValue() != getValue.apply(t).intValue()){ count++; } setRank.accept(t, count); } r.add(t); } }; } /** * スキップ順位付け. * @param getValue 比較値取得 Function<T, Integer> * @param setRank 順位設定 BiConsumer<T, Integer> * @return BiConsumer<R, T> */ public static <R extends List<T>, T> BiConsumer<R, T> skip(Function<T, Integer> getValue, BiConsumer<T, Integer> setRank){ return new BiConsumer<R, T>(){ int count = 1; @Override public void accept(R r, T t){ int rsize = r.size(); if (rsize==0){ setRank.accept(t, 1); }else{ T last = r.get(r.size()-1); if (getValue.apply(last).intValue() == getValue.apply(t).intValue()){ setRank.accept(t, count); }else{ int inc = 0; for(ListIterator<T> it=r.listIterator(rsize); it.hasPrevious();){ T pre = it.previous(); if (getValue.apply(last).intValue() != getValue.apply(pre).intValue()) break; inc++; } if (inc > 0){ count += inc; }else { count++; } setRank.accept(t, count); } } r.add(t); } }; } }
すると、、
シーケンシャル順位付けは、、
List<User> list; // list に格納 list.stream() .sorted((a, b)->Integer.valueOf(b.point).compareTo(Integer.valueOf(a.point))) .collect(()->new ArrayList<User>(), RankAccumulator.seek(t->t.point, (t, u)->t.rank=u), (r, u)->{});
スキップ順位付けは、
List<User> list; // list に格納 list.stream(). sorted((a, b)->Integer.valueOf(b.point).compareTo(Integer.valueOf(a.point))) .collect(()->new ArrayList<User>(), RankAccumulator.skip(t->t.point, (t, u)->t.rank=u), (r, u)->{});
となり、順位を決定する属性、順位の採番を格納する先、
に処理を注力させる記述を書くことできます。
この、.「。。。注力させる記述を書くことできる」のが大切な目的であり、
遊んでるわけではありません。