递归过滤不可变列表

时间:2015-02-25 17:35:23

标签: c# arrays list recursion

假设我有一个整数数组,我需要返回一个数组,其中包含可按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);
            }
        }

如果我被允许将指针传递到我所在列表中的位置会更容易,但我所拥有的只是输入列表和要使用的返回列表。如何删除列表中的元素,同时保留与条件匹配的元素?或者我是否会以错误的方式解决这个问题?我不确定我应该如何理解这一点。我也意识到我的基本情况也是错误的。

6 个答案:

答案 0 :(得分:1)

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

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);
    }
}

答案 1 :(得分:0)

整个问题很奇怪。

要解决处理当前位置的初始问题,您可以跟踪当前索引(在我的代码中名为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();

答案 2 :(得分:0)

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

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。这很容易改变,所以,试试吧,你想要的。

答案 3 :(得分:0)

奇怪的问题: - )

这是一个更灵活的解决方案: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;
        }         
    }
}

答案 4 :(得分:0)

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

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<T>根本不适合这种类型的算法,因为它依赖于能够计算“包含第一项之后的所有内容的列表”以及还能够轻松计算“相同的新列表”到旧列表,但添加了一个项目“,这两个都不是List<T>可以轻松或有效执行的操作,但不可变列表通常将作为主要操作。

答案 5 :(得分:-2)

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