分区/分割/部分IEnumerable <t>到IEnumerable <ienumerable <t>&gt;基于使用LINQ的函数?</ienumerable <t> </t>

时间:2011-07-11 16:46:23

标签: linq data-partitioning

我想使用LINQ将C#中的序列拆分为序列序列。我做了一些调查,我发现的最接近的SO文章与this略有关系。

但是,这个问题只询问如何根据常量值对原始序列进行分区。我想根据操作对我的序列进行分区。

具体来说,我有一个包含十进制属性的对象列表。

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

假设我的序列为ExampleClassTheValue的相应值序列为:

{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 个答案:

答案 0 :(得分:6)

好的,我想我们可以做到这一点......

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 :(得分:1)

或者使用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分区为用于记录的临时表。如果我没记错的话,奇数结尾选择是为了防止在输入为字符串枚举时出现歧义。

答案 2 :(得分:1)

这是一个自定义LINQ运算符,它根据任何条件分割序列。其参数是:

  1. xs:输入元素序列。
  2. func:一个接受“当前”输入元素和状态对象的函数,并作为元组返回:
    • a bool说明输入序列是否应在“当前”元素之前拆分;和
    • 将传递给下一次func
    • 调用的状态对象
  3. initialState:在第一次调用时传递给func的状态对象。
  4. 这里是一个帮助类(需要因为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的副作用。
    •