如何迭代两个项目之间的集合?

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 是否为真。如果是这样,我们可以做点什么。

在线演示:https://dotnetfiddle.net/xYcr7V

如果您正在寻找性能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 循环支持的版本范围从 elel。例如,对于 [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