简体   繁体   English

C# 使用 a.Skip(1) 作为输入的递归算法错误

[英]C# Recursive algorithm error using a.Skip(1) as input

代码和错误的图像

using System;
using System.Collections.Generic;
using System.Linq;

internal class Program
{
    public static bool Aa(int[] a, int k)
    {
        for (int i = 1; i < a.Length; i++)
            if (a[0] + a[i] == k)
                return true;
        if (a.Length != 1)
            Aa(a.Skip(1), k);
        return false;
    }

    static void Main(string[] args)
    {
        int[] a = { 1, 2, 3, 4, 2, 3, 2, 1 };
        Console.WriteLine(Aa(a, 10));
        Console.ReadLine();
    }
}

The following build error occurs on the recursive method call Aa(a.Skip(1), k);递归方法调用Aa(a.Skip(1), k);

Argument 1: cannot convert from 'System.Collections.IEnumerable' to 'int[]'参数 1:无法从“System.Collections.IEnumerable”转换为“int[]”

you need to pass an Array obj to Aa Like this:您需要像这样将 Array obj 传递给 Aa:

a.Skip().ToArray();

Like most LINQ methods, Skip is designed to work with enumerables, not arrays.与大多数 LINQ 方法一样, Skip旨在与枚举一起使用,而不是 arrays。

public static System.Collections.Generic.IEnumerable<TSource> Skip<TSource> (this System.Collections.Generic.IEnumerable<TSource> source, int count);

It is OK to pass in an array, but it will implicitly be upcast to the more generalized type IEnumerable , and that is also the type that will be returned by Skip .传入一个数组是可以的,但它会隐式地向上转换为更通用的类型IEnumerable ,这也是Skip将返回的类型。

int[] whatGoesIn = {1, 2, 3};
IEnumerable<int> whatComesOut = whatGoesIn.Skip(1);

You cannot safely downcast the return value to int[] .您不能安全地将返回值向下转换int[] As proposed by others, LINQ has a convenient method ToArray , but think twice before using it.正如其他人所建议的, LINQ 有一个方便的方法ToArray ,但在使用它之前请三思。 The method will create a completely new array object, which is often a total waste of memory as well as CPU time.该方法将创建一个全新的数组 object,这通常完全浪费 memory 以及 CPU 时间。 Only use this method when you really need an array .只有当你真的需要一个数组时才使用这个方法。

In your case, ToArray would have to be called once for every element of the original array.在您的情况下,必须为原始数组的每个元素调用一次ToArray This will put a lot of strain on the heap.这会给堆带来很大的压力。 With a big array, you will be seeing a lot of GC.使用大数组,您会看到很多 GC。

Make up your mind: either use Skip , or use arrays. Don't do both.下定决心:要么使用Skip ,要么使用 arrays。不要两者都做。

Option 1: Skip, without arrays选项 1:跳过,没有 arrays

Follow the LINQ philosophy and use enumerables all the way.遵循 LINQ 哲学,一路使用枚举。 This has the advantage that it will work on any enumerable: arrays, collections, lists, dictionaries...这样做的好处是它适用于任何可枚举的:arrays、collections、列表、字典...

So instead of declaring parameter a as an array:因此,不是将参数a声明为数组:

public static bool Aa(int[] a, int k)

declare a as an enumerable: a声明为可枚举:

public static bool Aa(IEnumerable<int> a, int k)

Notice this will immediately eliminate your error.请注意,这将立即消除您的错误。 It will introduce a few new ones though.不过,它会引入一些新的。 Like in the for loop;就像在for循环中一样; IEnumerable<int> has no property Length . IEnumerable<int>没有属性Length

for (int i = 1; i < a.Length; i++)
    if (a[0] + a[i] == k)
        return true;

Upon close inspection, you are basically looking for an array element that equals k - a[0] .经过仔细检查,您基本上是在寻找等于k - a[0]的数组元素。 Just use Contains :只需使用包含

bool found = a.Skip(1).Contains(k - a.First());
if (found) return true;

Another reference to Length :Length的另一个参考:

if (a.Length != 1)
    Aa(a.Skip(1), k);
return false;

That's weird;这很奇怪; you call Aa but don't do anything with its return value.您调用Aa但不对其返回值执行任何操作。 You probably meant this:你可能是这个意思:

if (a.Length != 1)
    return Aa(a.Skip(1), k);
return false;

I will not use Count , as it is potentially expensive on long enumerables.我不会使用Count ,因为它在长枚举上可能很昂贵。 I am not actually interested in the exact length;我实际上对确切的长度不感兴趣; we can stop counting after the second element.我们可以在第二个元素之后停止计数。

return a.Skip(1).Any() && Aa(a.Skip(1), k);

After refactoring, the whole function becomes a one-liner:重构后整个function变成了一行:

public static bool Aa(IEnumerable<int> a, int k)
{
    return a.Skip(1).Contains(k - a.First()) || (a.Skip(1).Any() && Aa(a.Skip(1), k));
}

I'd recommend to make the function robust against zero-length arrays by moving the 'Any' check to the front.我建议通过将“任何”检查移到前面来使 function 对零长度 arrays 具有鲁棒性。

public static bool Aa(IEnumerable<int> a, int k)
{
    return a.Any() && (a.Skip(1).Contains(k - a.First()) || Aa(a.Skip(1), k));
}

Option 2: arrays, without Skip选项 2:arrays,不跳过

Arrays have one big advantage over enumerables: they are fast . Arrays 与枚举相比有一大优势:速度快 OP's for loop is faster than my call to Contains . OP 的for循环比我调用Contains更快。 There is no need to use Skip ;无需使用Skip just start iterating at a different index.只需在不同的索引处开始迭代。

public static bool Aa(int[] a, int k, int start = 0)
{
    for (int i = start + 1; i < a.Length; i++)
        if (a[start] + a[i] == k)
            return true;
    if (start < a.Length)
        return Aa(a, k, start + 1);
    return false;
}

This is twice as fast as the ToArray solution, and three times as fast as enumerables.这是ToArray解决方案的两倍,是可枚举解决方案的三倍。

Note笔记

I'm not too thrilled about the use of recursion here, because nesting depth is proportional to array length.我对这里使用递归不太感兴趣,因为嵌套深度与数组长度成正比。 For long arrays, this may cause a stack overflow.对于 long arrays,这可能会导致堆栈溢出。 In theory, the compiler could optimize away the tail recursion, but even in .NET 6, it doesn't (although this answer suggests otherwise).理论上,编译器可以优化尾递归,但即使在 .NET 6 中,它也不会(尽管这个答案另有建议)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM