use Heapsort

pull/7174/head
Thibault Duplessis 2020-08-23 09:13:34 +02:00
parent 3911d62158
commit f38a1a51ac
8 changed files with 64 additions and 31 deletions

View File

@ -3,10 +3,12 @@ package lila.activity
import org.joda.time.{ DateTime, Interval }
import reactivemongo.api.ReadPreference
import lila.common.Heapsort
import lila.db.dsl._
import lila.game.LightPov
import lila.practice.PracticeStructure
import lila.user.User
import lila.tournament.LeaderboardApi
final class ActivityReadApi(
coll: Coll,
@ -95,7 +97,11 @@ final class ActivityReadApi(
.dmap { entries =>
entries.nonEmpty option ActivityView.Tours(
nb = entries.size,
best = entries.sortBy(_.rankRatio.value).take(activities.maxSubEntries)
best = Heapsort.topN(
entries,
activities.maxSubEntries,
Ordering.by[LeaderboardApi.Entry, Double](-_.rankRatio.value)
)
)
}
.mon(_.user segment "activity.tours")

View File

@ -2,8 +2,14 @@ package lila.common
import scala.collection.BuildFrom
import scala.collection.mutable.{ Growable, PriorityQueue }
import scala.math.Ordering
/*
* Sorts elements in priority order: higher first, lower last.
* It's the opposite of what scala .sort does.
*/
object Heapsort {
private[this] def moveN[T](p: PriorityQueue[T], g: Growable[T], n: Int): Unit = {
//Only the dequeue and dequeueAll methods will return elements in priority order (while removing elements from the heap).
var k = n
@ -12,11 +18,12 @@ object Heapsort {
k -= 1
}
}
/* selects maximum nb elements from n size collection
* should be used for small nb and large n
* Complexity: O(n + nb * log(n))
*/
def topN[T, C](xs: IterableOnce[T], nb: Int, ord: scala.math.Ordering[T])(implicit
def topN[T, C](xs: IterableOnce[T], nb: Int, ord: Ordering[T])(implicit
bf: BuildFrom[xs.type, T, C]
): C = {
val p = PriorityQueue.from(xs)(ord)
@ -25,15 +32,23 @@ object Heapsort {
moveN(p, b, nb)
b.result()
}
def topNToList[T, C](xs: IterableOnce[T], nb: Int, ord: scala.math.Ordering[T]): List[T] = {
def topNToList[T, C](xs: IterableOnce[T], nb: Int, ord: Ordering[T]): List[T] = {
val p = PriorityQueue.from(xs)(ord)
val b = List.newBuilder[T]
moveN(p, b, nb)
b.result()
}
class ListOps[A](private val l: List[A]) extends AnyVal {
def topN(nb: Int)(implicit ord: scala.math.Ordering[A]): List[A] =
Heapsort.topNToList(l, nb, ord)
object implicits {
implicit final class ListOps[A](private val l: List[A]) extends AnyVal {
def topN(nb: Int)(implicit ord: Ordering[A]): List[A] =
Heapsort.topNToList(l, nb, ord)
def botN(nb: Int)(implicit ord: Ordering[A]): List[A] =
Heapsort.topNToList(l, nb, ord.reverse)
}
}
implicit def toListOps[A](l: List[A]) = new ListOps(l)
}

View File

@ -2,6 +2,7 @@ package lila.lobby
import org.joda.time.DateTime
import lila.common.Heapsort
import lila.socket.Socket.Sri
private object HookRepo {
@ -10,6 +11,8 @@ private object HookRepo {
private val hardLimit = 200
implicit private val creationOrdering = Ordering.by[Hook, Long](_.createdAt.getMillis)
def size = hooks.size
def findCompatible(hook: Hook): Vector[Hook] = hooks.filter(_ compatibleWith hook)
@ -17,7 +20,7 @@ private object HookRepo {
def truncateIfNeeded() =
if (size >= hardLimit) {
logger.warn(s"Found $size hooks, cleaning up!")
hooks = hooks.sortBy(-_.createdAt.getMillis).take(hardLimit * 2 / 3)
hooks = Heapsort.topN(hooks, hardLimit * 2 / 3, creationOrdering)
logger.warn(s"Kept ${hooks.size} hooks")
}

View File

@ -1,10 +1,11 @@
package lila.perfStat
import org.joda.time.{ DateTime, Period }
import lila.common.Heapsort
import lila.game.Pov
import lila.rating.PerfType
import org.joda.time.{ DateTime, Period }
case class PerfStat(
_id: String, // userId/perfId
userId: UserId,
@ -27,8 +28,8 @@ case class PerfStat(
copy(
highest = RatingAt.agg(highest, pov, 1),
lowest = if (thisYear) RatingAt.agg(lowest, pov, -1) else lowest,
bestWins = if (~pov.win) bestWins.agg(pov, -1) else bestWins,
worstLosses = if (thisYear && ~pov.loss) worstLosses.agg(pov, 1) else worstLosses,
bestWins = if (~pov.win) bestWins.agg(pov, 1) else bestWins,
worstLosses = if (thisYear && ~pov.loss) worstLosses.agg(pov, -1) else worstLosses,
count = count(pov),
resultStreak = resultStreak agg pov,
playStreak = playStreak agg pov
@ -194,12 +195,16 @@ case class Results(results: List[Result]) extends AnyVal {
.ifTrue(pov.game.bothPlayersHaveMoved)
.fold(this) { opInt =>
Results(
(Result(
opInt,
UserId(~pov.opponent.userId),
pov.game.movedAt,
pov.gameId
) :: results).sortBy(_.opInt * comp) take Results.nb
Heapsort.topN(
Result(
opInt,
UserId(~pov.opponent.userId),
pov.game.movedAt,
pov.gameId
) :: results,
Results.nb,
Ordering.by[Result, Int](_.opInt * comp)
)
)
}
def userIds = results.map(_.opId)

View File

@ -5,13 +5,14 @@ import reactivemongo.api._
import reactivemongo.api.bson._
import scala.concurrent.duration._
import BSONHandlers._
import lila.common.Bus
import lila.common.Heapsort.implicits._
import lila.db.dsl._
import lila.db.paginator._
import lila.hub.actorApi.timeline.{ Propagate, Follow => FollowUser }
import lila.hub.actors
import lila.memo.CacheApi._
import lila.relation.BSONHandlers._
import lila.user.User
final class RelationApi(
@ -229,5 +230,5 @@ final class RelationApi(
}
def searchFollowedBy(u: User, term: String, max: Int): Fu[List[User.ID]] =
repo.followingLike(u.id, term) map { _.sorted take max }
repo.followingLike(u.id, term) map { _ botN max } // alphabetical order
}

View File

@ -2,7 +2,7 @@ package lila.slack
import org.joda.time.DateTime
import lila.common.{ ApiVersion, EmailAddress, IpAddress, LightUser }
import lila.common.{ ApiVersion, EmailAddress, Heapsort, IpAddress, LightUser }
import lila.hub.actorApi.slack._
import lila.user.User
@ -20,6 +20,8 @@ final class SlackApi(
private var buffer: Vector[ChargeEvent] = Vector.empty
implicit private val amountOrdering = Ordering.by[ChargeEvent, Int](_.amount)
def apply(event: ChargeEvent): Funit =
if (event.amount < 10000) addToBuffer(event)
else
@ -30,7 +32,7 @@ final class SlackApi(
private def addToBuffer(event: ChargeEvent): Funit = {
buffer = buffer :+ event
(buffer.head.date isBefore DateTime.now.minusHours(12)) ?? {
val firsts = buffer.sortBy(-_.amount).take(10).map(_.username).map(userAt).mkString(", ")
val firsts = Heapsort.topN(buffer, 10, amountOrdering).map(_.username).map(userAt).mkString(", ")
val amountSum = buffer.map(_.amount).sum
val patrons =
if (firsts.lengthIs > 10) s"$firsts and, like, ${firsts.lengthIs - 10} others,"

View File

@ -1,5 +1,6 @@
package lila.tournament
import lila.common.Heapsort.implicits._
import lila.user.User
case class Spotlight(
@ -14,16 +15,13 @@ object Spotlight {
import Schedule.Freq._
implicit private val importanceOrdering = Ordering.by[Tournament, Int](_.schedule.??(_.freq.importance))
def select(tours: List[Tournament], user: Option[User], max: Int): List[Tournament] =
user.fold(sort(tours) take max) { select(tours, _, max) }
user.fold(tours topN max) { select(tours, _, max) }
def select(tours: List[Tournament], user: User, max: Int): List[Tournament] =
sort(tours.filter { select(_, user) }) take max
private def sort(tours: List[Tournament]) =
tours.sortBy { t =>
-t.schedule.??(_.freq.importance)
}
tours.filter { select(_, user) } topN max
private def select(tour: Tournament, user: User): Boolean =
!tour.isFinished &&

View File

@ -1,8 +1,9 @@
package lila.user
import chess.Speed
import org.joda.time.DateTime
import chess.Speed
import lila.common.Heapsort.implicits._
import lila.db.BSON
import lila.rating.{ Glicko, Perf, PerfType }
@ -59,12 +60,14 @@ case class Perfs(
}
}
implicit private val ratingOrdering = Ordering.by[(PerfType, Perf), Int](_._2.intRating)
def bestPerfs(nb: Int): List[(PerfType, Perf)] = {
val ps = PerfType.nonPuzzle map { pt =>
pt -> apply(pt)
}
val minNb = math.max(1, ps.foldLeft(0)(_ + _._2.nb) / 15)
ps.filter(p => p._2.nb >= minNb).sortBy(-_._2.intRating) take nb
ps.filter(p => p._2.nb >= minNb).topN(nb)
}
def bestPerfType: Option[PerfType] = bestPerf.map(_._1)