我想使用LINQ将C#中的序列拆分为序列序列。我做了一些调查,我发现的最接近的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将增加我的代码的优雅。
答案 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运算符,它根据任何条件分割序列。其参数是:
xs
:输入元素序列。func
:一个接受“当前”输入元素和状态对象的函数,并作为元组返回:
bool
说明输入序列是否应在“当前”元素之前拆分;和func
。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
的副作用。