简单描述下问题:有一个网站,现在有很多用户,每个用户有一个积分,要求在用户登录时显示用户的积分排名,用户的积分会有变动。注意考虑数据量比较大的场景。
原理是以树的方式来分解排名的计算。参考在这里。
实际代码分为三部分:
- 查询某个积分的排名
- 更新积分
- 构造积分树
查询积分排名
原理是计算所有右子树或者同级节点的和,再加1。每个节点包含当前积分或者积分段内的用户总数。右子树或者同级节点代表大于指定积分的积分节点。排名的实际含义就是计算所有大于自己积分的人的总数。最后加1是因为大于最大积分的人的总数为0,但是排名不能为0,所以实际显示是必须加1的。
这块实际依赖树的结构,可能是数组或者是链表,但通用逻辑如下(递归):
in: current node, score if current node is score node if current node is left leaf return count of right sibling else # current node is right node, no right sibling return 0 else # current node is score range node if score in left subtree return count of score in left subtree + count of right subtree else # score in right subtree return count of score in right subtree
更新积分
同样是递归逻辑,伪代码如下:
in: current node, score, delta if current node is score node count of current node += delta else # current node is score range node count cache of current node += delta if score in left subtree update count of left subtree else # in right subtree update count of right subtree
顺便说一句,某个用户积分变化,实际上积分树的变化是分两步:减少旧分数的总数,增加新积分的总数。
构造排名树
理论上树的结构应该先定义,不过这边我是功能优先设计的。
首先,每个节点必须有count,积分节点的count就是拥有某个积分的总数,积分区间节点的count是区间内所有积分节点的总数和,是个缓存值。
查询时需要一个同级右节点,还有左右子树。
为了防止意外的分数,需要提供一个最大最小积分便于检查。
一开始我在python中用数组实现的,后来觉得pattern match好像比较适合,就用scala重写了。
最终代码如下:
abstract class AbstractScoreNode { var rightSibling: Option[AbstractScoreNode] = None def getCount(): Int val minScore: Int val maxScore: Int } class ScoreNode(val score: Int, var count: Int) extends AbstractScoreNode { def getCount() = count def updateCount(delta: Int): Unit = count += delta val minScore = score val maxScore = score override def toString(): String = "ScoreNode(score = %d, count = %d)".format(score, count) } class ScoreRangeNode(val leftChild: AbstractScoreNode, val rightChild: AbstractScoreNode) extends AbstractScoreNode { leftChild.rightSibling = Some(rightChild) private var countCache = leftChild.getCount() + rightChild.getCount() def getCount() = countCache def updateCountCache(delta: Int): Unit = countCache += delta val minScore: Int = leftChild match { case x: ScoreNode => x.score case x: ScoreRangeNode => x.minScore } val maxScore: Int = rightChild match { case x: ScoreNode => x.score case x: ScoreRangeNode => x.maxScore } val pivotScore: Int = (minScore + maxScore) / 2 override def toString(): String = "ScoreRangeNode(min = %d, max =%d, count = %d)".format(minScore, maxScore, getCount()) } object ScoreRanking { def apply(maxScore: Int): ScoreRanking = apply(0, maxScore) def apply(minScore: Int, maxScore: Int): ScoreRanking = { if(minScore > maxScore) throw new IllegalArgumentException("min score must less than max score") new ScoreRanking(createNode(minScore, maxScore)) } private def createNode(minScore: Int, maxScore: Int): AbstractScoreNode = { if(minScore == maxScore) return new ScoreNode(minScore, 0) val pivot = (minScore + maxScore) / 2 new ScoreRangeNode(createNode(minScore, pivot), createNode(pivot + 1, maxScore)) } } class ScoreRanking(root: AbstractScoreNode) { def getRankingOf(score: Int): Int = {checkScore(score); getCount(root, score) + 1 } private def checkScore(score: Int): Unit = { if(score < root.minScore || score > root.maxScore) { throw new IllegalArgumentException("score should be in [%d, %d]".format(root.minScore, root.maxScore)) } } private def getCount(node: AbstractScoreNode, score: Int): Int = { node match { case x: ScoreNode => x.rightSibling match { case Some(rightSibling) => getCount(rightSibling, score) case _ => 0 } case x: ScoreRangeNode => if(score <= x.pivotScore) { getCount(x.leftChild, score) + x.rightChild.getCount() } else { getCount(x.rightChild, score) } } } def increaseCountOf(score: Int): Unit = {checkScore(score); updateCountOf(root, score, +1)} def decreaseCountOf(score: Int): Unit = {checkScore(score); updateCountOf(root, score, -1)} private def updateCountOf(node: AbstractScoreNode, score: Int, delta: Int): Unit = { node match { case x: ScoreNode => x.updateCount(delta) case x: ScoreRangeNode => { if(score <= x.pivotScore) { updateCountOf(x.leftChild, score, delta) } else { updateCountOf(x.rightChild, score, delta) } x.updateCountCache(delta) } } } override def toString(): String = root.toString() } object Ranking { def main(args: Array[String]): Unit = { val scoreRanking = ScoreRanking(3) scoreRanking.increaseCountOf(1) scoreRanking.increaseCountOf(2) scoreRanking.increaseCountOf(3) scoreRanking.increaseCountOf(0) scoreRanking.increaseCountOf(3) println(scoreRanking.getRankingOf(0)); println(scoreRanking.getRankingOf(1)); println(scoreRanking.getRankingOf(2)); println(scoreRanking.getRankingOf(3)); } }
主逻辑:构造[0, 3]的积分排名表,导入用户积分1, 2, 3, 0, 3,获取积分为0, 1, 2, 3的人的排名。以下是执行输出
$ scala Ranking 5 4 3 1