【问题标题】:Filter an immutable list recursively递归过滤不可变列表
【发布时间】:2015-04-27 20:19:48
【问题描述】:

假设我有一个整数数组,我需要按顺序返回一个包含所有可被 3 整除的数字的数组。

我得到了界面:

List<int> ListElementsDivisibleBy3Recursive(List<int> input)

所以它必须接受一个列表并返回一个列表,并递归过滤它。我尝试从列表的开头弹出元素,直到我找到一个符合所需条件的数字,这就是我卡住的地方。我不知道如何跳到下一个元素并保留与条件匹配的元素。这是我目前所拥有的:

static List<int> ListElementsDivisibleBy3Recursive(List<int> input)
        {
            if (input.Count == 0)
                return input;
            else if (input.ElementAt(0) % 3 == 0)
            {
                return ListElementsDivisibleBy3Recursive(input); <--I have no idea what to do here
            }
            else
            {
                input.RemoveAt(0);
                return ListElementsDivisibleBy3Recursive(input);
            }
        }

如果允许我将指针传递给我所在的列表中的位置,那会容易得多,但我所拥有的只是输入列表和要使用的返回列表。如何删除列表中的元素,同时保留与条件匹配的元素?或者我是不是走错了路?不知道我应该如何从逻辑上解决这个问题。我也意识到我的基本条件也是错误的。

【问题讨论】:

  • 试图用递归解决这个问题....是个坏主意。我的意思是,你可以做到。但为什么呢?
  • 为方便起见,创建一个新列表,在循环过程中,您可以将可被 3 整除的整数添加到该新列表中。之后,您可以对新的列表值进行排序。为了使它更容易,您可以使用单行 LINQ 语句来做到这一点。
  • 您一直在使用array 这个词,但实际上您正在使用列表。另请注意,只有当您的数据结构类似于不可变链表而不是可变数组支持的列表时,才能以递归方式解决此类问题。
  • return input.Where(x =&gt; x % 3 == 0).OrderBy(x =&gt; x); 将是最简单的解决方案。不需要递归。
  • @ps2goat 我必须使用 LINQ、循环和递归来完成。前两个很简单。这让我很头疼。

标签: c# arrays list recursion


【解决方案1】:
return input.Where( i => (i % 3) == 0).ToList();

【讨论】:

  • 这不是递归解决方案,因为问题需要。
【解决方案2】:

这整个问题很奇怪。

要解决 initial 处理当前位置的问题,您可以跟踪当前索引(在我的代码中名为 count 的变量中),然后执行以下操作:

return currentList.AddRange(ListElementsDivisibleBy3Recursive(input.Skip(count).ToList()));

使用Skip 摆脱已经处理的元素。当然,此时你已经有了 LINQ,所以你不妨这样做:

return currentList.AddRange(ListElementsDivisibleBy3Recursive(input.SkipWhile(i => i % 3 != 0).ToList()));

当然,这需要枚举两次,因为您无法从 SkipWhile 中获取值。但是你根本不需要递归,你可以写:

return currentList.Where( i => (i % 3) == 0).ToList();

