/// <summary> /// Judges a set of recipes based on a scale and its efficiency with regards to the current pantry. The lower the score, the better. /// </summary> double GetScore(RecipeNode[] currentSet, byte scale) { double wasted = 0; //Add 1.0 for ingredients that don't exist in pantry, add percentage of leftover otherwise float avgRating = 0; //Average rating for all recipes in the set (0-4) float tagPoints = 0; //Point for each tag that's one of our favorites float tagTotal = 0; //Total number of tags in all recipes float ingPoints = 0; //Point for each ing that's one of our favorites float ingTotal = 0; //Total number of ingrediets in all recipes for (var i = 0; i < currentSet.Length; i++) { var recipe = currentSet[i]; var ingredients = (IngredientUsage[]) recipe.Ingredients; //Add points for any favorite tags this recipe uses tagTotal += recipe.Tags.Length; ingTotal += ingredients.Length; for (var t = 0; t < recipe.Tags.Length; t++) //TODO: Use bitmasks for storing recipe tags and fav tags, then count bits { if (favTags[t]) { tagPoints++; } } byte realRating; //Real rating is the user's rating, else the public rating, else 3. if (!ratings.TryGetValue(recipe, out realRating)) { realRating = (recipe.Rating == 0) ? (byte) 3 : recipe.Rating; //if recipe has no ratings, use average rating of 3. } avgRating += (realRating - 1); for (var j = 0; j < ingredients.Length; j++) { var ingredient = ingredients[j]; //Add points for any favorite ingredients this recipe uses var ingKey = ingredient.Ingredient.Key; for (var k = 0; k < favIngs.Length; k++) //For loop is actually faster than 5 "if" statements { if (favIngs[k] == ingKey) { ingPoints++; break; } } IngredientUsage curUsage; var fContains = totals.TryGetValue(ingredient.Ingredient, out curUsage); if (!fContains) { curUsage = new IngredientUsage(); curUsage.Amt = ingredient.Amt; totals.Add(ingredient.Ingredient, curUsage); } else { curUsage.Amt += ingredient.Amt; } } } if (profile.Pantry != null) //If profile has a pantry, figure out how much of it is wasted { //For each pantry ingredient that we're not using, punish the score by MISSING_ING_PUNISH amount. var pEnum = pantryAmounts.GetEnumerator(); while (pEnum.MoveNext()) { if (!totals.ContainsKey(pEnum.Current.Key)) { wasted += MISSING_ING_PUNISH; } } var e = totals.GetEnumerator(); while (e.MoveNext()) { var curKey = e.Current.Key; float? have; if (pantryAmounts.TryGetValue(curKey, out have)) //We have this in our pantry { if (!have.HasValue) //We have this in our pantry, but no amount is specified - So we "act" like we have whatever we need { continue; } if (!e.Current.Value.Amt.HasValue) //This recipe doesn't specify an amount - So we "act" like we use half of what we have { wasted += EMPTY_RECIPE_AMOUNT; continue; } var need = e.Current.Value.Amt.Value; var ratio = 1 - ((have.Value - need)/have.Value); //Percentage of how much you're using of what you have if (ratio > 1) //If you need more than you have, add the excess ratio to the waste but don't go over the punishment for not having the ingredient at all { wasted += Math.Min(ratio, NEW_ING_PUNISH); } else { wasted += (1 - ratio); } } else { wasted += NEW_ING_PUNISH; //For each ingredient this meal set needs that we don't have, increment by NEW_ING_PUNISH } } } double worstScore, trendScore, efficiencyScore; if (profile.Pantry == null) //No pantry, efficiency is defined by the overlap of ingredients across recipes { efficiencyScore = totals.Keys.Count/ingTotal; } else //Efficiency is defined by how efficient the pantry ingredients are utilized { worstScore = ((totals.Keys.Count*NEW_ING_PUNISH) + (profile.Pantry.Length*MISSING_ING_PUNISH)); //Worst possible efficiency score efficiencyScore = (wasted/worstScore); } avgRating /= currentSet.Length; trendScore = 1 - ((((avgRating/4)*4) + (tagPoints/tagTotal) + (ingPoints/ingTotal))/6); totals.Clear(); if (scale == 1) return efficiencyScore; else if (scale == 2) return (efficiencyScore + efficiencyScore + trendScore)/3; else if (scale == 3) return (efficiencyScore + trendScore)/2; else if (scale == 4) return (efficiencyScore + trendScore + trendScore)/3; else if (scale == 5) return trendScore; return 0; }
/// <summary> /// Judges a set of recipes based on a scale and its efficiency with regards to the current pantry. The lower the score, the better. /// </summary> private double GetScore(RecipeNode[] currentSet, byte scale) { double wasted = 0; // Add 1.0 for ingredients that don't exist in pantry, add percentage of leftover otherwise float averageRating = 0; // Average rating for all recipes in the set (0-4) float tagPoints = 0; // Point for each tag that's one of our favorites float tagTotal = 0; // Total number of tags in all recipes float ingredientPoints = 0; // Point for each ingredient that's one of our favorites float ingredientTotal = 0; // Total number of ingrediets in all recipes for (var i = 0; i < currentSet.Length; i++) { var recipe = currentSet[i]; var ingredients = (IngredientUsage[])recipe.Ingredients; // Add points for any favorite tags this recipe uses tagTotal += recipe.Tags.Length; ingredientTotal += ingredients.Length; // TODO: Use bitmasks for storing recipe tags and fav tags, then count bits for (int tag = 0; tag < recipe.Tags.Length; tag++) { if (this.favoriteTags[tag]) { tagPoints++; } } // Real rating is the user's rating, else the public rating, else 3. byte realRating; if (!this.ratings.TryGetValue(recipe, out realRating)) { // if recipe has no ratings, use average rating of 3. realRating = (recipe.Rating == 0) ? (byte)3 : recipe.Rating; } averageRating += realRating - 1; for (var j = 0; j < ingredients.Length; j++) { var ingredient = ingredients[j]; // Add points for any favorite ingredients this recipe uses var ingredientKey = ingredient.Ingredient.Key; for (var key = 0; key < this.favoriteIngredients.Length; key++) { if (this.favoriteIngredients[key] == ingredientKey) { ingredientPoints++; break; } } IngredientUsage currentUsage; bool fContains = this.totals.TryGetValue(ingredient.Ingredient, out currentUsage); if (!fContains) { currentUsage = new IngredientUsage(); currentUsage.Amount = ingredient.Amount; this.totals.Add(ingredient.Ingredient, currentUsage); } else { currentUsage.Amount += ingredient.Amount; } } } // If profile has a pantry, figure out how much of it is wasted if (this.profile.Pantry != null) { // For each pantry ingredient that we're not using, punish the score by MissingIngredientPunish amount. var pantryEnumerator = this.pantryAmounts.GetEnumerator(); while (pantryEnumerator.MoveNext()) { if (!this.totals.ContainsKey(pantryEnumerator.Current.Key)) { wasted += MissingIngredientPunish; } } var enumerator = this.totals.GetEnumerator(); while (enumerator.MoveNext()) { var currentKey = enumerator.Current.Key; float? haveAmount; // We have this in our pantry if (this.pantryAmounts.TryGetValue(currentKey, out haveAmount)) { // We have this in our pantry, but no amount is specified - So we "act" like we have whatever we need if (!haveAmount.HasValue) { continue; } // This recipe doesn't specify an amount - So we "act" like we use half of what we have if (!enumerator.Current.Value.Amount.HasValue) { wasted += EmptyRecipeAmount; continue; } float needAmount = enumerator.Current.Value.Amount.Value; // Percentage of how much you're using of what you have float ratio = 1 - ((haveAmount.Value - needAmount) / haveAmount.Value); // If you need more than you have, add the excess ratio to the waste but don't go over the punishment for not having the ingredient at all if (ratio > 1) { wasted += Math.Min(ratio, NewIngredientPunish); } else { wasted += 1 - ratio; } } else { // For each ingredient this meal set needs that we don't have, increment by NewIngredientPunish wasted += NewIngredientPunish; } } } double worstScore; double trendScore; double efficiencyScore; // No pantry, efficiency is defined by the overlap of ingredients across recipes if (this.profile.Pantry == null) { efficiencyScore = this.totals.Keys.Count / ingredientTotal; } else { // Efficiency is defined by how efficient the pantry ingredients are utilized worstScore = this.totals.Keys.Count * NewIngredientPunish + this.profile.Pantry.Length * MissingIngredientPunish; // Worst possible efficiency score efficiencyScore = wasted / worstScore; } averageRating /= currentSet.Length; trendScore = 1 - ((averageRating + tagPoints / tagTotal + ingredientPoints / ingredientTotal) / 6); this.totals.Clear(); switch (scale) { case 1: return efficiencyScore; case 2: return (efficiencyScore + efficiencyScore + trendScore) / 3; case 3: return (efficiencyScore + trendScore) / 2; case 4: return (efficiencyScore + trendScore + trendScore) / 3; case 5: return trendScore; default: return 0; } }
/// <summary> /// Judges a set of recipes based on a scale and its efficiency with regards to the current pantry. The lower the score, the better. /// </summary> public double GetScore(RecipeNode[] currentSet, byte scale) { double wasted = 0; // Add 1.0 for ingredients that don't exist in pantry, add percentage of leftover otherwise float avgRating = 0; // Average rating for all recipes in the set (0-4) float tagPoints = 0; // Point for each tag that's one of our favorites float tagTotal = 0; // Total number of tags in all recipes float ingPoints = 0; // Point for each ing that's one of our favorites float ingTotal = 0; // Total number of ingrediets in all recipes for (var i = 0; i < currentSet.Length; i++) { var recipe = currentSet[i]; var ingredients = (IngredientUsage[])recipe.Ingredients; // Add points for any favorite tags this recipe uses tagTotal += recipe.Tags.Length; ingTotal += ingredients.Length; for (var t = 0; t < recipe.Tags.Length; t++) { if (this.favTags[t]) { tagPoints++; } } byte realRating; // Real rating is the user's rating, else the public rating, else 3. if (!this.ratings.TryGetValue(recipe, out realRating)) { realRating = (recipe.Rating == 0) ? (byte)3 : recipe.Rating; // if recipe has no ratings, use average rating of 3. } avgRating += realRating - 1; foreach (var ingredient in ingredients) { // Add points for any favorite ingredients this recipe uses var ingKey = ingredient.Ingredient.Key; if (this.favIngs.Any(t => t == ingKey)) { ingPoints++; } IngredientUsage curUsage; var fContains = this.totals.TryGetValue(ingredient.Ingredient, out curUsage); if (!fContains) { curUsage = new IngredientUsage(); curUsage.Amt = ingredient.Amt; this.totals.Add(ingredient.Ingredient, curUsage); } else { curUsage.Amt += ingredient.Amt; } } } // If profile has a pantry, figure out how much of it is wasted if (this.profile.Pantry != null) { // For each pantry ingredient that we're not using, punish the score by MISSING_ING_PUNISH amount. var pEnum = this.pantryAmounts.GetEnumerator(); while (pEnum.MoveNext()) { if (!this.totals.ContainsKey(pEnum.Current.Key)) { wasted += MissingIngPunish; } } var e = this.totals.GetEnumerator(); while (e.MoveNext()) { var curKey = e.Current.Key; float? have; // We have this in our pantry if (this.pantryAmounts.TryGetValue(curKey, out have)) { // We have this in our pantry, but no amount is specified - So we "act" like we have whatever we need if (!have.HasValue) { continue; } // This recipe doesn't specify an amount - So we "act" like we use half of what we have if (!e.Current.Value.Amt.HasValue) { wasted += EmptyRecipeAmount; continue; } var need = e.Current.Value.Amt.Value; var ratio = 1 - ((have.Value - need) / have.Value); // Percentage of how much you're using of what you have // If you need more than you have, add the excess ratio to the waste but don't go over the punishment for not having the ingredient at all if (ratio > 1) { wasted += Math.Min(ratio, NewIngPunish); } else { wasted += 1 - ratio; } } else { wasted += NewIngPunish; // For each ingredient this meal set needs that we don't have, increment by NEW_ING_PUNISH } } } double efficiencyScore; // No pantry, efficiency is defined by the overlap of ingredients across recipes if (this.profile.Pantry == null) { efficiencyScore = totals.Keys.Count / ingTotal; } else { // Efficiency is defined by how efficient the pantry ingredients are utilized double worstScore = (this.totals.Keys.Count * NewIngPunish) + (this.profile.Pantry.Length * MissingIngPunish); efficiencyScore = wasted / worstScore; } avgRating /= currentSet.Length; double trendScore = 1 - ((((avgRating / 4) * 4) + (tagPoints / tagTotal) + (ingPoints / ingTotal)) / 6); this.totals.Clear(); switch (scale) { case 1: return efficiencyScore; case 2: return (efficiencyScore + efficiencyScore + trendScore) / 3; case 3: return (efficiencyScore + trendScore) / 2; case 4: return (efficiencyScore + trendScore + trendScore) / 3; case 5: return trendScore; } return 0; }
/// <summary> /// Judges a set of recipes based on a scale and its efficiency with regards to the current pantry. The lower the score, the better. /// </summary> public double GetScore(RecipeNode[] currentSet, byte scale) { double wasted = 0; // Add 1.0 for ingredients that don't exist in pantry, add percentage of leftover otherwise float avgRating = 0; // Average rating for all recipes in the set (0-4) float tagPoints = 0; // Point for each tag that's one of our favorites float tagTotal = 0; // Total number of tags in all recipes float ingPoints = 0; // Point for each ing that's one of our favorites float ingTotal = 0; // Total number of ingrediets in all recipes for (var i = 0; i < currentSet.Length; i++) { var recipe = currentSet[i]; var ingredients = (IngredientUsage[])recipe.Ingredients; // Add points for any favorite tags this recipe uses tagTotal += recipe.Tags.Length; ingTotal += ingredients.Length; for (var t = 0; t < recipe.Tags.Length; t++) { if (this.favTags[t]) { tagPoints++; } } byte realRating; // Real rating is the user's rating, else the public rating, else 3. if (!this.ratings.TryGetValue(recipe, out realRating)) { realRating = (recipe.Rating == 0) ? (byte)3 : recipe.Rating; // if recipe has no ratings, use average rating of 3. } avgRating += realRating - 1; foreach (var ingredient in ingredients) { // Add points for any favorite ingredients this recipe uses var ingKey = ingredient.Ingredient.Key; if (this.favIngs.Any(t => t == ingKey)) { ingPoints++; } IngredientUsage curUsage; var fContains = this.totals.TryGetValue(ingredient.Ingredient, out curUsage); if (!fContains) { curUsage = new IngredientUsage(); curUsage.Amt = ingredient.Amt; this.totals.Add(ingredient.Ingredient, curUsage); } else { curUsage.Amt += ingredient.Amt; } } } // If profile has a pantry, figure out how much of it is wasted if (this.profile.Pantry != null) { // For each pantry ingredient that we're not using, punish the score by MISSING_ING_PUNISH amount. var pEnum = this.pantryAmounts.GetEnumerator(); while (pEnum.MoveNext()) { if (!this.totals.ContainsKey(pEnum.Current.Key)) { wasted += MissingIngPunish; } } var e = this.totals.GetEnumerator(); while (e.MoveNext()) { var curKey = e.Current.Key; float?have; // We have this in our pantry if (this.pantryAmounts.TryGetValue(curKey, out have)) { // We have this in our pantry, but no amount is specified - So we "act" like we have whatever we need if (!have.HasValue) { continue; } // This recipe doesn't specify an amount - So we "act" like we use half of what we have if (!e.Current.Value.Amt.HasValue) { wasted += EmptyRecipeAmount; continue; } var need = e.Current.Value.Amt.Value; var ratio = 1 - ((have.Value - need) / have.Value); // Percentage of how much you're using of what you have // If you need more than you have, add the excess ratio to the waste but don't go over the punishment for not having the ingredient at all if (ratio > 1) { wasted += Math.Min(ratio, NewIngPunish); } else { wasted += 1 - ratio; } } else { wasted += NewIngPunish; // For each ingredient this meal set needs that we don't have, increment by NEW_ING_PUNISH } } } double efficiencyScore; // No pantry, efficiency is defined by the overlap of ingredients across recipes if (this.profile.Pantry == null) { efficiencyScore = totals.Keys.Count / ingTotal; } else { // Efficiency is defined by how efficient the pantry ingredients are utilized double worstScore = (this.totals.Keys.Count * NewIngPunish) + (this.profile.Pantry.Length * MissingIngPunish); efficiencyScore = wasted / worstScore; } avgRating /= currentSet.Length; double trendScore = 1 - ((((avgRating / 4) * 4) + (tagPoints / tagTotal) + (ingPoints / ingTotal)) / 6); this.totals.Clear(); switch (scale) { case 1: return(efficiencyScore); case 2: return((efficiencyScore + efficiencyScore + trendScore) / 3); case 3: return((efficiencyScore + trendScore) / 2); case 4: return((efficiencyScore + trendScore + trendScore) / 3); case 5: return(trendScore); } return(0); }