func dumpSetOp(conn *client.Conn, args []string) { op, err := setop.NewSetOpParser(args[2]).Parse() if err != nil { fmt.Println(err) } else { for _, res := range conn.SetExpression(setop.SetExpression{Dest: []byte(args[1]), Op: op}) { printSetOpRes(res) } } }
func getRecommendations(w http.ResponseWriter, r *http.Request, c *client.Conn) { uid := mux.Vars(r)["user_id"] var request common.RecommendationsRequest if err := json.NewDecoder(r.Body).Decode(&request); err != nil { panic(err) } // Create a set operation that returns the union of the likers of all objects we have liked, just returning the user key likersOp := &setop.SetOp{ Merge: setop.First, Type: setop.Union, } // For each object we have liked, add the likers of that flavor as a source to the union of likers for _, obj := range c.Slice(uLikesKey(uid), nil, nil, true, true) { likersOp.Sources = append(likersOp.Sources, setop.SetOpSource{Key: oLikesKey(string(obj.Key))}) } // Create a set operation that returns the union of all things liked by all likers of objects we liked, returning the sum of the likes for each object objectsOp := &setop.SetOp{ Merge: setop.FloatSum, Type: setop.Union, } // For each user in the union of users having liked something we like for _, user := range c.SetExpression(setop.SetExpression{ Op: likersOp, }) { // If the user is not us if string(user.Key) != uid { // Fetch the number of objects we have both liked similarity := len(c.SetExpression(setop.SetExpression{ Code: fmt.Sprintf("(I:First %v %v)", string(uLikesKey(string(user.Key))), string(uLikesKey(uid))), })) // And weight the user according to how many commonalities we have weight := math.Log(float64(similarity + 1)) // Add the objects liked by this user, weighed this much, as a source objectsOp.Sources = append(objectsOp.Sources, setop.SetOpSource{Key: uLikesKey(string(user.Key)), Weight: &weight}) } } // If we want to filter on active objects if request.Actives != "" { // Create an operation on the simple Union and the active objects objectsOp = &setop.SetOp{ Merge: setop.First, Sources: []setop.SetOpSource{ setop.SetOpSource{ SetOp: objectsOp, }, setop.SetOpSource{ Key: activeObjectsKey, }, }, } // And make it of the correct type if request.Actives == common.Reject { objectsOp.Type = setop.Difference } else if request.Actives == common.Intersect { objectsOp.Type = setop.Intersection } } // If we want to filter on viewed objects if request.Viewed != "" { // Create an operation on the previous operation and the viewed objects objectsOp = &setop.SetOp{ Merge: setop.First, Sources: []setop.SetOpSource{ setop.SetOpSource{ SetOp: objectsOp, }, setop.SetOpSource{ Key: uViewsKey(uid), }, }, } // And make it of the correct type if request.Viewed == common.Reject { objectsOp.Type = setop.Difference } else if request.Viewed == common.Intersect { objectsOp.Type = setop.Intersection } } // Finally, fetch the wanted number of recommendations var result []common.Message for _, item := range c.SetExpression(setop.SetExpression{ Op: objectsOp, }) { w := godCommon.MustDecodeFloat64(item.Values[0]) i := sort.Search(len(result), func(i int) bool { return w > result[i].Weight }) if i < len(result) { if len(result) < request.Num { result = append(result[:i], append([]common.Message{common.Message{ Object: string(item.Key), Weight: w, }}, result[i:]...)...) } else { if i > 0 { result = append(result[:i], append([]common.Message{common.Message{ Object: string(item.Key), Weight: w, }}, result[i:len(result)-1]...)...) } else { result = append([]common.Message{common.Message{ Object: string(item.Key), Weight: w, }}, result[i:len(result)-1]...) } } } else { if len(result) < request.Num { result = append(result, common.Message{ Object: string(item.Key), Weight: w, }) } } } w.Header().Set("Content-Type", "application/json; charset=UTF-8") if err := json.NewEncoder(w).Encode(result); err != nil { panic(err) } }