我应该如何使用谓词逻辑基于列表对象包含 C# 中另一个列表的元素从通用列表中删除元素?
How should I remove elements from a generic list based on the list s object's inclusion of elementfrom another list in C# using predicate logic?
我正在尝试通过制作一个简单的程序来学习 C#,该程序向用户展示给定所需配料的寿司卷。即用户想要一个带螃蟹的卷,程序将吐出包含螃蟹的寿司卷列表。
我创建了一个 Roll class
public class Roll
{
private string name;
private List<string> ingredients = new List<string>();
}
使用一些 getter 和 setter 以及其他各种方法。
在 GUI 中,我有一些复选框,每个复选框都从控件 class 调用 update() 方法,然后需要根据 GUI 复选框给出的配料列表检查面包卷列表.我有的是这个
class Controller
{
static List<Roll> Rolls = new List<Roll>();
static RollList RL = new RollList();
static List<String> ingredients = new List<String>();
static Roll roll = new Roll();
}
public void update
{
foreach(Roll roll in Rolls)
{
foreach (String ingredient in ingredients)
if (!roll.checkForIngredient(ingredient))
Rolls.Remove(roll);
}
}
但是抛出一个System.InvalidOperationException说因为集合被修改,操作无法执行。好吧,这很公平,但是最好的方法是什么?在 Stack Overflow 上有一个 post about removing elements from a generic list while iterating over it。
这很好,为我指明了正确的方向,但不幸的是,我的谓词条件与最佳答案不符。
它必须遍历成分列表,我什至不确定这是否可能...
list.RemoveAll(roll => !roll.containsIngredient(each string ingredient in ingredients) );
不寒而栗
我已经尝试了 for 循环,但我似乎也无法使枚举工作,我想知道是否有必要仅针对此方法枚举 class。
所以我来这里是为了尝试找到一个优雅、专业的解决方案来解决我的问题。请记住,我是 C# 的新手,我不太熟悉 classes 上的谓词逻辑或枚举。
要使用 RemoveAll
,您可以将条件重写为:
list.RemoveAll(roll => !ingredients.All(roll.checkForIngredient));
这利用了这样一个事实,即当编译器看到它时,它会有效地将其重写为:
list.RemoveAll(roll => !ingredients.All(i => roll.checkForIngredient(i)));
这就是你想要的。如果不是所有成分都存在,请取出卷。
话虽如此,既然你说你是初学者,也许你觉得保持你的循环更舒服,如果你能让它工作(即停止由于修改循环而崩溃)。为此,只需复制该集合,然后循环遍历该副本,只需将 foreach
语句修改为:
foreach(Roll roll in Rolls.ToList())
这将创建一个基于列表的 Rolls
集合副本,然后在其上循环。该列表不会被修改,即使 Rolls
被创建,它也是一个单独的副本,包含 Rolls
创建时的所有元素。
根据评论中的要求,我将尝试解释这行代码的工作原理:
list.RemoveAll(roll => !ingredients.All(roll.checkForIngredient));
RemoveAll
方法,您可以看到 documentation for here 接受一个谓词,一个 Predicate<T>
,它基本上是一个委托,对方法的引用。
这可以是使用 =>
运算符创建匿名方法的 lambda 语法。匿名方法基本上是在您要使用它的地方声明的方法,没有名称,因此是匿名部分。让我们重写代码以使用匿名方法而不是 lambda:
list.RemoveAll(delegate(Roll roll)
{
return !ingredients.All(roll.checkForIngredient);
});
这是与上述 lambda 版本完全相同的编译代码,只是使用了匿名方法的更详细的语法。
那么,方法中的代码是如何工作的。
All
方法是一种扩展方法,在 Enumerable class: Enumerable.All 上找到。
它基本上会遍历它正在扩展的集合的所有元素,在本例中是单个卷的成分集合,并调用谓词函数。如果对于任何元素谓词 returns false
,调用 All
的结果也将是 false
。如果全部调用returntrue
,结果也会是true
。请注意,如果集合 (ingredients) 为空,则结果也将是 true
.
所以让我们尝试重写我们的 lambda 代码,它再次看起来像这样:
list.RemoveAll(roll => !ingredients.All(roll.checkForIngredient));
进入更详细的方法,不使用 All
扩展方法:
list.RemoveAll(delegate(Roll roll)
{
bool all = true;
foreach (var ingredient in ingredients)
if (!roll.checkForIngredient(ingredient))
{
all = false;
break;
}
return !all;
});
这现在开始看起来像您的原始代码片段,只是我们使用的是 RemoveAll
方法,该方法需要一个谓词 return 是否删除该项目。因为如果 all
是 false
,我们需要删除滚动,我们使用非运算符 !
来反转该值。
I am trying to learn C# by making a simple program that shows the user
sushi rolls given their desired ingredients. i.e. a user wants a roll
with crab, and the program will spit out a list of sushi rolls that
contain crab.
这是我对给定问题的解决方案:
public class Roll
{
public string Name { get; set; }
private List<string> ingredients = new List<string>();
public IList<string> Ingredients { get { return ingredients; } }
public bool Contains(string ingredient)
{
return Ingredients.Any(i => i.Equals(ingredient));
}
}
您可以使用 LINQ 扩展方法 .Where
来 filter 您的 Rolls
public class Program
{
static void Main()
{
var allRolls = new List<Roll>
{
new Roll
{
Name = "Roll 1",
Ingredients = { "IngredientA", "Crab", "IngredientC" }
},
new Roll
{
Name = "Roll 2",
Ingredients = { "IngredientB", "IngredientC" }
},
new Roll
{
Name = "Roll 3",
Ingredients = { "Crab", "IngredientA" }
}
};
var rollsWithCrab = allRolls.Where(roll => roll.Contains("Crab"));
foreach (Roll roll in rollsWithCrab)
{
Console.WriteLine(roll.Name);
}
}
}
据我所知,您正试图从 中删除所有不包含 crab 的 rolls卷列表。更好的方法是过滤掉那些 rolls 不包含 crab (使用 .Where
),然后你可以使用 .ToList()
如果您需要直接操作整个列表而不是遍历集合(一次获取一项)。
您应该阅读 Delegates、Iterators、Extension Methods 和 LINQ 以便更好了解幕后发生的事情。
由于你们都是 C# 的新手,但又要求一个优雅的解决方案,我将举例说明如何使用更面向对象的方法来解决这个问题。
首先,任何重要的 "thing" 都应该建模为 class,即使它只有一个 属性。这使得以后扩展行为变得更加容易。您已经为 Roll 定义了 class。我还要为成分添加一个 class:
public class Ingredient
{
private string _name;
public string Name
{
get { return _name; }
}
public Ingredient(string name)
{
_name = name;
}
}
注意只有 getter 的名称 属性,以及接受字符串 name
的构造函数。乍一看,这可能看起来像是不必要的复杂性,但会让您的代码更直接,以便在以后使用。
接下来,我们将根据此指南修改您的 Roll
class 并为其提供一些辅助方法,以便我们更轻松地检查 roll 是否包含某个(列表)成分:
public class Roll
{
private string _name;
private List<Ingredient> _ingredients = new List<Ingredient>();
public string Name
{
// By only exposing the property through a getter, you are preventing the name
// from being changed after the roll has been created
get { return _name; }
}
public List<Ingredient> Ingredients
{
// Similarly here, you are forcing the consumer to use the AddIngredient method
// where you can do any necessary checks before actually adding the ingredient
get { return _ingredients; }
}
public Roll(string name)
{
_name = name;
}
public bool AddIngredient(Ingredient ingredient)
{
// Returning a boolean value to indicate whether the ingredient was already present,
// gives the consumer of this class a way to present feedback to the end user
bool alreadyHasIngredient = _ingredients.Any(i => i.Name == ingredient.Name);
if (!alreadyHasIngredient)
{
_ingredients.Add(ingredient);
return true;
}
return false;
}
public bool ContainsIngredients(IEnumerable<Ingredient> ingredients)
{
// We use a method group to check for all of the supplied ingredients
// whether or not they exist
return ingredients.All(ContainsIngredient);
// Could be rewritten as: ingredients.All(i => ContainsIngredient(i));
}
public bool ContainsIngredient(Ingredient ingredient)
{
// We simply check if an ingredient is present by comparing their names
return _ingredients.Any(i => i.Name == ingredient.Name);
}
}
注意这里的ContainsIngredient和ContainsIngredients方法。现在您可以执行 if (roll.ContainsIngredient(ingredient))
之类的操作,这将使您的代码更具表现力和可读性。在我要添加的 class RollCollection
.
中,您将看到它的实际效果
您正在对可供选择的食物集合进行建模,大概是在餐厅菜单或某个类似领域的上下文中。您还不如继续建模:一个 RollCollection。这将允许您在集合中封装一些有意义的逻辑。
同样,这类事情往往需要一些样板代码,一开始可能看起来过于复杂,但它会让您的 classes 更易于使用。所以让我们添加一个 RollCollection:
public class RollCollection : IEnumerable<Roll>
{
private List<Roll> _rolls = new List<Roll>();
public RollCollection()
{
// We need to provide a default constructor if we want to be able
// to instantiate an empty RollCollection and then add rolls later on
}
public RollCollection(IEnumerable<Roll> rolls)
{
// By providing a constructor overload which accepts an IEnumerable<Roll>,
// we have the opportunity to create a new RollCollection based on a filtered existing collection of rolls
_rolls = rolls.ToList();
}
public RollCollection WhichContainIngredients(IEnumerable<Ingredient> ingredients)
{
IEnumerable<Roll> filteredRolls = _rolls
.Where(r => r.ContainsIngredients(ingredients));
return new RollCollection(filteredRolls);
}
public bool AddRoll(Roll roll)
{
// Similar to AddIngredient
bool alreadyContainsRoll = _rolls.Any(r => r.Name == roll.Name);
if (!alreadyContainsRoll)
{
_rolls.Add(roll);
return true;
}
return false;
}
#region IEnumerable implementation
public IEnumerator<Roll> GetEnumerator()
{
foreach (Roll roll in _rolls)
{
yield return roll;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
WhichContainIngredients
是我们真正想要的东西,因为它允许你做这样的事情:
// I have omitted the (proper) instantiation of Rolls and ChosenIngredients for brevity here
public RollCollection Rolls { get; set; }
public List<Ingredient> ChosenIngredients { get; set; }
public void Update()
{
Rolls = Rolls.WhichContainIngredients(ChosenIngredients);
}
这简单明了,正是您想在表示层中做的事情。完成您的要求的逻辑现在很好地封装在 RollCollection class.
中
编辑:一个更完整(但仍然简化)的示例,说明您的控制器 class 最终可能看起来像:
public class Controller
{
private RollCollection _availableRolls = new RollCollection();
private List<Ingredient> _availableIngredients = new List<Ingredient>();
public RollCollection AvailableRolls
{
get { return _availableRolls; }
}
public List<Ingredient> AvailableIngredients
{
get { return _availableIngredients; }
}
public RollCollection RollsFilteredByIngredients
{
get { return AvailableRolls.WhichContainIngredients(ChosenIngredients); }
}
public List<Ingredient> ChosenIngredients { get; set; }
public Controller()
{
ChosenIngredients = new List<Ingredient>();
InitializeTestData();
}
private void InitializeTestData()
{
Ingredient ingredient1 = new Ingredient("Ingredient1");
Ingredient ingredient2 = new Ingredient("Ingredient2");
Ingredient ingredient3 = new Ingredient("Ingredient3");
_availableIngredients.Add(ingredient1);
_availableIngredients.Add(ingredient2);
_availableIngredients.Add(ingredient3);
Roll roll1 = new Roll("Roll1");
roll1.AddIngredient(ingredient1);
roll1.AddIngredient(ingredient2);
Roll roll2 = new Roll("Roll2");
roll2.AddIngredient(ingredient3);
_availableRolls.AddRoll(roll1);
_availableRolls.AddRoll(roll2);
}
}
我正在尝试通过制作一个简单的程序来学习 C#,该程序向用户展示给定所需配料的寿司卷。即用户想要一个带螃蟹的卷,程序将吐出包含螃蟹的寿司卷列表。
我创建了一个 Roll class
public class Roll
{
private string name;
private List<string> ingredients = new List<string>();
}
使用一些 getter 和 setter 以及其他各种方法。
在 GUI 中,我有一些复选框,每个复选框都从控件 class 调用 update() 方法,然后需要根据 GUI 复选框给出的配料列表检查面包卷列表.我有的是这个
class Controller
{
static List<Roll> Rolls = new List<Roll>();
static RollList RL = new RollList();
static List<String> ingredients = new List<String>();
static Roll roll = new Roll();
}
public void update
{
foreach(Roll roll in Rolls)
{
foreach (String ingredient in ingredients)
if (!roll.checkForIngredient(ingredient))
Rolls.Remove(roll);
}
}
但是抛出一个System.InvalidOperationException说因为集合被修改,操作无法执行。好吧,这很公平,但是最好的方法是什么?在 Stack Overflow 上有一个 post about removing elements from a generic list while iterating over it。 这很好,为我指明了正确的方向,但不幸的是,我的谓词条件与最佳答案不符。 它必须遍历成分列表,我什至不确定这是否可能...
list.RemoveAll(roll => !roll.containsIngredient(each string ingredient in ingredients) );
不寒而栗
我已经尝试了 for 循环,但我似乎也无法使枚举工作,我想知道是否有必要仅针对此方法枚举 class。
所以我来这里是为了尝试找到一个优雅、专业的解决方案来解决我的问题。请记住,我是 C# 的新手,我不太熟悉 classes 上的谓词逻辑或枚举。
要使用 RemoveAll
,您可以将条件重写为:
list.RemoveAll(roll => !ingredients.All(roll.checkForIngredient));
这利用了这样一个事实,即当编译器看到它时,它会有效地将其重写为:
list.RemoveAll(roll => !ingredients.All(i => roll.checkForIngredient(i)));
这就是你想要的。如果不是所有成分都存在,请取出卷。
话虽如此,既然你说你是初学者,也许你觉得保持你的循环更舒服,如果你能让它工作(即停止由于修改循环而崩溃)。为此,只需复制该集合,然后循环遍历该副本,只需将 foreach
语句修改为:
foreach(Roll roll in Rolls.ToList())
这将创建一个基于列表的 Rolls
集合副本,然后在其上循环。该列表不会被修改,即使 Rolls
被创建,它也是一个单独的副本,包含 Rolls
创建时的所有元素。
根据评论中的要求,我将尝试解释这行代码的工作原理:
list.RemoveAll(roll => !ingredients.All(roll.checkForIngredient));
RemoveAll
方法,您可以看到 documentation for here 接受一个谓词,一个 Predicate<T>
,它基本上是一个委托,对方法的引用。
这可以是使用 =>
运算符创建匿名方法的 lambda 语法。匿名方法基本上是在您要使用它的地方声明的方法,没有名称,因此是匿名部分。让我们重写代码以使用匿名方法而不是 lambda:
list.RemoveAll(delegate(Roll roll)
{
return !ingredients.All(roll.checkForIngredient);
});
这是与上述 lambda 版本完全相同的编译代码,只是使用了匿名方法的更详细的语法。
那么,方法中的代码是如何工作的。
All
方法是一种扩展方法,在 Enumerable class: Enumerable.All 上找到。
它基本上会遍历它正在扩展的集合的所有元素,在本例中是单个卷的成分集合,并调用谓词函数。如果对于任何元素谓词 returns false
,调用 All
的结果也将是 false
。如果全部调用returntrue
,结果也会是true
。请注意,如果集合 (ingredients) 为空,则结果也将是 true
.
所以让我们尝试重写我们的 lambda 代码,它再次看起来像这样:
list.RemoveAll(roll => !ingredients.All(roll.checkForIngredient));
进入更详细的方法,不使用 All
扩展方法:
list.RemoveAll(delegate(Roll roll)
{
bool all = true;
foreach (var ingredient in ingredients)
if (!roll.checkForIngredient(ingredient))
{
all = false;
break;
}
return !all;
});
这现在开始看起来像您的原始代码片段,只是我们使用的是 RemoveAll
方法,该方法需要一个谓词 return 是否删除该项目。因为如果 all
是 false
,我们需要删除滚动,我们使用非运算符 !
来反转该值。
I am trying to learn C# by making a simple program that shows the user sushi rolls given their desired ingredients. i.e. a user wants a roll with crab, and the program will spit out a list of sushi rolls that contain crab.
这是我对给定问题的解决方案:
public class Roll
{
public string Name { get; set; }
private List<string> ingredients = new List<string>();
public IList<string> Ingredients { get { return ingredients; } }
public bool Contains(string ingredient)
{
return Ingredients.Any(i => i.Equals(ingredient));
}
}
您可以使用 LINQ 扩展方法 .Where
来 filter 您的 Rolls
public class Program
{
static void Main()
{
var allRolls = new List<Roll>
{
new Roll
{
Name = "Roll 1",
Ingredients = { "IngredientA", "Crab", "IngredientC" }
},
new Roll
{
Name = "Roll 2",
Ingredients = { "IngredientB", "IngredientC" }
},
new Roll
{
Name = "Roll 3",
Ingredients = { "Crab", "IngredientA" }
}
};
var rollsWithCrab = allRolls.Where(roll => roll.Contains("Crab"));
foreach (Roll roll in rollsWithCrab)
{
Console.WriteLine(roll.Name);
}
}
}
据我所知,您正试图从 中删除所有不包含 crab 的 rolls卷列表。更好的方法是过滤掉那些 rolls 不包含 crab (使用 .Where
),然后你可以使用 .ToList()
如果您需要直接操作整个列表而不是遍历集合(一次获取一项)。
您应该阅读 Delegates、Iterators、Extension Methods 和 LINQ 以便更好了解幕后发生的事情。
由于你们都是 C# 的新手,但又要求一个优雅的解决方案,我将举例说明如何使用更面向对象的方法来解决这个问题。
首先,任何重要的 "thing" 都应该建模为 class,即使它只有一个 属性。这使得以后扩展行为变得更加容易。您已经为 Roll 定义了 class。我还要为成分添加一个 class:
public class Ingredient
{
private string _name;
public string Name
{
get { return _name; }
}
public Ingredient(string name)
{
_name = name;
}
}
注意只有 getter 的名称 属性,以及接受字符串 name
的构造函数。乍一看,这可能看起来像是不必要的复杂性,但会让您的代码更直接,以便在以后使用。
接下来,我们将根据此指南修改您的 Roll
class 并为其提供一些辅助方法,以便我们更轻松地检查 roll 是否包含某个(列表)成分:
public class Roll
{
private string _name;
private List<Ingredient> _ingredients = new List<Ingredient>();
public string Name
{
// By only exposing the property through a getter, you are preventing the name
// from being changed after the roll has been created
get { return _name; }
}
public List<Ingredient> Ingredients
{
// Similarly here, you are forcing the consumer to use the AddIngredient method
// where you can do any necessary checks before actually adding the ingredient
get { return _ingredients; }
}
public Roll(string name)
{
_name = name;
}
public bool AddIngredient(Ingredient ingredient)
{
// Returning a boolean value to indicate whether the ingredient was already present,
// gives the consumer of this class a way to present feedback to the end user
bool alreadyHasIngredient = _ingredients.Any(i => i.Name == ingredient.Name);
if (!alreadyHasIngredient)
{
_ingredients.Add(ingredient);
return true;
}
return false;
}
public bool ContainsIngredients(IEnumerable<Ingredient> ingredients)
{
// We use a method group to check for all of the supplied ingredients
// whether or not they exist
return ingredients.All(ContainsIngredient);
// Could be rewritten as: ingredients.All(i => ContainsIngredient(i));
}
public bool ContainsIngredient(Ingredient ingredient)
{
// We simply check if an ingredient is present by comparing their names
return _ingredients.Any(i => i.Name == ingredient.Name);
}
}
注意这里的ContainsIngredient和ContainsIngredients方法。现在您可以执行 if (roll.ContainsIngredient(ingredient))
之类的操作,这将使您的代码更具表现力和可读性。在我要添加的 class RollCollection
.
您正在对可供选择的食物集合进行建模,大概是在餐厅菜单或某个类似领域的上下文中。您还不如继续建模:一个 RollCollection。这将允许您在集合中封装一些有意义的逻辑。
同样,这类事情往往需要一些样板代码,一开始可能看起来过于复杂,但它会让您的 classes 更易于使用。所以让我们添加一个 RollCollection:
public class RollCollection : IEnumerable<Roll>
{
private List<Roll> _rolls = new List<Roll>();
public RollCollection()
{
// We need to provide a default constructor if we want to be able
// to instantiate an empty RollCollection and then add rolls later on
}
public RollCollection(IEnumerable<Roll> rolls)
{
// By providing a constructor overload which accepts an IEnumerable<Roll>,
// we have the opportunity to create a new RollCollection based on a filtered existing collection of rolls
_rolls = rolls.ToList();
}
public RollCollection WhichContainIngredients(IEnumerable<Ingredient> ingredients)
{
IEnumerable<Roll> filteredRolls = _rolls
.Where(r => r.ContainsIngredients(ingredients));
return new RollCollection(filteredRolls);
}
public bool AddRoll(Roll roll)
{
// Similar to AddIngredient
bool alreadyContainsRoll = _rolls.Any(r => r.Name == roll.Name);
if (!alreadyContainsRoll)
{
_rolls.Add(roll);
return true;
}
return false;
}
#region IEnumerable implementation
public IEnumerator<Roll> GetEnumerator()
{
foreach (Roll roll in _rolls)
{
yield return roll;
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
WhichContainIngredients
是我们真正想要的东西,因为它允许你做这样的事情:
// I have omitted the (proper) instantiation of Rolls and ChosenIngredients for brevity here
public RollCollection Rolls { get; set; }
public List<Ingredient> ChosenIngredients { get; set; }
public void Update()
{
Rolls = Rolls.WhichContainIngredients(ChosenIngredients);
}
这简单明了,正是您想在表示层中做的事情。完成您的要求的逻辑现在很好地封装在 RollCollection class.
中编辑:一个更完整(但仍然简化)的示例,说明您的控制器 class 最终可能看起来像:
public class Controller
{
private RollCollection _availableRolls = new RollCollection();
private List<Ingredient> _availableIngredients = new List<Ingredient>();
public RollCollection AvailableRolls
{
get { return _availableRolls; }
}
public List<Ingredient> AvailableIngredients
{
get { return _availableIngredients; }
}
public RollCollection RollsFilteredByIngredients
{
get { return AvailableRolls.WhichContainIngredients(ChosenIngredients); }
}
public List<Ingredient> ChosenIngredients { get; set; }
public Controller()
{
ChosenIngredients = new List<Ingredient>();
InitializeTestData();
}
private void InitializeTestData()
{
Ingredient ingredient1 = new Ingredient("Ingredient1");
Ingredient ingredient2 = new Ingredient("Ingredient2");
Ingredient ingredient3 = new Ingredient("Ingredient3");
_availableIngredients.Add(ingredient1);
_availableIngredients.Add(ingredient2);
_availableIngredients.Add(ingredient3);
Roll roll1 = new Roll("Roll1");
roll1.AddIngredient(ingredient1);
roll1.AddIngredient(ingredient2);
Roll roll2 = new Roll("Roll2");
roll2.AddIngredient(ingredient3);
_availableRolls.AddRoll(roll1);
_availableRolls.AddRoll(roll2);
}
}