【讨论】:

    【解决方案3】:

    如果你真的想通过递归来解决这个问题,你可以这样做

    static List<int> FilterMod3Internal(List<int> state, List<int> initialList, int index)
    {
        if (index >= initialList.Count())
        {
            return state;
        }
        else
        {
            var number = initialList[index];
            if (number%3 == 0)
            {
                state.Add(number);
    
            }
    
            return FilterMod3Internal(state, initialList, index + 1);
        }
    }
    static List<int> FilterMod3(List<int> list)
    {
        return FilterMod3Internal(new List<int>(), list, 0);
    }
    

    但正如其他答案所指出的那样,当它可以通过迭代(或至少是尾递归)来完成时,递归地做几乎任何事情都是一个坏主意。

    如果你想在不使用更多内存的情况下过滤现有集合,你可以这样做

    static void FilterListMod3Internal(List<int> list, int index)
    {
        if (index >= list.Count())
        {
            return;
        }
    
        var number = list[index];
    
        if (number%3 != 0)
        {
            list.RemoveAt(index);
            FilterListMod3Internal(list, index);
        }
        else
        {
            FilterListMod3Internal(list, index + 1);
        }
    }
    

    两个版本都是递归的,但它们需要有某种内部函数,它有额外的参数来维持函数调用之间的状态。

    你也可以像这样泛化这种方法

    static List<T> FilterInternal<T>(List<T> state, List<T> initialList, int index, Func<T, bool> filterFunction)
    {
        if (index >= initialList.Count())
        {
            return state;
        }
        else
        {
            var item = initialList[index];
            if (filterFunction(item))
            {
                state.Add(item);
    
            }
    
            return FilterInternal(state, initialList, index + 1, filterFunction);
        }
    }
    static List<T> Filter<T>(List<T> list, Func<T, bool> filterFunction)
    {
        return FilterInternal<T>(new List<T>(), list, 0, filterFunction);
    }
    

    而且不消耗额外内存的那个

    static void FilterListInternal<T>(List<T> list, int index, Func<T, bool> filterFunction)
    {
        if (index >= list.Count())
        {
            return;
        }
    
        var item = list[index];
    
        if (!filterFunction(item))
        {
            list.RemoveAt(index);
            FilterListInternal(list, index, filterFunction);
        }
        else
        {
            FilterListInternal(list, index + 1, filterFunction);
        }
    }
    
    static void FilterList<T>(List<T> list, Func<T, bool> filterFunction)
    {
        FilterListInternal<T>(list, 0, filterFunction);
    }
    

    如果您希望项目位于结果集合中,filterFunction 应该返回 true,否则返回 true。它很容易改变,所以,去尝试吧,你想要的。

    【讨论】:

      【解决方案4】:

      奇怪的问题:-)

      这是一种更灵活的解决方案:IList RecursiveFilter 的扩展方法接受谓词并返回 IEnumerable(可用于构造 List、Array 或其他东西)。

      using System;
      using System.Collections.Generic;
      
      namespace Sandbox
      {
          class Program
          {
              static void Main()
              {
                  var array = new[] { 1, 6, 8, 3, 6, 2, 9, 0, 7, 3, 5 };
                  var filtred = array.RecursiveFilter(i => i % 3 == 0);
      
                  foreach (var item in filtred)
                      Console.WriteLine(item);
              }
          }
      
          public static class MyCollectionExtensions
          {
              public static IEnumerable<T> RecursiveFilter<T>(this IList<T> collection, Func<T, bool> predicate, int index = 0)
              {
                  if (index < 0 || index >= collection.Count)
                      yield break;
                  if (predicate(collection[index]))
                      yield return collection[index];
                  foreach (var item in RecursiveFilter(collection, predicate, index + 1))
                      yield return item;
              }         
          }
      }
      

      【讨论】:

        【解决方案5】:

        递归遍历列表来过滤它并不是一个特别适合可变数组支持列表的算法。这是一种更适合处理不可变结构的方法。在处理不可变列表(通常建模为具有当前项和到另一个节点的链接的节点)时,递归解决方案非常适合此问题:

        public class Node<T>
        {
            public Node(T value, Node<T> tail)
            {
                Value = value;
                Tail = tail;
            }
        
            public T Value { get; private set; }
            public Node<T> Tail { get; private set; }
        }
        
        public static Node<int> ListElementsDivisibleBy3Recursive(Node<int> node)
        {
            if (node == null)
                return node;
            else if (node.Value % 3 == 0)
                return new Node<int>(node.Value, ListElementsDivisibleBy3Recursive(node.Tail));
            else
                return ListElementsDivisibleBy3Recursive(node.Tail);
        }
        

        我们有一个简单的递归案例,其中节点为空(每个列表的末尾都有一个Tail,即null)。然后我们有两种递归情况,要么当前节点的值与过滤器匹配,我们创建一个新列表,该节点在头部,尾部是列表其余部分的递归解决方案,或者当前节点的值不应该被包括在内,解决方案只是列表尾部的递归。

        List&lt;T&gt; 根本不适合这种类型的算法,因为它依赖于能够计算“包含第一项之后的所有内容的列表”以及轻松计算“等于旧列表,但添加了一项”,这两种操作都不是 List&lt;T&gt; 可以轻松或高效地执行的操作,但不可变列表通常将作为主要操作。

        【讨论】:

          【解决方案6】:

          这是我能想到的唯一方法。每次递归都会删除最后一个元素,直到列表为空,然后当递归展开时,将满足条件的元素添加回列表的末尾。

          List<int> ListElementsDivisibleBy3Recursive(List<int> input) {
              if (input.Count > 0) {
                  int last = input.Last();
                  input.RemoveAt(input.Count - 1);
                  input = ListElementsDivisibleBy3Recursive(input);
                  if (last % 3 == 0)
                      input.Add(last);
              }
              return input;
          }
          

          虽然从方法中返回任何东西都是没有意义的,因为无论返回值如何,input 都会根据需要进行修改。所以这达到了相同的结果:

          void ListElementsDivisibleBy3Recursive(List<int> input) {
              if (input.Count > 0) {
                  int last = input.Last();
                  input.RemoveAt(input.Count - 1);
                  ListElementsDivisibleBy3Recursive(input);
                  if (last % 3 == 0)
                      input.Add(last);
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-09-07
            • 1970-01-01
            • 2020-05-25
            • 1970-01-01
            • 2016-06-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多