xref: /web-php/src/UserNotes/Sorter.php (revision 9709f819)
1<?php
2
3namespace phpweb\UserNotes;
4
5class Sorter
6{
7    private $maxVote;
8
9    private $minVote;
10
11    private $maxAge;
12
13    private $minAge;
14
15    private $voteFactor;
16
17    private $ageFactor;
18
19    private $voteWeight = 38;
20
21    private $ratingWeight = 60;
22
23    private $ageWeight = 2;
24
25    /**
26     * @param array<string, UserNote> $notes
27     */
28    public function sort(array &$notes):void {
29        // First we make a pass over the data to get the min and max values
30        // for data normalization.
31        $this->findMinMaxValues($notes);
32
33        $this->voteFactor = $this->maxVote - $this->minVote
34            ? (1 - .3) / ($this->maxVote - $this->minVote)
35            : .5;
36        $this->ageFactor = $this->maxAge - $this->minAge
37            ? 1 / ($this->maxAge - $this->minAge)
38            : .5;
39
40        $this->ageFactor *= $this->ageWeight;
41
42        // Second we loop through to calculate sort priority using the above numbers
43        $prio = $this->calcSortPriority($notes);
44
45        // Third we sort the data.
46        uasort($notes, function ($a, $b) use ($prio) {
47            return $prio[$b->id] <=> $prio[$a->id];
48        });
49    }
50
51    private function calcVotePriority(UserNote $note):float {
52        return ($note->upvotes - $note->downvotes - $this->minVote) * $this->voteFactor + .3;
53    }
54
55    private function calcRatingPriority(UserNote $note):float {
56        return $note->upvotes + $note->downvotes <= 2 ? 0.5 : $this->calcRating($note);
57    }
58
59    private function calcRating(UserNote $note):float {
60        $totalVotes = $note->upvotes + $note->downvotes;
61        return $totalVotes > 0 ? $note->upvotes / $totalVotes : .5;
62    }
63
64    /**
65     * @param array<string, UserNote> $notes
66     */
67    private function calcSortPriority(array $notes): array {
68        $prio = [];
69        foreach ($notes as $note) {
70            $prio[$note->id] = ($this->calcVotePriority($note) * $this->voteWeight)
71                + ($this->calcRatingPriority($note) * $this->ratingWeight)
72                + (($note->ts - $this->minAge) * $this->ageFactor);
73        }
74        return $prio;
75    }
76
77    /**
78     * @param array<string, UserNote> $notes
79     */
80    private function findMinMaxValues(array $notes):void {
81        if ($notes === []) {
82            return;
83        }
84
85        $first = array_shift($notes);
86
87        $this->minVote = $this->maxVote = ($first->upvotes - $first->downvotes);
88        $this->minAge = $this->maxAge = $first->ts;
89
90        foreach ($notes as $note) {
91            $this->maxVote = max($this->maxVote, ($note->upvotes - $note->downvotes));
92            $this->minVote = min($this->minVote, ($note->upvotes - $note->downvotes));
93            $this->maxAge = max($this->maxAge, $note->ts);
94            $this->minAge = min($this->minAge, $note->ts);
95        }
96    }
97}
98