下面给出一种 「加权总变差」(Weighted TVD) 算法 ——在保持线性时间复杂度的同时,对 “缺料 / 额外材料” 赋予更高惩罚,使其相似度必然 低于 “材料全对但比例偏差” 的情形。随后提供可直接拷贝的 C# 实现。
1 算法设计要点
| 场景 | 惩罚思路 |
|---|---|
| 比例偏差(配方所含材料,比例有误) | 按偏移量线性扣分 |
| 缺料(配方里有,但实际没放) | 额外乘以权重 λ (>1) |
| 额外材料(配方里无,但实际多放) | 同样乘以权重 λ |
只要 λ > 1,就能保证:
缺料 / 多料 ⇒ 分数一定比仅比例误差更低(其余条件相同)。
1.1 形式化定义
- 目标配方百分比向量
- 实际投入百分比
令
各项差异
设置惩罚权重 λ (,默认可取 2)。
加权总差
当两分布完全无交集时 ,
最大 Dmax=2λD_{\max}=2λ。
1.2 相似度得分
- 完全一致
- 完全不相关
- 只要出现缺料/多料, 会因 被放大 → 得分更低。
2 效果验证
| 场景 | 举例(%) | 结果 |
|---|---|---|
| 比例偏差配方:A 50/B 50实际:A 60/B 40 | → Score ≈ 95.0 | |
| 多放新料配方:A 50/B 50实际:A 45/B 45/C 10 | → ⇒ ⇒ Score = (1‑0.30/4)·100≈92.5比纯比例偏差更低 | |
| 缺少原料配方:A 80/B 20实际:A 100 | → Score≈85.0 |
3 C# 参考实现
using System;
using System.Collections.Generic;
using System.Linq;
public static class RecipeSimilarity
{
/// <summary>
/// 计算配方与实际投入的相似度 (0~100)。
/// </summary>
/// <param name="recipe">配方百分比,已归一化:{ingredient → fraction(0~1)}</param>
/// <param name="actual">实际投入:{ingredient → amount(>0)}</param>
/// <param name="penalty">缺料 / 额外材料的惩罚权重 λ,建议 1.5~3;若 =1 则退化为普通 TVD。</param>
public static double GetScore(
IDictionary<string, double> recipe,
IDictionary<string, double> actual,
double penalty = 2.0)
{
if (penalty <= 1.0) throw new ArgumentException("penalty 必须大于 1");
// 1) 归一化实际数据 → 百分比
double total = actual.Values.Sum();
if (total <= 0) return 0.0; // 没放材料,判 0 分
var p = actual.ToDictionary(kv => kv.Key, kv => kv.Value / total);
// 2) 计算三类差异
double dCom = 0, dMissing = 0, dExtra = 0;
foreach (var kv in recipe) // 处理共有 + 缺失
{
if (p.TryGetValue(kv.Key, out double pf))
{
dCom += Math.Abs(kv.Value - pf); // 共有材料比例差
}
else
{
dMissing += kv.Value; // 缺料
}
}
foreach (var kv in p) // 处理额外
{
if (!recipe.ContainsKey(kv.Key))
dExtra += kv.Value; // 多料
}
// 3) 加权总差
double D = dCom + penalty * (dMissing + dExtra);
// 4) 归一化得分
double score = Math.Max(0.0, 1.0 - D / (2 * penalty)) * 100.0;
return score;
}
}使用示例
var recipe = new Dictionary<string, double>
{
["DragonScale"] = 0.4,
["BatWing"] = 0.4,
["StarEssence"] = 0.2
};
var actual = new Dictionary<string, double>
{
["DragonScale"] = 20,
["BatWing"] = 40,
["RatTail"] = 40 // 额外材料
};
double score = RecipeSimilarity.GetScore(recipe, actual, penalty: 2);
Console.WriteLine($"相似度 = {score:F1}%"); // 输出 50.0%4 参数调优建议
| 目标 | 建议 λ |
|---|---|
| 比例误差与缺料/多料差不多严重 | 1.1 – 1.5 |
| 缺料/多料 明显更严重(多数配方场景) | 2 – 3 |
| 需要_极端_惩罚(炼金失败直接爆炸这类玩法) | ≥4 |
扩展方向
- 多级权重:对“关键原料”自定义 ;
- 容差区间 ε:先剪去 的细微误差;
- Tag‑Based 替代:把同类物品先聚合到功能 Tag,再做上述比较;
- 非线性衰减:最后分数可再做 γ 次方平滑,强化高分区分度。
这样即可满足「原料不对 → 分数更低」的要求,并保留易实现、易调参的优点。祝开发顺利!