Разделение / разделение / раздел IEnumerable <T>на IEnumerableна основе функции, использующей LINQ? - PullRequest
5 голосов
/ 11 июля 2011

Я хотел бы разбить последовательность в C # на последовательность последовательностей, используя LINQ. Я провел некоторое исследование, и ближайшая SO-статья, которую я нашел, немного связана с this .

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

В частности, у меня есть список объектов, которые содержат десятичное свойство.

public class ExampleClass
{
    public decimal TheValue { get; set; }
}

Допустим, у меня есть последовательность ExampleClass, и соответствующая последовательность значений TheValue равна:

{0,1,2,3,1,1,4,6,7,0,1,0,2,3,5,7,6,5,4,3,2,1}

Я бы хотел разделить исходную последовательность на IEnumerable<IEnumerable<ExampleClass>> со значениями TheValue, похожими на:

{{0,1,2,3}, {1,1,4,6,7}, {0,1}, {0,2,3,5,7}, {6,5,4,3,2,1}}

Я просто заблудился от того, как это будет реализовано. ТАК, ты можешь помочь?

У меня сейчас серьезное уродливое решение, но у меня есть "ощущение", что LINQ увеличит элегантность моего кода.

Ответы [ 3 ]

6 голосов
/ 11 июля 2011

Хорошо, я думаю, мы можем сделать это ...

public static IEnumerable<IEnumerable<TElement>>
    PartitionMontonically<TElement, TKey>
    (this IEnumerable<TElement> source,
     Func<TElement, TKey> selector)
{
    // TODO: Argument validation and custom comparisons
    Comparer<TKey> keyComparer = Comparer<TKey>.Default;

    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
        {
            yield break;
        }
        TKey currentKey = selector(iterator.Current);
        List<TElement> currentList = new List<TElement> { iterator.Current };
        int sign = 0;
        while (iterator.MoveNext())
        {
            TElement element = iterator.Current;
            TKey key = selector(element);
            int nextSign = Math.Sign(keyComparer.Compare(currentKey, key));

            // Haven't decided a direction yet
            if (sign == 0)
            {
                sign = nextSign;
                currentList.Add(element);
            }
            // Same direction or no change
            else if (sign == nextSign || nextSign == 0)
            {
                currentList.Add(element);
            }
            else // Change in direction: yield current list and start a new one
            {
                yield return currentList;
                currentList = new List<TElement> { element };
                sign = 0;
            }
            currentKey = key;
        }
        yield return currentList;
    }
}

Полностью не проверено, но я думаю, что это может сработать ...

1 голос
/ 24 января 2013

Вот пользовательский оператор LINQ, который разбивает последовательность по любому критерию.Его параметры:

  1. xs: последовательность элементов ввода.
  2. func: функция, которая принимает «текущий» элемент ввода и объект состояния и возвращает каккортеж:
    • a bool с указанием, следует ли разбивать входную последовательность перед элементом «current»;и
    • объект состояния, который будет передан для следующего вызова func.
  3. initialState: объект состояния, который передается func вего первый вызов.

Здесь, вместе с вспомогательным классом (требуется, потому что yield return, очевидно, не может быть вложенным):

public static IEnumerable<IEnumerable<T>> Split<T, TState>(
                  this IEnumerable<T> xs,
                  Func<T, TState, Tuple<bool, TState>> func, 
                  TState initialState)
{
    using (var splitter = new Splitter<T, TState>(xs, func, initialState))
    {
        while (splitter.HasNext)
        {
            yield return splitter.GetNext();
        }
    }
}
internal sealed class Splitter<T, TState> : IDisposable
{
    public Splitter(IEnumerable<T> xs, 
                    Func<T, TState, Tuple<bool, TState>> func, 
                    TState initialState)
    {
        this.xs = xs.GetEnumerator();
        this.func = func;
        this.state = initialState;
        this.hasNext = this.xs.MoveNext();
    }

    private readonly IEnumerator<T> xs;
    private readonly Func<T, TState, Tuple<bool, TState>> func;
    private bool hasNext;
    private TState state;

    public bool HasNext { get { return hasNext; } }

    public IEnumerable<T> GetNext()
    {
        while (hasNext)
        {
            Tuple<bool, TState> decision = func(xs.Current, state);
            state = decision.Item2;
            if (decision.Item1) yield break;
            yield return xs.Current;
            hasNext = xs.MoveNext();
        }
    }

    public void Dispose() { xs.Dispose(); }
}

Примечание: Вот некоторые из проектных решений, которые вошли в метод Split:

  • Должно быть только однопередать последовательность.
  • Состояние сделано явным, чтобы можно было исключить побочные эффекты func.
1 голос
/ 12 июля 2011

альтернативно с операторами linq и некоторыми злоупотреблениями .net закрытиями по ссылке.

public static IEnumerable<IEnumerable<T>> Monotonic<T>(this IEnumerable<T> enumerable)
{
  var comparator = Comparer<T>.Default;
  int i = 0;
  T last = default(T);
  return enumerable.GroupBy((value) => { i = comparator.Compare(value, last) > 0 ? i : i+1; last = value; return i; }).Select((group) => group.Select((_) => _));
}

Взят из некоторого случайного служебного кода для разбиения IEnumerable на временную таблицу для ведения журнала. Если я правильно помню, нечетное окончание Select предназначено для предотвращения неоднозначности, когда ввод является перечислением строк.

...