1

目标:从字典中获取一个值。所述值具有字典作为键。

我在做什么:我正在创建第二个字典,它的值与我试图获取的键的值完全相同。使用TryGetValue

结果:期望一个值但得到空值;

背景: 我正在尝试在 Unity 中制作制作功能。这就是工艺成分类的样子(ICombinable 现在看起来完全一样):

public class Ingredient : ICombinable
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public Effect Effect { get; set; }
    }

在实践中,我希望用户能够将 ICombinable 类型的对象拖到 UI(未实现)上并按下按钮将它们组合成一个新项目。例如,2 种药草和 1 杯水返回治疗药水(一种新物品)。

在表面后面,我会将拖动/选择的对象存储Dictionary<ICombinable, int>在 int 是 per 的数量ICombinable

在另一个类中,我正在存储另一个字典,它将保存所有的食谱。

public class Combiner
{
        private Dictionary<Dictionary<ICombinable, int>, ICraftable> _recipebook;

        public Combiner()
        {
            _recipebook = new Dictionary<Dictionary<ICombinable, int>, ICraftable>(); // <Recipe, Item it unlocks>
        }

        public void AddRecipe(Dictionary<ICombinable, int> recipe, ICraftable item) =>  _recipebook.Add(recipe, item);
        
        public ICraftable Craft(Dictionary<ICombinable, int> ingredientsAndAmount) =>
            _recipebook.TryGetValue(ingredientsAndAmount, out var item) == false ? null : item;
        //FirstOrDefault(x => x.Key.RequiredComponents.Equals(givenIngredients)).Value;
    }

_recipebook 的关键是由成分及其数量组成的实际配方。ICraftable 是与该配方对应的对象/项目。在我之前给出的示例中,ICraftable 是治疗药水,两根木棍和一杯水都是字典中的一个条目,它是该值的关键。

最后,Craft 方法需要一个字典(换句话说,一个成分列表及其数量),我希望它在 _recipebook 中检查与给定字典对应的项目。如果成分组合有效,则应返回一个项目,否则为 null。

我如何测试这个功能: 我刚开始这个项目,所以我想从单元测试开始。这是设置:

[Test]
    public void combiner_should_return_healing_potion()
    {
        // Use the Assert class to test conditions
        var combiner = new Combiner();
        var item = new Item
        {
            Name = "Healing Potion",
            Unlocked = false
        };

    
        combiner.AddRecipe(new Dictionary<ICombinable, int>
        {
            {new Ingredient {Name = "Herb", Description = "Has healing properties", Effect = Effect.Heal}, 3},
            {new Ingredient {Name = "Water", Description = "Spring water", Effect = default}, 1},
            {new Ingredient {Name = "Sugar", Description = "Sweetens", Effect = default}, 2}
        },
        item);

        var actualItem = combiner.Craft(new Dictionary<ICombinable, int>
        {
            {new Ingredient { Name = "Herb", Description = "Has healing properties", Effect = Effect.Heal} , 3},
            {new Ingredient {Name = "Water", Description = "Spring water", Effect = default}, 1},
            {new Ingredient {Name = "Sugar", Description = "Sweetens", Effect = default}, 2}
        });
        
        Assert.That(actualItem, Is.EqualTo(item));

    }

结果:

combiner_should_return_healing_potion (0.023s)
---
Expected: <Models.Item>
  But was:  null
---

我正在创建一个名为治疗药水的项目和一本应该是它的食谱的字典。我将这些添加到食谱书中。之后,我正在创建第二个字典来“模拟”用户的输入。该词典的内容与我使用 Add recipe() 添加到食谱书中的内容完全相同。为什么TryGetValue不认为这两个字典是平等的?

我该怎么做才能让它工作?

4

2 回答 2

0

因为您要查找的对象在字典中不存在。

您可以很容易地说服自己,只需遍历Keys集合并使用==Equals将每个关键字典与搜索到的字典进行比较。

那是

_recipebook.Count(x => x.Key == ingredientsAndAmount)

你会发现零匹配。

解决方案是提供您自己的Equalsand实现HashCode,或者IEqualityComparer按照评论中的建议,或者通过将键字典包装到Recipe提供它们的类中,并将其Recipe用作实际键。

