Использование массива и IEnumerable в методе расширения - PullRequest
0 голосов
/ 04 июля 2019

Я пытаюсь написать метод расширения следующим образом:

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 делает:

Ошибка CS1061: «Массив» не содержит определения для «Пропустить» и нет доступного метода расширения «Пропустить»

Но когда яизменил типы параметров на IEnumerable<T>, он говорит, что IEnumerable не имеет свойства .Length, только Array имеет это.

ошибка CS1061: «IEnumerable» не содержитопределение «Длина» и отсутствие доступного метода расширения «Длина»

Когда я написал аналогичный код вне метода расширения, оба типа которого byte[], у меня не было проблем с использованием .Skip()и .Length, поскольку он легко преобразует byte[] в IEnumerable<byte> при необходимости.

Как я могу написать свой метод расширения для использования как .Skip, так и .Length?

Ответы [ 5 ]

3 голосов
/ 04 июля 2019

Вы можете сделать метод универсальным и изменить типы аргументов:

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

Стоит отметить, что этот подход к поиску подпоследовательностей не очень эффективен, и вы можете рассмотреть другие алгоритмы, такие как Бойер-Мур или Кнут-Моррис-Пратт .

1 голос
/ 04 июля 2019

Измените будущее и используйте это

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;
    }
0 голосов
/ 04 июля 2019

Вот решение, которое позволяет вам использовать 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;
    }

В этом решении используется упаковка объектов и создается несколько избыточных копий массивов.Если вам не нужно использовать Array или SequenceEquals(), я бы порекомендовал решение @ NetMage.

0 голосов
/ 04 июля 2019

Вы можете использовать IEnumerable с 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;
        }
0 голосов
/ 04 июля 2019

Если вы действительно хотите использовать Array, вы можете. Он не будет столь же производительным, как универсальные для большинства типов значений, так как они будут заключены в object, и необходимо выполнить общий тест метода Equals, но это возможно. Вместо использования LINQ, просто реализуйте собственное сравнение последовательностей. Кроме того, не начинайте так далеко, чтобы закончить, пытаясь сопоставить подпоследовательность.

Наконец, возвращаем -1, если не найден, так как 0 является допустимым совпадающим возвращаемым значением.

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