Как избежать бесконечной рекурсии с помощью пользовательского перечислителя?

Я сделал метод расширения, чтобы найти количество последовательных значений в коллекции. Поскольку он является общим, Я разрешаю вызывающему определить "incrementor", который является Func, который должен увеличивать значение, чтобы проверить наличие "следующего" значения.

однако, если вызывающий объект передает неправильный инкрементор (т. е. x => x), это вызовет бесконечный рекурсивный цикл. Есть предложения по чистому способу предотвращения этого?

public static int CountConsecutive<T>(this IEnumerable<T> values, T startValue, Func<T, T> incrementor)
{
    if (values == null)
    {
        throw new ArgumentNullException("values");
    }
    if (incrementor == null)
    {
        throw new ArgumentNullException("incrementor");
    }
    var nextValue = incrementor(startValue);
    return values.Contains(nextValue)
        ? values.CountConsecutive(nextValue, incrementor) + 1
        : 1;
}

3 ответов


в чистом смысле это попытка Проблема Останова и неразрешимые. Для всех, кроме самых простых случаев, вам придется доверять тем, кто вызывает ваш метод.

Как показали другие, вы можете сделать простую проверку равенства, чтобы показать, что следующее значение отличается. Хранение каждого посещенного T будет работать, но вам придется беспокоиться о памяти в конце концов.

в стороне, вот легко реализуемый StackOverflowException поэтому вы должны быть осторожны с любым набором данных, который будет иметь много последовательных значений.

var x = Enumerable.Range(1, 100000).CountConsecutive(1, x => x+1);

чтобы справиться с самым простым случаем, вы можете сделать следующее:

var nextValue = incrementor(startValue);
if (nextValue.Equals(startValue)) {
    throw new ArgumentException("incrementor");
}

В общем случае этого:

public static int CountConsecutive<T>(this IEnumerable<T> values, T startValue, Func<T, T> incrementor) {
    if (values == null) {
        throw new ArgumentNullException("values");
    }
    if (incrementor == null) {
        throw new ArgumentNullException("incrementor");
    }
    ISet<T> seen = new HashSet<T>();
    return CountConsecutive(values, startValue, incrementor, seen);
}

private static int CountConsecutive<T>(IEnumerable<T> values, T startValue, Func<T, T> incrementor, ISet<T> seen) {
    if (!seen.Add(startValue)) {
        throw new ArgumentException("incrementor");
    }
    var nextValue = incrementor(startValue);
    return values.Contains(nextValue)
        ? values.CountConsecutive(nextValue, incrementor) + 1
        : 1;
}

вы можете сравнить nextValue с startValue (вам понадобится T для реализации IComparable).

Это решит эту ошибку, это не решит неприятную ошибку инкрементора, которая возвращает цикл - a1, a2, a3,..., an, a1. Я не думаю, что вы хотите обрабатывать этой случае