于 2021-04-16T15:43:00.743 回答
0

解决了:

我尝试制作自己的 IEqualitycomparer,但无法让我的字典使用我在其中定义的 Equals。所以我创建了一个食谱类并覆盖了那里的 Equals。

public class Recipe : ICraftable
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public Dictionary<ICombinable, int> RequiredComponents { get; set; }
        public bool Unlocked { get; set; }

        public override bool Equals(object obj)
        {
            var otherDict = obj as Dictionary<ICombinable, int>;
            if (ReferenceEquals(otherDict, RequiredComponents)) return true;
            if (ReferenceEquals(otherDict, null)) return false;
            if (ReferenceEquals(RequiredComponents, null)) return false;
            if (otherDict.GetType() != RequiredComponents.GetType()) return false;
            return otherDict.Count == RequiredComponents.Count && AreValuesEqual(otherDict, RequiredComponents);
        }
        
        private bool AreValuesEqual(Dictionary<ICombinable, int> x, Dictionary<ICombinable, int> y)
        {
            var matches = 0;
            //Goal is to check if all ingredients have the same name on both sides. Then I'll check if they have the same amount
            foreach (var xKvp in x)
            {
                foreach (var yKvp in y)
                {
                    if (xKvp.Key.Name == yKvp.Key.Name && xKvp.Value == yKvp.Value)
                        matches++;
                }
            }
            return matches == x.Count;
        }
    }

我从使用 IEqualityComparer 获得的默认 Equals 中复制了前 4 行,并为第 5 行做了一个自定义。我的 AreValuesEqual bool 只是按名称和计数检查所有成分是否存在于其他字典中。

我更改了我的 Combiner 类以使用 Recipe 对象而不是 Dictionary 作为键,并相应地调整了 AddRecipe 和 Craft 的方法:

public class Combiner
    {
        private Dictionary<Recipe, ICraftable> _recipebook;

        public Combiner()
        {
            _recipebook = new Dictionary<Recipe, ICraftable>(); // <Recipe, Item it unlocks>
        }

        public void AddRecipe(Recipe recipe, ICraftable item) =>  _recipebook.Add(recipe, item);
        
        public ICraftable Craft(Dictionary<ICombinable, int> ingredientsAndAmount) =>
            _recipebook.FirstOrDefault(kvp=> kvp.Key.Equals(ingredientsAndAmount)).Value;
    }

这就是我设置单元测试的方式:

[Test]
    public void combiner_should_return_healing_potion()
    {
        // Use the Assert class to test conditions
        var combiner = new Combiner();
        var potion = new Item
        {
            Name = "Healing Potion",
            Unlocked = false
        };

        combiner.AddRecipe(new Recipe
            {
                Name = "Healing potion recipe",
                Description = "Invented by some sage",
                RequiredComponents = new Dictionary<ICombinable, int>
                {
                    {new Ingredient() { Name = "Herb", Description = "Has healing properties", Effect = Effect.Heal} , 3},
                    {new Ingredient {Name = "Water", Description = "Spring water", Effect = default}, 1},
                    {new Ingredient {Name = "Sugar", Description = "Sweetens", Effect = default}, 2}

                }
            },
            potion);

        var userGivenIngredientsAndAmount = combiner.Craft(new Dictionary<ICombinable, int>()
        {
            {new Ingredient() { Name = "Herb", Description = "Has healing properties", Effect = Effect.Heal} , 3},
            {new Ingredient {Name = "Water", Description = "Spring water", Effect = default}, 1},
            {new Ingredient {Name = "Sugar", Description = "Sweetens", Effect = default}, 2}
        });
        
        Assert.That(userGivenIngredientsAndAmount, Is.EqualTo(potion));
    }

它按预期运行。更改其中一个字典中的名称或计数会导致它返回 null,就像它应该的那样。这可能非常低效!但我仍处于“让它发挥作用”阶段。我很快就会'让它变得更好,让它变得更快'。

感谢大家让我走上正轨!我很乐意接受有关提高效率的任何建议。但由于它按预期工作,我将这个问题标记为明天已解决。

于 2021-04-17T13:33:05.887 回答