github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/service/interesting.go (about) 1 package service 2 3 import ( 4 "context" 5 "sort" 6 7 "github.com/keybase/client/go/libkb" 8 "github.com/keybase/client/go/protocol/keybase1" 9 ) 10 11 type rankedList struct { 12 uids []keybase1.UID 13 ranks map[string]int 14 } 15 16 func newRankedList(uids []keybase1.UID) *rankedList { 17 r := &rankedList{ 18 uids: uids, 19 ranks: make(map[string]int), 20 } 21 for index, uid := range uids { 22 r.ranks[uid.String()] = index 23 } 24 return r 25 } 26 27 func (r *rankedList) UIDs() []keybase1.UID { 28 return r.uids 29 } 30 31 func (r *rankedList) Rank(uid keybase1.UID) float64 { 32 var index int 33 var ok bool 34 if index, ok = r.ranks[uid.String()]; !ok { 35 return 0 36 } 37 38 total := float64(len(r.uids)) 39 return ((total - float64(index)) / total) 40 } 41 42 type weightedRankedList struct { 43 *rankedList 44 weight float64 45 } 46 47 func newWeightedRankedList(rl *rankedList, weight float64) *weightedRankedList { 48 return &weightedRankedList{ 49 rankedList: rl, 50 weight: weight, 51 } 52 } 53 54 func (w *weightedRankedList) Weight() float64 { 55 return w.weight 56 } 57 58 type linearWeightedSelector struct { 59 libkb.Contextified 60 lists []*weightedRankedList 61 } 62 63 func newLinearWeightedSelector(g *libkb.GlobalContext, lists []*weightedRankedList) *linearWeightedSelector { 64 return &linearWeightedSelector{ 65 Contextified: libkb.NewContextified(g), 66 lists: lists, 67 } 68 } 69 70 func (w *linearWeightedSelector) getAllUIDs() (res []keybase1.UID) { 71 m := make(map[string]bool) 72 for _, list := range w.lists { 73 uids := list.UIDs() 74 for _, uid := range uids { 75 m[uid.String()] = true 76 } 77 } 78 for uid := range m { 79 res = append(res, keybase1.UID(uid)) 80 } 81 return res 82 } 83 84 func (w *linearWeightedSelector) Select(maxUsers int) (res []keybase1.UID) { 85 type userScore struct { 86 uid keybase1.UID 87 score float64 88 } 89 var scores []userScore 90 for _, uid := range w.getAllUIDs() { 91 total := 0.0 92 // Get score in each list to get a total score 93 for _, list := range w.lists { 94 total += list.Rank(uid) * list.Weight() 95 } 96 scores = append(scores, userScore{ 97 uid: uid, 98 score: total, 99 }) 100 } 101 102 sort.Slice(scores, func(i, j int) bool { return scores[i].score > scores[j].score }) 103 for _, score := range scores { 104 res = append(res, score.uid) 105 if len(res) > maxUsers { 106 break 107 } 108 } 109 return res 110 } 111 112 type interestingPeopleFn func(uid keybase1.UID) ([]keybase1.UID, error) 113 114 type interestingPeopleSource struct { 115 fn interestingPeopleFn 116 weight float64 117 } 118 119 type interestingPeople struct { 120 libkb.Contextified 121 sources []interestingPeopleSource 122 } 123 124 func newInterestingPeople(g *libkb.GlobalContext) *interestingPeople { 125 return &interestingPeople{ 126 Contextified: libkb.NewContextified(g), 127 } 128 } 129 130 func (i *interestingPeople) AddSource(fn interestingPeopleFn, weight float64) { 131 i.sources = append(i.sources, interestingPeopleSource{ 132 fn: fn, 133 weight: weight, 134 }) 135 } 136 137 func (i interestingPeople) Get(ctx context.Context, maxUsers int) ([]keybase1.UID, error) { 138 uid := i.G().Env.GetUID() 139 if uid.IsNil() { 140 return nil, libkb.LoginRequiredError{} 141 } 142 143 var weightedLists []*weightedRankedList 144 for _, source := range i.sources { 145 ppl, err := source.fn(uid) 146 if err != nil { 147 i.G().Log.Debug("interestingPeople: failed to get list from source: %s", err.Error()) 148 return nil, err 149 } 150 weightedLists = append(weightedLists, newWeightedRankedList(newRankedList(ppl), source.weight)) 151 } 152 153 return newLinearWeightedSelector(i.G(), weightedLists).Select(maxUsers), nil 154 }