如何迭代两个项目之间的集合?
How to iterate a collection between two items?
考虑一个对象列表的例子:
List<MyClass> myList;
我有一个方法可以将两个引用传递给列表中的项目。我想遍历给定项目中的所有项目(注意:我不知道这两个项目中的哪一个在列表中排在第一位):
privat void MyFunction(MyClass listItem, MyClass anotherListItem)
{
foreach(var item in ????)
{
// do something
}
}
目前我已经解决了这个用例如下:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
if(listItemIdx < anotherListItemIdx )
{
for(int i = listItemIdx ; i <= anotherListItemIdx ; i++)
{
// do stuff
}
}
else
{
for (int i = anotherListItemIdx ; i < listItemIdx ; i++)
{
// do stuff
}
}
我想知道是否有更优雅、更高效或内置的解决方案来解决这个问题?
列表排序了吗?您可以使用该事实来了解哪个项目必须在第一位。不过,如果您愿意,您可以使用一个 for-loop:
private static void MyFunction(string item1, string item2)
{
List<string> input = new() {"A", "B", "C", "D", "E"};
int index1 = input.IndexOf(item1);
int index2 = input.IndexOf(item2);
int beginIndex = Math.Min(index1, index2);
int count = Math.Abs(index1 - index2) + 1;
foreach (string item in input.GetRange(beginIndex, count))
{
Console.Write(item);
}
}
您在列表中迭代三次:在 IndexOf
中迭代两次,然后在循环中再次迭代。您可以使用此代码提高代码效率,该代码仅在列表中迭代一次。
privat void MyFunction(MyClass listItem, MyClass anotherListItem)
{
bool betweenTwoItems = false;
foreach(var item in myList)
{
if(item == listItem || item == anotherListItem)
{
betweenTwoItems = !betweenTwoItems;
if(!betweenTwoItems)
{
break;
}
}
if(betweenTwoItems )
{
// do stuff
}
}
}
如果我们在两个项目之间,我们设置一个 bool 变量。一开始,它是错误的。我们遍历列表并检查当前项目是否是两个方法参数之一。如果是这种情况,我们反转 bool 的值。如果在 bool 反转之后值为 false,我们可以离开列表。之后,我们检查 bool 是否为真。如果是这样,我们可以做点什么。
如果您正在寻找性能(IndexOf
两次可能会有点慢)和泛化
(当 myList
不是必需的 List<T>
而只是 IEnumerable<T>
时)你可以把它写成
bool proceed = false;
MyClass lastItem = default;
foreach (var item in myList) {
if (!proceed) {
if (proceed = item == listItem)
lastItem = anotherListItem;
else if (proceed = item == anotherListItem)
lastItem = listItem;
}
if (proceed) {
//TODO: do staff here
if (item == lastItem)
break;
}
}
您现有的解决方案可以改进:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
int startIdx = Math.Min(listItemIdx, anotherListItemIdx);
int endIdx = Math.Max(listItemIdx, anotherListItemIdx);
for(int i = startIdx ; i <= endIdx ; i++)
{
// do stuff
}
因此,代码重复消失了,只需要进行少量重构。
要创建 range-loop 版本,您可以使用 GetRange() 创建子集,例如:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
int startIdx = Math.Min(listItemIdx, anotherListItemIdx);
int endIdx = Math.Max(listItemIdx, anotherListItemIdx);
var subset = myList.GetRange(startIdx, endIdx - startIdx);
foreach(var item in subset)
{
// do stuff
}
因此,过滤列表和处理列表现在可以分开了。
同一想法的更通用版本。所以这可以创建为 IEnumerable<,>
的扩展方法
public static IEnumerable<T> RangeOf<T>(this IEnumerable<T> elements, T el1, T el2,
IEqualityComparer<T> comparer = null)
{
comparer ??= EqualityComparer<T>.Default;
var hasStarted = false;
var end = default;
foreach (T el in elements)
{
if (!hasStarted)
{
hasStarted = comparer.Equals(el, el1) || comparer.Equals(el, el2);
end = comparer.Equals(el, el1) ? el2 : el1;
}
if (hasStarted)
yield return el;
if (comparer.Equals(el, end))
yield break;
}
}
和 while
循环支持的版本范围从 el
到 el
。例如,对于 [5, 0, 1, 2, 0, 6]
,范围 [0, 0]
将是 [0, 1, 2, 0]
:
public static IEnumerable<T> RangeOf<T>(this IEnumerable<T> elements, T el1, T el2,
IEqualityComparer<T> comparer = null)
{
comparer ??= EqualityComparer<T>.Default;
var hasStarted = false;
var end = default;
var it = elements.GetEnumerator();
while (!hasStarted && it.MoveNext())
{
T el = it.Current;
hasStarted = comparer.Equals(el , el1) || comparer.Equals(el , el2);
end = comparer.Equals(it.Current, el1) ? el2 : el1;
}
if (hasStarted)
yield return it.Current;
while (it.MoveNext())
{
yield return it.Current;
if (comparer.Equals(it.Current, end))
yield break;
}
}
两者都可以这样使用
foreach (var el in list.RangeOf(listItem, anotherListItem))
// Do with el whatever you want to do
考虑一个对象列表的例子:
List<MyClass> myList;
我有一个方法可以将两个引用传递给列表中的项目。我想遍历给定项目中的所有项目(注意:我不知道这两个项目中的哪一个在列表中排在第一位):
privat void MyFunction(MyClass listItem, MyClass anotherListItem)
{
foreach(var item in ????)
{
// do something
}
}
目前我已经解决了这个用例如下:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
if(listItemIdx < anotherListItemIdx )
{
for(int i = listItemIdx ; i <= anotherListItemIdx ; i++)
{
// do stuff
}
}
else
{
for (int i = anotherListItemIdx ; i < listItemIdx ; i++)
{
// do stuff
}
}
我想知道是否有更优雅、更高效或内置的解决方案来解决这个问题?
列表排序了吗?您可以使用该事实来了解哪个项目必须在第一位。不过,如果您愿意,您可以使用一个 for-loop:
private static void MyFunction(string item1, string item2)
{
List<string> input = new() {"A", "B", "C", "D", "E"};
int index1 = input.IndexOf(item1);
int index2 = input.IndexOf(item2);
int beginIndex = Math.Min(index1, index2);
int count = Math.Abs(index1 - index2) + 1;
foreach (string item in input.GetRange(beginIndex, count))
{
Console.Write(item);
}
}
您在列表中迭代三次:在 IndexOf
中迭代两次,然后在循环中再次迭代。您可以使用此代码提高代码效率,该代码仅在列表中迭代一次。
privat void MyFunction(MyClass listItem, MyClass anotherListItem)
{
bool betweenTwoItems = false;
foreach(var item in myList)
{
if(item == listItem || item == anotherListItem)
{
betweenTwoItems = !betweenTwoItems;
if(!betweenTwoItems)
{
break;
}
}
if(betweenTwoItems )
{
// do stuff
}
}
}
如果我们在两个项目之间,我们设置一个 bool 变量。一开始,它是错误的。我们遍历列表并检查当前项目是否是两个方法参数之一。如果是这种情况,我们反转 bool 的值。如果在 bool 反转之后值为 false,我们可以离开列表。之后,我们检查 bool 是否为真。如果是这样,我们可以做点什么。
如果您正在寻找性能(IndexOf
两次可能会有点慢)和泛化
(当 myList
不是必需的 List<T>
而只是 IEnumerable<T>
时)你可以把它写成
bool proceed = false;
MyClass lastItem = default;
foreach (var item in myList) {
if (!proceed) {
if (proceed = item == listItem)
lastItem = anotherListItem;
else if (proceed = item == anotherListItem)
lastItem = listItem;
}
if (proceed) {
//TODO: do staff here
if (item == lastItem)
break;
}
}
您现有的解决方案可以改进:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
int startIdx = Math.Min(listItemIdx, anotherListItemIdx);
int endIdx = Math.Max(listItemIdx, anotherListItemIdx);
for(int i = startIdx ; i <= endIdx ; i++)
{
// do stuff
}
因此,代码重复消失了,只需要进行少量重构。
要创建 range-loop 版本,您可以使用 GetRange() 创建子集,例如:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
int startIdx = Math.Min(listItemIdx, anotherListItemIdx);
int endIdx = Math.Max(listItemIdx, anotherListItemIdx);
var subset = myList.GetRange(startIdx, endIdx - startIdx);
foreach(var item in subset)
{
// do stuff
}
因此,过滤列表和处理列表现在可以分开了。
同一想法的更通用版本。所以这可以创建为 IEnumerable<,>
public static IEnumerable<T> RangeOf<T>(this IEnumerable<T> elements, T el1, T el2,
IEqualityComparer<T> comparer = null)
{
comparer ??= EqualityComparer<T>.Default;
var hasStarted = false;
var end = default;
foreach (T el in elements)
{
if (!hasStarted)
{
hasStarted = comparer.Equals(el, el1) || comparer.Equals(el, el2);
end = comparer.Equals(el, el1) ? el2 : el1;
}
if (hasStarted)
yield return el;
if (comparer.Equals(el, end))
yield break;
}
}
和 while
循环支持的版本范围从 el
到 el
。例如,对于 [5, 0, 1, 2, 0, 6]
,范围 [0, 0]
将是 [0, 1, 2, 0]
:
public static IEnumerable<T> RangeOf<T>(this IEnumerable<T> elements, T el1, T el2,
IEqualityComparer<T> comparer = null)
{
comparer ??= EqualityComparer<T>.Default;
var hasStarted = false;
var end = default;
var it = elements.GetEnumerator();
while (!hasStarted && it.MoveNext())
{
T el = it.Current;
hasStarted = comparer.Equals(el , el1) || comparer.Equals(el , el2);
end = comparer.Equals(it.Current, el1) ? el2 : el1;
}
if (hasStarted)
yield return it.Current;
while (it.MoveNext())
{
yield return it.Current;
if (comparer.Equals(it.Current, end))
yield break;
}
}
两者都可以这样使用
foreach (var el in list.RangeOf(listItem, anotherListItem))
// Do with el whatever you want to do