module/realtime_user2item_dao.go (160 lines of code) (raw):
package module
import (
"strconv"
"strings"
"github.com/alibaba/pairec/v2/context"
"github.com/alibaba/pairec/v2/recconf"
)
type RealTimeUser2ItemDao interface {
ListItemsByUser(user *User, context *context.RecommendContext) []*Item
GetTriggers(user *User, context *context.RecommendContext) map[string]float64
GetTriggerInfos(user *User, context *context.RecommendContext) []*TriggerInfo
}
func NewRealTimeUser2ItemDao(config recconf.RecallConfig) RealTimeUser2ItemDao {
if config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.AdapterType == recconf.DaoConf_Adapter_Hologres {
if config.RealTimeUser2ItemDaoConf.Item2XTable != "" && config.RealTimeUser2ItemDaoConf.X2ItemTable != "" {
return NewRealtimeUser2Item2X2ItemHologresDao(config)
}
return NewRealtimeUser2ItemHologresDao(config)
} else if config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.AdapterType == recconf.DataSource_Type_BE {
return NewRealtimeUser2ItemBeDao(config)
} else if config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.AdapterType == recconf.DataSource_Type_FeatureStore {
return NewRealtimeUser2ItemFeatureStoreDao(config)
} else {
panic("RealTimeUser2ItemDao not implement")
}
}
type ItemScoreSlice []*Item
func (us ItemScoreSlice) Len() int {
return len(us)
}
func (us ItemScoreSlice) Less(i, j int) bool {
return us[i].Score < us[j].Score
}
func (us ItemScoreSlice) Swap(i, j int) {
tmp := us[i]
us[i] = us[j]
us[j] = tmp
}
func uniqItems(items []*Item) (ret []*Item) {
uniq := make(map[ItemId]bool, len(items))
for _, item := range items {
if _, ok := uniq[item.Id]; !ok {
uniq[item.Id] = true
ret = append(ret, item)
}
}
return
}
type RealtimeUser2ItemBaseDao struct {
recallCount int
triggerCount int
limit int
recallName string
propertyFields []string
propertyFieldMap map[string]int
diversityRules []recconf.TriggerDiversityRuleConfig
eventPlayTimeMap map[string]float64
eventWeightMap map[string]float64
}
func NewRealtimeUser2ItemBaseDao(config *recconf.RecallConfig) *RealtimeUser2ItemBaseDao {
dao := &RealtimeUser2ItemBaseDao{
recallName: config.Name,
recallCount: config.RecallCount,
diversityRules: config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.DiversityRules,
propertyFields: config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.PropertyFields,
propertyFieldMap: make(map[string]int, len(config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.PropertyFields)),
triggerCount: config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.TriggerCount,
limit: config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.Limit,
eventPlayTimeMap: make(map[string]float64),
eventWeightMap: make(map[string]float64),
}
if dao.triggerCount == 0 {
dao.triggerCount = dao.limit
}
if config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.EventPlayTime != "" {
playTimes := strings.Split(config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.EventPlayTime, ";")
for _, eventTime := range playTimes {
strs := strings.Split(eventTime, ":")
if len(strs) == 2 {
if t, err := strconv.ParseFloat(strs[1], 64); err == nil {
dao.eventPlayTimeMap[strs[0]] = t
}
}
}
}
if config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.EventWeight != "" {
weights := strings.Split(config.RealTimeUser2ItemDaoConf.UserTriggerDaoConf.EventWeight, ";")
for _, weight := range weights {
strs := strings.Split(weight, ":")
if len(strs) == 2 {
if t, err := strconv.ParseFloat(strs[1], 64); err == nil {
dao.eventWeightMap[strs[0]] = t
}
}
}
}
if len(dao.propertyFields) > 0 {
for i, field := range dao.propertyFields {
dao.propertyFieldMap[field] = i
}
}
return dao
}
func (d *RealtimeUser2ItemBaseDao) DiversityTriggers(triggers []*TriggerInfo) []*TriggerInfo {
if len(d.diversityRules) == 0 {
return triggers
}
length := len(triggers)
if length == 0 {
return triggers
}
var diversityRules []*TriggerDiversityRule
for _, config := range d.diversityRules {
rule := NewTriggerDiversityRule(config)
diversityRules = append(diversityRules, rule)
}
diversitySize := d.triggerCount
if diversitySize > length {
diversitySize = length
}
var triggerResult []*TriggerInfo
alreadyMatch := make(map[string]bool, diversitySize)
//alreadyMatch[triggers[0].itemId] = true
//triggerResult = append(triggerResult, triggers[0])
//triggers = triggers[1:]
index := 0
for len(triggerResult) <= diversitySize {
if index == length {
break
}
flag := true
// if all the rest items not match diversity rule, use the first item append to the result
firstIndex := -1
for i, trigger := range triggers {
if _, ok := alreadyMatch[trigger.ItemId]; ok {
continue
}
if firstIndex == -1 {
firstIndex = i
}
flag = true
for _, rule := range diversityRules {
if flag = rule.Match(trigger, d.propertyFieldMap, triggerResult); !flag {
break
}
}
// if the item match all the diversity rule, so add it to the result
if flag {
alreadyMatch[trigger.ItemId] = true
triggerResult = append(triggerResult, trigger)
index++
for _, rule := range diversityRules {
rule.AddDimensionValue(trigger, d.propertyFieldMap)
}
break
}
}
if !flag {
alreadyMatch[triggers[firstIndex].ItemId] = true
triggerResult = append(triggerResult, triggers[firstIndex])
index++
}
}
return triggerResult
}