mirror of
https://github.com/twitter/the-algorithm.git
synced 2024-06-17 16:48:47 +02:00
![twitter-team](/assets/img/avatar_default.png)
Please note we have force-pushed a new initial commit in order to remove some publicly-available Twitter user information. Note that this process may be required in the future.
83 lines
3.7 KiB
Scala
83 lines
3.7 KiB
Scala
package com.twitter.product_mixer.component_library.selector
|
|
|
|
import com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate
|
|
import com.twitter.product_mixer.component_library.model.candidate.IsPinnedFeature
|
|
import com.twitter.product_mixer.core.feature.featuremap.FeatureMapBuilder
|
|
import com.twitter.product_mixer.core.model.common.presentation.ItemCandidateWithDetails
|
|
import com.twitter.product_mixer.core.model.common.presentation.CandidatePipelines
|
|
import com.twitter.product_mixer.core.model.common.presentation.CandidateSources
|
|
import com.twitter.product_mixer.core.model.common.presentation.CandidateSourcePosition
|
|
|
|
/**
|
|
* Once a pair of duplicate candidates has been found we need to someone 'resolve' the duplication.
|
|
* This may be as simple as picking whichever candidate came first (see [[PickFirstCandidateMerger]]
|
|
* but this strategy could mean losing important candidate information. Candidates might, for
|
|
* example, have different features. [[CandidateMergeStrategy]] lets you define a custom behavior
|
|
* for resolving duplication to help support these more nuanced situations.
|
|
*/
|
|
trait CandidateMergeStrategy {
|
|
def apply(
|
|
existingCandidate: ItemCandidateWithDetails,
|
|
newCandidate: ItemCandidateWithDetails
|
|
): ItemCandidateWithDetails
|
|
}
|
|
|
|
/**
|
|
* Keep whichever candidate was encountered first.
|
|
*/
|
|
object PickFirstCandidateMerger extends CandidateMergeStrategy {
|
|
override def apply(
|
|
existingCandidate: ItemCandidateWithDetails,
|
|
newCandidate: ItemCandidateWithDetails
|
|
): ItemCandidateWithDetails = existingCandidate
|
|
}
|
|
|
|
/**
|
|
* Keep the candidate encountered first but combine all candidate feature maps.
|
|
*/
|
|
object CombineFeatureMapsCandidateMerger extends CandidateMergeStrategy {
|
|
override def apply(
|
|
existingCandidate: ItemCandidateWithDetails,
|
|
newCandidate: ItemCandidateWithDetails
|
|
): ItemCandidateWithDetails = {
|
|
// Prepend new because list set keeps insertion order, and last operations in ListSet are O(1)
|
|
val mergedCandidateSourceIdentifiers =
|
|
newCandidate.features.get(CandidateSources) ++ existingCandidate.features
|
|
.get(CandidateSources)
|
|
val mergedCandidatePipelineIdentifiers =
|
|
newCandidate.features.get(CandidatePipelines) ++ existingCandidate.features
|
|
.get(CandidatePipelines)
|
|
|
|
// the unitary features are pulled from the existing candidate as explained above, while
|
|
// Set Features are merged/accumulated.
|
|
val mergedCommonFeatureMap = FeatureMapBuilder()
|
|
.add(CandidatePipelines, mergedCandidatePipelineIdentifiers)
|
|
.add(CandidateSources, mergedCandidateSourceIdentifiers)
|
|
.add(CandidateSourcePosition, existingCandidate.sourcePosition)
|
|
.build()
|
|
|
|
existingCandidate.copy(features =
|
|
existingCandidate.features ++ newCandidate.features ++ mergedCommonFeatureMap)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Keep the pinnable candidate. For cases where we are dealing with duplicate entries across
|
|
* different candidate types, such as different sub-classes of
|
|
* [[com.twitter.product_mixer.component_library.model.candidate.BaseTweetCandidate]], we will
|
|
* prioritize the candidate with [[IsPinnedFeature]] because it contains additional information
|
|
* needed for the positioning of a pinned entry on a timeline.
|
|
*/
|
|
object PickPinnedCandidateMerger extends CandidateMergeStrategy {
|
|
override def apply(
|
|
existingCandidate: ItemCandidateWithDetails,
|
|
newCandidate: ItemCandidateWithDetails
|
|
): ItemCandidateWithDetails =
|
|
Seq(existingCandidate, newCandidate)
|
|
.collectFirst {
|
|
case candidate @ ItemCandidateWithDetails(_: BaseTweetCandidate, _, features)
|
|
if features.getTry(IsPinnedFeature).toOption.contains(true) =>
|
|
candidate
|
|
}.getOrElse(existingCandidate)
|
|
}
|