我一直在干扰C#中的一个想法来创建一个软件(用于伪个人用法),但遇到了实现问题。嗯......也许他们是设计我不知道的问题,但我根本无法弄明白。
考虑我们有以下“数据库”:
Foo + Bar = Foobar
Baz + Qux = Bazqux
Foobar + Bazqux = Foobarbazqux
这些食谱用于创建所述项目。使用一个Foo
和一个Bar
,我们会为另一个配方创建一个Foobar
结果,它可以进一步成为成分
现在认为我们需要找出项目Foobarbazqux
的完整配方。对于人类的思想和智慧,它相对容易:
We need Foobarbazqux
Foobarbazqux = Foobar + Bazqux
Foobar = Foo + Bar
Bazqux = Baz + Qux
Foobarbazqux = Foo + Bar + Baz + Qux
当然,如果我们拥有所有成分,我们可以“运行”食谱并通过执行每个食谱创建项目以上几段的顺序。 (所以:我们首先创建Foobar
,然后Bazqux
,然后将两者合并以获得最终结果。)
但实时,数据库的强度很多。我认为,这就是计算机应该进入的地方。通过一个可用的软件,机器可以轻松找到所有成分和执行(制作)步骤并将其提供给用户,因此我们不需要通读 ALL 手工输入。
我考虑过使用C#来实现这个软件。命令行项目,没有任何闪亮或任何东西,只有纯stdio
。
首先,当然,我需要定义项。 (注意:现在我考虑使用结构体,但如果需要,我可以将它们“升级”为类。“数据库”是从某种顺序文本文件初始化的,稍后将对其进行初始化。在执行期间或之后,“数据库”不会以任何方式被更改或写入,一旦加载,它就是只读的。)当然,我需要定义配方。
struct Item
{
public string Name;
}
struct Recipe
{
public Item Result;
public Item[] Ingredients;
}
这两个结构包含在static
(默认类)的通用List<>
Program
字段中。
在入口点,我们定义了一些项目和一些测试方法:
items.Add(new Item { Name = "Foo" });
items.Add(new Item { Name = "Bar" });
items.Add(new Item { Name = "Baz" });
items.Add(new Item { Name = "Qux" });
items.Add(new Item { Name = "Foobar" });
items.Add(new Item { Name = "Bazqux" });
items.Add(new Item { Name = "Foobarbazqux" });
recipes.Add(new Recipe
{
Result = GetItem("Foobar"),
Ingredients = new Item[] { GetItem("Foo"), GetItem("Bar") }
});
recipes.Add(new Recipe
{
Result = GetItem("Bazqux"),
Ingredients = new Item[] { GetItem("Baz"), GetItem("Qux") }
});
recipes.Add(new Recipe
{
Result = GetItem("Foobarbazqux"),
Ingredients = new Item[] { GetItem("Foobar"), GetItem("Bazqux") }
});
由于项目是Name
字段的主键,GetItem
只是一个List.Find<>
快捷键:
private static Item GetItem(string _name)
{
return Program.Items.Find(match => match.Name == _name);
}
现在,数据库已设置完毕。我已经意识到我需要制作某种递归方法来获取所有成分,但这就是我遇到问题的地方。
考虑以下,第一次尝试(这不是递归,只是“一级深”):
string search = "Foobarbazqux";
SearchRecipe(search);
private static void SearchRecipe(string _search)
{
List<Recipe> item_recipes = Program.Recipes.FindAll(match => match.result.Name == GetItem(search).Name);
foreach (Recipe recp in item_recipes)
{
Console.WriteLine("-------------");
Console.WriteLine("Ingredients: ");
foreach (Item ingredient in recp.Ingredients)
{
Console.WriteLine(ingredient.Name);
}
}
}
这会产生以下输出,即没有递归,非常好并且可以预期:
-------------
Ingredients:
Foobar
Bazqux
因此,对于递归,我修改了这样的代码:
foreach (Item ingredient in recp.Ingredients)
{
+++ if (recipes.FindAll(match2 => match2.Result.name == Ingredient.name).Count != 0)
+++ {
+++ SearchRecipe(ingredient.Name);
+++ }
Console.WriteLine(ingredient.Name);
}
现在输出如下:
| -------------
| Ingredients:
* -------------
* Ingredients:
* Foo
* Bar
| Foobar
@ -------------
@ Ingredients:
@ Baz
@ Qux
| Bazqux
我看到当我理解我的代码时会发生什么。标有*
的行是Foobar
的递归,@
是Bazqux
的递归,|
是原始方法调用的输出。但这不是......有点我想要实现的输出,因为它不容易被理解,而且......通常不正确。
我可视化的输出类似于以下内容(当然“额外”状态行是可忽略的):
Searching for: Foobarbazqux
1 recipe found.
-----
1 Foobarbazqux = 1 Foobar + 1 Bazqux
1 Foobar = 1 Foo + 1 Bar
1 Bazqux = 1 Baz + 1 Qux
1 Foobarbazqux = 1 Foo + 1 Bar + 1 Baz + 1 Qux
1 Foo + 1 Bar => 1 Foobar
1 Baz + 1 Qux => 1 Bazqux
1 Foobar + 1 Bazqux => 1 Foobarbazqux
-----
Exit.
这就是我所说的分解 项目创建食谱链......或者......我不致电 ,但想象所以,参考问题的标题。所以该程序的工作原理如下:
Foobarbazqux
)Result
,它们都不是Recipe
。注意:更好地适应真实场景,但会产生额外的实施差距:大部分项目都有多个可以创建它们的食谱。
我想询问的建议和帮助。我慢慢想要说服自己这个程序不可写(这只是某种绝望的强烈抗议)而且我的想法有设计问题。但我仍然希望,在界限范围内,我们不需要花哨但尚未创建的人工智能系统来解决这个问题......有点难以估量的问题。
谢谢。
答案 0 :(得分:1)
第二个答案,对于“从头开始重写”的方法。如果只是给出没有先前代码的问题陈述,我或多或少会这样做。
abstract class Item
{
public string Name { get; private set; }
public abstract IEnumerable<Item> Ingredients { get; }
public abstract IEnumerable<Item> ParseRecipe();
protected Item(string name)
{
Name = name;
}
public static Item GetItem(string _name)
{
return items.Find(match => match.Name == _name);
}
}
class Atom : Item
{
public Atom(string name) : base(name) { }
public override IEnumerable<Item> Ingredients { get { return null; } }
public override IEnumerable<Item> ParseRecipe()
{
return new[] { this };
}
}
class Recipe : Item
{
public Recipe(string name, params Item[] ingredients) : base(name)
{
_ingredients = ingredients;
}
public Recipe(string name, params string[] ingredients) : base(name)
{
_ingredients = ingredients.Select(x => Item.GetItem(x));
}
private IEnumerable<Item> _ingredients;
public override IEnumerable<Item> Ingredients { get { return _ingredients;} }
public override IEnumerable<Item> ParseRecipe()
{
Console.WriteLine("1 " + this.Name + " = "
+ String.Join(" + ", this.Ingredients.Select(x => "1 " + x.Name)));
var components = new List<Item>();
foreach(var ing in Ingredients)
{
components.AddRange(ing.ParseRecipe());
}
return components;
}
}
用过:
void Main()
{
items.Add(new Atom("Foo"));
items.Add(new Atom("Bar"));
items.Add(new Atom("Baz"));
items.Add(new Atom("Qux" ));
items.Add(new Recipe("Foobar", "Foo", "Bar"));
items.Add(new Recipe( "Bazqux", "Baz", "Qux" ));
items.Add(new Recipe( "Foobarbazqux", "Foobar", "Bazqux" ));
string search = "Foobarbazqux";
var item = Item.GetItem(search);
Console.WriteLine("1 " + item.Name + " = "
+ String.Join(" + ", item.ParseRecipe().Select(x => "1 " + x.Name)));
}
结果:
1 Foobarbazqux = 1 Foobar + 1 Bazqux
1 Foobar = 1 Foo + 1 Bar
1 Bazqux = 1 Baz + 1 Qux
1 Foobarbazqux = 1 Foo + 1 Bar + 1 Baz + 1 Qux
此方法相对于原始代码的主要优势与abstract class Item
有关。有了这个,我们只关心初始化期间Atom
和Recipe
之间的差异。之后,所有内容都被视为Item
。这意味着递归变得更加简单,因为我们不关心当前项的Ingredients
是原子,收件人还是其混合。这也意味着你可以在任何事情上调用SearchRecipe()
,而不必先检查它是Recipe
。 (请注意,在这种情况下,我稍微编辑了上面的代码以获得更好的输出。)
这对于struct
是不可能的,因为它们不能相互继承,因此不能被视为父类型。你可以声明他们都会实现的interface
,但是每this,当你将它转换为接口时,你会失去大部分内存优势struct
,我们会一直这样做。
答案 1 :(得分:0)
我会在解决时将所有成分=成分[]加载到内存中。我认为这不应该是一个很大的问题。
首先想到的是你需要一个最短的路径算法:由于配方可以分成多个其他配方,你可能正在寻找最少数量的配料,并希望确保它完成。
Dijkstra算法的一个标准实现,它将所有叶子作为目标解决,我会想到(或者是一个变体吗?)并且还确保你不会在圆圈中运行。
然后......您可以使用该路径生成您想要的输出。
答案 2 :(得分:0)
我确信整体设计更好,但这是一个小修改,可以产生你想要的输出(或多或少):
public static IEnumerable<IEnumerable<Item>> SearchRecipe(string _search)
{
List<Recipe> item_recipes = recipes.FindAll(match => match.Result.Name == Item.GetItem(_search).Name);
var ingredients = new List<IEnumerable<Item>>();
foreach (Recipe recp in item_recipes)
{
foreach (Item ingredient in recp.Ingredients)
{
if (recipes.FindAll(match2 => match2.Result.Name == ingredient.Name).Count != 0)
{
ingredients.Add(SearchRecipe(ingredient.Name).SelectMany(x => x));
}
else
{
ingredients.Add(new[] { ingredient });
}
}
Console.WriteLine(recp.Result.Name + " = " + String.Join(" + ", ingredients.SelectMany(x => x.Select(y => y.Name))));
}
return ingredients;
}
产地:
Foobar = Foo + Bar
Bazqux = Baz + Qux
Foobarbazqux = Foo + Bar + Baz + Qux