C#:如何在具有多种条件(过滤器和项目组)的列表上迭代

C#:如何在具有多种条件(过滤器和项目组)的列表上迭代,c#,algorithm,conditional-statements,C#,Algorithm,Conditional Statements,假设我有一个INT列表: var numbers = new List<int> { 0, 0, 0, 27, 29, 24, 35, 33, 32, 1, 1, 1, 22, 55, 44, 44, 55, 59, 0, 0, 0, 0 }; var number=新列表{0,0,0,27,29,24,35,33,32,1,1,1,22,55, 44, 44, 55, 59, 0, 0, 0, 0 }; 我想实现下面描述的搜索算法。我在找59号 一些未指定的条件决定了我

假设我有一个INT列表:

var numbers = new List<int> { 0, 0, 0, 27, 29, 24, 35, 33, 32, 1, 1, 1, 22, 55,
    44, 44, 55, 59, 0, 0, 0, 0 };
var number=新列表{0,0,0,27,29,24,35,33,32,1,1,1,22,55,
44, 44, 55, 59, 0, 0, 0, 0 };
我想实现下面描述的搜索算法。我在找59号

  • 一些未指定的条件决定了我是从左迭代还是从右迭代。在这个例子中,假设从左到右是迭代的方向
  • 我们需要调整列表的开头:
    • 2.1。应忽略前导0
    • 2.2。如果下一个项目具有相同的十位数,则将它们分组在一起。例如,27、29、24被分组在一起。接下来,将35、33、32分组在一起。接下来,55。等等
    • 2.3。如果组中包含偶数,则忽略它,然后继续下一个,直到找到只包含奇数的组。这个组也被忽略,我们继续这个算法的第3步
    • 2.3。1也被忽略
  • 清除列表的开头后,我们需要处理剩余项目的结尾(
    44、44、55、59、0、0、0、0
    ):

    • 3.1。我们正在寻找第一组包含以9结尾的项。我们退回这件物品。返回59。如果我们从另一个方向迭代,就会找到29个
  • 我如何在C#中实现这个算法?以下是我关注的一些问题:

    • 我可以使用
      来迭代整个列表,但有时我必须从列表的末尾开始,这会导致索引代码混乱。我曾想过实现一个自定义的
      IEnumerable/IEnumerator
      来隐藏这个混乱,但在这种情况下,我应该使用
      foreach
      语句。然而,我想当我试图处理上述
      foreach
      中描述的组时,我仍然会有一团混乱
    • 我应该如何迭代列表并同时构建这些组。在C#中,什么是干净的方法
    • 出于效率的考虑,我们不应该先过滤掉列表中的所有0。在本例中,该列表可能是1000000000个元素的很长列表的开始。如果我们要查找的数字是第15个,则无需检查第9918477个元素
    • 此外,该算法有两个不同的部分(序列的开始和结束)。我不知道如何在一次迭代中处理这两个问题

    注意:这个例子不是家庭作业。这是一个简化的问题,旨在删除涉及复杂对象和条件的实际问题的不必要细节。

    您可以使用Linq,并进行一些自定义。您需要实现一个反向枚举器,以便可以保持主逻辑不变,而不管您是从左向右迭代还是从左向右迭代。不要对大列表使用Linq的
    Reverse
    方法,因为这会首先迭代整个集合

    我应该如何迭代列表并同时构建这些组。在C#中,什么是干净的方法

    最干净的方法是使用Linq的
    GroupBy
    ,但这将导致在构建组时对整个列表进行迭代。同样,您可以创建自己的枚举数和扩展方法来包装原始枚举数并返回组,这将更有效,因为您知道分组函数对已排序的元素进行操作


    一旦你有了一个可枚举的组,只需一小段时间来过滤掉零,另一小段时间来过滤带有偶数的组,一小段时间来跳过下一个组,然后首先找到一个以9结尾的组,假设你有以下列表:

    var numbers = new List<int> { 0, 0, 0, 27, 29, 24, 35, 33, 32, 1, 1, 1, 55, 44, 44, 55, 59, 0, 0, 0, 0 };
    
    要实现可逆的功能,您可以在变量中保留
    条件,如下所示:

        var startIndex = reversed ? numbers.Count - 1 : 0;
        var endIndex   = reversed ? 0 : numbers.Count - 1;
        var increment  = reversed ? -1 : 1;
    
        for (int index = startIndex; index != endIndex; index += incremenet)
        {
            var currentNumber = numbers[index];
    
    分组有几种方法,但一种方法是使用字典:

    var groups = new Dictionary<int, List<int>>();
    

    我不会为您编写其余的代码,但这应该会给您一个非常好的主意如何继续。

    @Theodor Zoulias使用LINQ给出了答案

    以下解决方案更为手动:

    @John Wu通过使用变量和
    for
    循环解决了我的索引问题

    至于问题的另一部分,即如何在迭代列表时构建组,这应该做到:

    // This function finds the next group in numbers, starting at index startIndex.
    IList<int> FindNextGroup(IList<int> numbers, int startIndex, bool reversed)
    {
        var group = new List<int>();
    
        var firstNumberOfGroup = numbers[startIndex];
    
        // See @John Wu's answer
        var endIndex = reversed ? 0 : numbers.Count -1;
        var increment = reversed ? -1 : 1;
        for (int i = startIndex + 1; i != endIndex; i += increment)
        {
            if (numbers[i] is in same group as firstNumberOfGroup
                && numbers[i] != 0 && numbers[i] != 1 && ...)
                group.Add(numbers[i]);
            else
                return group;
        }
    }
    
    //此函数以数字形式查找下一个组,从索引startIndex开始。
    IList FindNextGroup(IList编号、int startIndex、bool反向)
    {
    var group=新列表();
    var firstNumberOfGroup=数字[startIndex];
    //见@John Wu的答案
    var endIndex=reversed?0:数字。计数-1;
    var增量=反向?-1:1;
    对于(int i=startIndex+1;i!=endIndex;i+=increment)
    {
    如果(数字[i]与firstNumberOfGroup位于同一组中
    &&数字[i]!=0和数字[i]!=1和……)
    增加(编号[i]);
    其他的
    返回组;
    }
    }
    
    如果您熟悉LINQ,并且熟悉编写非内置的LINQ方法,那么这个问题很容易解决

    using System.Linq;
    
    var source = new List<int> { 0, 0, 0, 27, 29, 24, 35, 33, 32, 1, 1, 1,
        22, 55, 44, 44, 55, 59, 0, 0, 0, 0 };
    
    var result = source
        .SkipWhile(n => n == 0) // Leading 0s ignored
        .GroupConsecutiveByKey(n => n / 10) // Next items having same tens grouped
        .SkipWhile(g => g.Any(n => n % 2 == 0)) // Group containing an even number ignored
        .Skip(1) // Next group ignored
        .Where(g => !g.All(n => n == 1)) // 1s are ignored as well
        .FirstOrDefault(g => g.Any(n => n % 10 == 9)) // Contains item ending in 9
        .FirstOrDefault(n => n % 10 == 9); // Item ending in 9
    
    Console.WriteLine($"Result: {result}");
    

    源序列仅枚举一次。唯一缓冲的元素是属于一组具有相同十位数的连续数字的元素。此查询可能会给出元素数量达到天文数字的序列的结果。

    您有一些自定义GroupBy的示例代码吗?正如您所说,我不想迭代整个列表来构建组。另外,通常GroupBy会将不连续的元素组合在一起,这意味着
    20、21、40、28
    将产生2个组,而不是我想要的3个组。这是家庭作业吗?不,这是虚构的示例。真实的事物涉及一系列复杂的对象,条件和分组是不同的。我试图从一个更复杂的问题中创建一个非常简单的例子。你用相同的十来分组所有的数字
    groups[tens].Add(currentNumber);
    
    // This function finds the next group in numbers, starting at index startIndex.
    IList<int> FindNextGroup(IList<int> numbers, int startIndex, bool reversed)
    {
        var group = new List<int>();
    
        var firstNumberOfGroup = numbers[startIndex];
    
        // See @John Wu's answer
        var endIndex = reversed ? 0 : numbers.Count -1;
        var increment = reversed ? -1 : 1;
        for (int i = startIndex + 1; i != endIndex; i += increment)
        {
            if (numbers[i] is in same group as firstNumberOfGroup
                && numbers[i] != 0 && numbers[i] != 1 && ...)
                group.Add(numbers[i]);
            else
                return group;
        }
    }
    
    using System.Linq;
    
    var source = new List<int> { 0, 0, 0, 27, 29, 24, 35, 33, 32, 1, 1, 1,
        22, 55, 44, 44, 55, 59, 0, 0, 0, 0 };
    
    var result = source
        .SkipWhile(n => n == 0) // Leading 0s ignored
        .GroupConsecutiveByKey(n => n / 10) // Next items having same tens grouped
        .SkipWhile(g => g.Any(n => n % 2 == 0)) // Group containing an even number ignored
        .Skip(1) // Next group ignored
        .Where(g => !g.All(n => n == 1)) // 1s are ignored as well
        .FirstOrDefault(g => g.Any(n => n % 10 == 9)) // Contains item ending in 9
        .FirstOrDefault(n => n % 10 == 9); // Item ending in 9
    
    Console.WriteLine($"Result: {result}");
    
    public static IEnumerable<List<TSource>> GroupConsecutiveByKey<TSource, TKey>(
        this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        var comparer = EqualityComparer<TKey>.Default;
        TKey prevKey = default;
        List<TSource> list = null;
        foreach (var item in source)
        {
            var key = keySelector(item);
            if (list == null)
            {
                list = new List<TSource>();
            }
            else if (!comparer.Equals(key, prevKey))
            {
                yield return list;
                list = new List<TSource>();
            }
            list.Add(item);
            prevKey = key;
        }
        if (list != null) yield return list;
    }