Skip to content

Instantly share code, notes, and snippets.

@wazeerc
Last active October 12, 2025 09:49
Show Gist options
  • Select an option

  • Save wazeerc/0f84028175ea050ba6c6c07998b4b6b6 to your computer and use it in GitHub Desktop.

Select an option

Save wazeerc/0f84028175ea050ba6c6c07998b4b6b6 to your computer and use it in GitHub Desktop.
Algorithm used to generate a number of recommendations based on selected items
/**
* Represents any item that can be identified.
*/
interface Identifiable {
id: any;
}
/**
* A generic and advanced recommendation algorithm that works with any data type.
* It uses a content-based filtering approach based on feature extraction and cosine similarity.
*
* @template T - The type of the items, which must have an 'id' property.
* @param allItems - An array of all available items.
* @param selectedItems - An array of items the user has selected.
* @param featureExtractor - A function that converts an item of type T into a numerical vector (number[]).
* @param numberOfRecommendations - The desired number of recommendations.
* @returns An array of recommended items of type T.
*/
const generateGenericRecommendations = <T extends Identifiable>(
allItems: T[],
selectedItems: T[],
featureExtractor: (item: T) => number[],
numberOfRecommendations: number = 3
): T[] => {
if (selectedItems.length === 0) {
// Return a default set of items if no selection has been made.
return allItems.slice(0, numberOfRecommendations);
}
// 1. Vectorize all items using the provided feature extractor.
const itemVectors = new Map<any, number[]>(
allItems.map(item => [item.id, featureExtractor(item)])
);
// 2. Create a user profile vector by averaging the vectors of selected items.
const selectedVectors = selectedItems.map(item => itemVectors.get(item.id)!);
const userProfileVector: number[] = selectedVectors[0].map((_, i) =>
selectedVectors.reduce((sum, vec) => sum + vec[i], 0) / selectedVectors.length
);
/**
* Calculates the cosine similarity between two numerical vectors.
* @param vecA - The first vector.
* @param vecB - The second vector.
* @returns The cosine similarity score (between -1 and 1).
*/
const cosineSimilarity = (vecA: number[], vecB: number[]): number => {
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < vecA.length; i++) {
dotProduct += (vecA[i] || 0) * (vecB[i] || 0);
normA += (vecA[i] || 0) ** 2;
normB += (vecB[i] || 0) ** 2;
}
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
return denominator === 0 ? 0 : dotProduct / denominator;
};
const selectedItemIds = new Set(selectedItems.map(item => item.id));
// 3. Calculate similarity scores for all unselected items.
const recommendationsWithScores = allItems
.filter(item => !selectedItemIds.has(item.id))
.map(item => {
const itemVector = itemVectors.get(item.id)!;
const similarity = cosineSimilarity(userProfileVector, itemVector);
return { item, similarity };
});
// 4. Sort by similarity and return the top recommendations.
recommendationsWithScores.sort((a, b) => b.similarity - a.similarity);
return recommendationsWithScores.slice(0, numberOfRecommendations).map(rec => rec.item);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment