[英]Equivalent in C# to Haskell's Data.List.Span
Hello is there any efficient method implemented already to get the functionality of Haskell Data.List.span
?您好,是否已经实现了任何有效的方法来获得 Haskell
Data.List.span
的功能?
span :: (a -> Bool) -> [a] -> ([a], [a])
Basically given a list
and a predicate
i want to split the list in two after the first occurence of a false predicate.The elements after the pivot
element that tests False
may or may not respect the predicate , but I do not care.基本上给定一个
list
和一个predicate
我想在第一次出现错误谓词后将列表一分为二。测试False
的pivot
元素之后的元素可能会或可能不会尊重谓词,但我不在乎。
List: [1,2,3,1,2,3]
Predicate: x<3
Span: `span (x<3) [1,2,3,1,2,3]` => `([1,2],[3,1,2,3])`
Update I do not care of the elements after the first false predicate.I just want to split the list at the first occurence of False
predicate.更新我不关心元素的第一个假predicate.I后只想在第一次出现的分裂列表
False
断言。 The sequence can be True
after the first False
predicate but I still want to split it.在第一个
False
谓词之后序列可以是True
但我仍然想拆分它。
You could make use of TakeWhile
and Skip
:您可以使用
TakeWhile
和Skip
:
public static IEnumerable<IEnumerable<T>> SplitWhen<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate)
{
var first = enumerable.TakeWhile(predicate);
yield return first;
var second = enumerable.Skip(first.Count());
yield return second;
}
Update更新
To avoid multiple iterations, and not requiring the use of a list or array:为了避免多次迭代,并且不需要使用列表或数组:
public static IEnumerable<IEnumerable<T>> SplitWhen<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate)
{
yield return enumerable.TakeWhile(predicate);
yield return enumerable.TakeAfter(predicate);
}
public static IEnumerable<T> TakeAfter<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate)
{
bool yielding = false;
foreach (T item in enumerable)
{
if (yielding = yielding || !predicate(item))
{
yield return item;
}
}
}
If you are happy with using lists, then you can make a single pass through the source list to create two new lists, like so:如果您对使用列表感到满意,那么您可以单次遍历源列表以创建两个新列表,如下所示:
public static (List<T> part1, List<T> part2) SplitListBy<T>(List<T> source, Predicate<T> splitWhen)
{
var part1 = new List<T>();
int i;
for (i = 0; i < source.Count && !splitWhen(source[i]); ++i)
part1.Add(source[i]);
var part2 = source.GetRange(i, source.Count - i);
return (part1, part2);
}
This should be extremely performant.这应该是非常高效的。 Note that this uses a tuple to return the two lists, which requires C# 7 or later.
请注意,这使用元组返回两个列表,这需要 C# 7 或更高版本。 If you can't use c# 7+, you'll have to change the code to use an
out
parameter to return one of the lists.如果您不能使用 c# 7+,则必须更改代码以使用
out
参数来返回列表之一。
Test code:测试代码:
var list = new List<int>{ 1, 2, 3, 1, 2, 3 };
var (part1, part2) = SplitListBy(list, item => item >= 3);
Console.WriteLine(string.Join(", ", part1));
Console.WriteLine(string.Join(", ", part2));
Output:输出:
1, 2
3, 1, 2, 3
If you don't need two new lists, but just want to use the original list for one part and a single new list for the other part, you can do it like this:如果您不需要两个新列表,而只想对一个部分使用原始列表,而对另一部分使用一个新列表,您可以这样做:
public static List<T> SplitListBy<T>(List<T> source, Predicate<T> splitWhen)
{
int i;
for (i = 0; i < source.Count && !splitWhen(source[i]); ++i)
;
var part2 = source.GetRange(i, source.Count - i);
source.RemoveRange(i, source.Count - i);
return part2;
}
Test code for this is very similar:对此的测试代码非常相似:
var list = new List<int>{ 1, 2, 3, 1, 2, 3 };
var part2 = SplitListBy(list, item => item >= 3);
Console.WriteLine(string.Join(", ", list));
Console.WriteLine(string.Join(", ", part2));
(Output is the same as the other test code.) (输出与其他测试代码相同。)
At the time that I'm writing this answer, I don't think that any of the other answers faithfully replicate Haskell's span
function.在我写这个答案的时候,我认为其他任何答案都没有忠实地复制 Haskell 的
span
函数。 That's okay, you may actually be looking for something else, but I wanted to add this for completion's sake.没关系,您实际上可能正在寻找其他东西,但为了完成起见,我想添加它。
First, you can't necessarily assume that span
only iterates over the input list once.首先,您不必假设
span
只对输入列表迭代一次。 It's difficult to reason about Haskell's run-time behaviour because of its lazy evaluation, but consider this list:由于其惰性求值,很难对 Haskell 的运行时行为进行推理,但请考虑以下列表:
xs = [trace "one" 1, trace "two" 2, trace "three" 3,
trace "one" 1, trace "two" 2, trace "three" 3]
Here I've deliberately used trace
from Debug.Trace
so that we can observe what's going on.在这里,我特意使用了
Debug.Trace
trace
,以便我们可以观察发生了什么。 Specifically, I want to point your attention to what happens if you iterate over the lists independently, as one would probably do in 'real' code:具体来说,我想让您注意如果您独立迭代列表会发生什么,就像在“真实”代码中可能会做的那样:
Prelude Data.List Debug.Trace> (l, r) = span (< 3) xs
Prelude Data.List Debug.Trace> l
one
[1two
,2three
]
Iterating over the first list stops at the first value that evaluates to False
, so that's fine and efficient.迭代第一个列表在第一个评估为
False
值处停止,因此这很好且有效。 That's not the case, however, when you print the second list:但是,当您打印第二个列表时,情况并非如此:
Prelude Data.List Debug.Trace> r
one
two
three
[3,one
1,two
2,three
3]
Notice that while it only prints [3, 1, 2, 3]
, it iterates over the entire list.请注意,虽然它只打印
[3, 1, 2, 3]
,但它会遍历整个列表。 How could it do otherwise?不然怎么办? It's a function.
这是一个函数。 It doesn't maintain a bookmark over how far it's already iterated the list.
它不会维护它已经迭代列表多远的书签。
On the other hand, the function does handle infinite lists:另一方面,该函数确实处理无限列表:
Prelude Data.List> take 10 $ fst $ span (< 3) $ repeat 1
[1,1,1,1,1,1,1,1,1,1]
Prelude Data.List> take 10 $ fst $ span (< 3) $ repeat 3
[]
Prelude Data.List> take 10 $ snd $ span (< 3) $ repeat 3
[3,3,3,3,3,3,3,3,3,3]
As far as I can tell, few of the other answers (as I'm writing this) handle infinite lists.据我所知,很少有其他答案(在我写这篇文章时)处理无限列表。
In C#, lazily evaluated lists are modelled with IEnumerable<T>
, so the best I've been able to come up with is this:在 C# 中,懒惰评估的列表是用
IEnumerable<T>
建模的,所以我能想到的最好的是:
public static (IEnumerable<T>, IEnumerable<T>) Span<T>(
this IEnumerable<T> source,
Func<T, bool> pred)
{
return (source.TakeWhile(pred), source.SkipWhile(pred));
}
which, admittedly, is hardly above the Fairbairn threshold .诚然,这几乎没有超过费尔贝恩门槛。 It does, however, handle infinite sequences in the same way as
span
does:但是,它确实以与
span
相同的方式处理无限序列:
> var (left, right) = new[] { 1, 2, 3, 1, 2, 3 }.Span(x => x < 3);
> left
TakeWhileIterator { 1, 2 }
> right
SkipWhileIterator { 3, 1, 2, 3 }
> var (left, right) = 1.RepeatInfinite().Span(x => x < 3);
> left.Take(10)
TakeIterator { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
> var (left, right) = 3.RepeatInfinite().Span(x => x < 3);
> right.Take(10)
TakeIterator { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }
> left.Take(10)
TakeIterator { }
the simplest way to use ToLookup使用ToLookup的最简单方法
example例子
var listInt = new List<int>{1, 2, 3, 4, 5, 6};
var result = listInt.ToLookup(x => x > 3);
Result结果
[[1,2,3], [4,5,6]] [[1,2,3], [4,5,6]]
Edit编辑
var listInt = new List<int> { 1, 2, 3, 1, 2, 3 };
Create an extension method创建扩展方法
public static IEnumerable<T> TakeUntil<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
foreach (var item in source)
{
if (!predicate(item))
break;
yield return item;
}
}
and call it并称之为
var first = listInt.TakeUntil(x => x < 3);
var second = listInt.Skip(first.Count());
Result结果
first = [1,2]第一个 = [1,2]
second = [3, 1, 2, 3]秒 = [3, 1, 2, 3]
I don't think there is a native .NET Framework or .NET Core method that does this, so you'll probably have to write your own.我认为没有本地 .NET Framework 或 .NET Core 方法可以执行此操作,因此您可能必须自己编写。 Here is my extension method implementation of this:
这是我的扩展方法实现:
public static Tuple<IEnumerable<T>, IEnumerable<T>> SplitWhen<T>(this IEnumerable<T> self, Func<T, bool> func)
{
// Enumerate self to an array so we don't do it multiple times
var enumerable = self as T[] ?? self.ToArray();
var matching = enumerable.TakeWhile(func).ToArray();
var notMatching = enumerable.Skip(matching.Length);
return new Tuple<IEnumerable<T>, IEnumerable<T>>(matching, notMatching);
}
This method will return a tuple with tuple.Item1
being the part of the list that matches the predicate, and tuple.Item2
being the rest of the list.此方法将返回一个元组,其中
tuple.Item1
是列表中与谓词匹配的部分,而tuple.Item2
是列表的其余部分。
This method needs to be declared in a separate static class as it is an extension method for IEnumerable<T>
.此方法需要在单独的静态类中声明,因为它是
IEnumerable<T>
的扩展方法。 You can also use Tuple construction/ deconstruction if you want to name Item1
and Item2
something different如果您想将
Item1
和Item2
命名为不同的名称,也可以使用元组构造/解构
I believe you are looking for ac# IEnumerable.我相信您正在寻找 ac# IEnumerable。 You could write for example
你可以写例如
IEnumerable<int> list = new List<int> { 1,2,3,4,5,6};
var list1 = list.Where(x=>x>3); //deferred execution
var list2 = list.Where(x=>x<=3); //deferred execution
I have accepted @Matthew Watson solution.Though i will also post a little modified version using the Span
and ReadOnlyMemory
我已经接受了@Matthew Watson 解决方案。虽然我也会使用
Span
和ReadOnlyMemory
发布一些修改过的版本
public static (IEnumerable<T>first,IEnumerable<T> second) Span<T>(this ReadOnlyMemory<T> original,Func<T,bool> predicate) {
List<T> list = new List<T>();
int splitIndex = 0;
for (int i = 0; i < original.Length && !predicate(original.Span[i]); i++) {
list.Add(original.Span[splitIndex=i]);
}
var part2 = original.Slice(splitIndex);
return (list, part2.ToArray());
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.