简体   繁体   English

在 C# 中等同于 Haskell 的 Data.List.Span

[英]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我想在第一次出现错误谓词后将列表一分为二。测试Falsepivot元素之后的元素可能会或可能不会尊重谓词,但我不在乎。

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 :您可以使用TakeWhileSkip

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如果您想将Item1Item2命名为不同的名称,也可以使用元组构造/解构

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 解决方案。虽然我也会使用SpanReadOnlyMemory发布一些修改过的版本

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM