在扩展方法中使用 Array 和 IEnumerable

Using Array and IEnumerable in an Extension Method

我正在尝试编写这样的扩展方法:

static class MyExtensions
{
    public static int FindSubArray(this Array x, Array y)
    {
        int offset = 0;
        for (int i = 0; i < x.Length; ++i)
        {
            if (y.SequenceEqual(x.Skip(i).Take(y.Length)))
            {
                offset = i;
                break;
            }
        }
        return offset;
    }
}

但是,编译器告诉我 Array 没有 .Skip() 方法。只有 IEnumerable 会:

error CS1061: 'Array' does not contain a definition for 'Skip' and no accessible extension method 'Skip'

但是当我将参数类型更改为 IEnumerable<T> 时,它说 IEnumerable 没有 .Length 属性,只有 Array 有那个.

error CS1061: 'IEnumerable' does not contain a definition for 'Length' and no accessible extension method 'Length'

当我在扩展方法之外编写类似的代码时,两种类型都为 byte[],我使用 .Skip().Length 没有问题,因为它很容易转换 byte[] 在需要时添加到 IEnumerable<byte>

如何编写我的扩展方法以同时使用 .Skip.Length

您可以使方法通用并更改参数类型:

public static int FindSubArray<T>(this T[] x, T[] y) { ... }

或者,您可以使用 IReadOnlyCollection<T>,它实现了 IEnumerable<T> 并具有 Count 属性:

public static int FindSubArray<T>(this IReadOnlyCollection<T> x, IReadOnlyCollection<T> y)
{
    int offset = 0;
    for (int i = 0; i < x.Count; ++i)
    {
        if (y.SequenceEqual(x.Skip(i).Take(y.Count)))
        {
            offset = i;
            break;
        }
    }
    return offset;
}

值得注意的是,这种查找子序列的方法效率不高,您可能需要查看其他算法,例如 Boyer-Moore or Knuth-Morris-Pratt

如果你真的想使用 Array,你可以。对于大多数值类型,它的性能不如泛型,因为它们将被装箱到 object 并且必须完成泛型 Equals 方法测试,但这是可能的。不使用 LINQ,只需实现您自己的序列比较。另外,不要开始太远,否则你会 运行 结束尝试匹配子序列。

最后,当未找到 return -1 时,因为 0 是一个有效的匹配 return 值。

static class MyExtensions {
    public static int FindSubArray(this Array x, Array y) {
        for (int i = 0; i < x.Length-y.Length+1; ++i) {
            var found = true;
            for (int j = 0; j < y.Length; ++j) {
                if (!((IList)x)[i + j].Equals(((IList)y)[j])) {
                    found = false;
                    break;
                }
            }
            if (found)
                return i;
        }
        return -1;
    }
}

您可以使用 IEnumerable with Count 而不是 Length 属性。

public static class MyExtensions
    {
        public static int FindSubArray<T>(this IEnumerable<T> x, IEnumerable<T> y)
        {
            int offset = 0;
            for (int i = 0; i < x.Count(); ++i)
            {
                if (y.SequenceEqual(x.Skip(i).Take(y.Count())))
                {
                    offset = i;
                    break;
                }
            }
            return offset;
        }

这是一个允许您使用 Array SequenceEquals() 的解决方案。 (虽然我会回应其他人的意见,他们认为这不是一个非常高效的解决方案,也可能不是最方便的。)

    public static int FindSubArray(this Array x, Array y)
    {
        int offset = 0;

        var loYArray = new List<Object>();
        var ieYArray = y.GetEnumerator();
        while (ieYArray.MoveNext())
            loYArray.Add(ieYArray.Current);

        for (int i = 0; i < x.Length; ++i)
        {
            var loXSubArray = new List<Object>();
            var ieXArray = x.GetEnumerator();
            var iSkip = 0;

            while (ieXArray.MoveNext())
            {
                iSkip++;
                if (iSkip > i)
                    loXSubArray.Add(ieXArray.Current);
                if (loXSubArray.Count >= y.Length)
                    break;
            }

            if (loYArray.SequenceEqual(loXSubArray))
                return i;
        }
        return -1;
    }

此解决方案使用对象装箱并创建数组的多个冗余副本。如果你不需要使用ArraySequenceEquals(),我推荐@NetMage的解决方案。

更改签名并使用此

public static int FindSubArray<T>(this IReadOnlyList<T> sourceCollection, IReadOnlyList<T> collectionToFind)
    {
        for (var i = 0; i <= sourceCollection.Count - collectionToFind.Count; i++)
        {
            var matched = true;
            for (var j = 0; j < collectionToFind.Count; j++)
            {
                if (sourceCollection[i + j].Equals(collectionToFind[j]))
                    continue;

                matched = false;
                break;
            }

            if (matched)
                return i;
        }
        return -1;
    }