如何通过一些规则懒惰合并一些枚举

时间:2019-02-08 23:48:14

标签: c# .net linq morelinq

例如,我必须排序:

IEnumerable<float> List1 = new float[] {
    0f, 0.1f, 0.5f, 0.9f, // < 1
    1f, 1.1f, 1.5f, 1.9f, // < 2
    2f, 2.9f // < 3
    };

IEnumerable<int> List2 = new int[] {
    1,
    2,
    3,
    4
    };

我需要得到下一个结果:

0f, 0.1f, 0.5f, 0.9f, 1,
1f, 1.1f, 1.5f, 1.9f, 2,
2f, 2.9f, 3,
4

我写了两个函数,但是它们并不懒惰!

private static IEnumerable<object> Merge(IEnumerable<float> list1, IEnumerable<int> list2) {
    var queue1 = new Queue<float>( list1 ); // it is not lazy!!
    var queue2 = new Queue<int>( list2 );

    while (queue1.Any() && queue2.Any()) {
        if (queue1.Peek() < queue2.Peek()) {
            yield return queue1.Dequeue();
        } else {
            yield return queue2.Dequeue();
        }
    }

    while (queue1.Any()) {
        yield return queue1.Dequeue();
    }

    while (queue2.Any()) {
        yield return queue2.Dequeue();
    }
}


private static IEnumerable<object> Merge2(IEnumerable<float> list1, IEnumerable<int> list2) {
    var queue1 = new Queue<float>( list1 ); // it is not lazy!!

    foreach (var item2 in list2) {
        while (queue1.Any() && queue1.Peek() < item2) {
            yield return queue1.Dequeue();
        }
        yield return item2;
    }
}

我该如何进行懒惰评估?

2 个答案:

答案 0 :(得分:2)

我假设您有两个已经分别排序的序列,并且您想返回合并两个序列后的排序序列。该序列是延迟生成的,一次只读取每个序列中的下一个元素。

private static IEnumerable<object> Merge(IEnumerable<float> sequence1, IEnumerable<int> sequence2)
{
    // Get enumerators for iterating through the two sequences.
    using (var enumerator1 = sequence1.GetEnumerator())
    using (var enumerator2 = sequence2.GetEnumerator())
    {
        var remaining1 = enumerator1.MoveNext();
        var remaining2 = enumerator2.MoveNext();

        while (remaining1 && remaining2)
        {
            if (enumerator1.Current < enumerator2.Current)
            {
                yield return enumerator1.Current;
                remaining1 = enumerator1.MoveNext();
            }
            else
            {
                yield return enumerator2.Current;
                remaining2 = enumerator2.MoveNext();
            }
        }

        if (remaining1)
        {
            do { yield return enumerator1.Current; }
            while (enumerator1.MoveNext());
        }
        else if (remaining2)
        {
            do { yield return enumerator2.Current; }
            while (enumerator2.MoveNext());
        }
    }
}

更新:基于Enigmativity的评论,您可以将其推广为可与任何可比较的类型一起使用:

private static IEnumerable<T> Merge<T>(IEnumerable<T> sequence1, IEnumerable<T> sequence2, IComparer<T> comparer = null)
{
    if (comparer == null)
        comparer = Comparer<T>.Default;

    // Get enumerators for iterating through the two sequences.
    using (var enumerator1 = sequence1.GetEnumerator())
    using (var enumerator2 = sequence2.GetEnumerator())
    {
        var remaining1 = enumerator1.MoveNext();
        var remaining2 = enumerator2.MoveNext();

        while (remaining1 && remaining2)
        {
            if (comparer.Compare(enumerator1.Current, enumerator2.Current) < 0)
            {
                yield return enumerator1.Current;
                remaining1 = enumerator1.MoveNext();
            }
            else
            {
                yield return enumerator2.Current;
                remaining2 = enumerator2.MoveNext();
            }
        }

        if (remaining1)
        {
            do { yield return enumerator1.Current; }
            while (enumerator1.MoveNext());
        }
        else if (remaining2)
        {
            do { yield return enumerator2.Current; }
            while (enumerator2.MoveNext());
        }
    }
}

但是,对于OP将float[]int[]合并的示例,则需要转换第二个列表:

var result = Merge(List1, List2.Select(x => (float)x));

答案 1 :(得分:0)

我为IEnumerator<T>和更漂亮的Merge方法制作了一个舒适的包装纸。

public static IEnumerable<object> Merge(IEnumerable<float> list1, IEnumerable<int> list2) {
    using (var enumerator1 = new SmartEnumerator<float>( list1.GetEnumerator() ))
    using (var enumerator2 = new SmartEnumerator<int>( list2.GetEnumerator() )) {

        while (!enumerator1.IsCompleted && !enumerator2.IsCompleted) {
            if (enumerator1.Peek() < enumerator2.Peek()) {
                yield return enumerator1.Pull();
            } else {
                yield return enumerator2.Pull();
            }
        }

        while (!enumerator1.IsCompleted) {
            yield return enumerator1.Pull();
        }

        while (!enumerator2.IsCompleted) {
            yield return enumerator2.Pull();
        }
    }
}


public class SmartEnumerator<T> : IDisposable {

    private readonly IEnumerator<T> target;
    private bool hasValue;
    private T Value => target.Current;

    public bool IsCompleted {
        get {
            RequestNextValueIfNeeded();
            return !hasValue;
        }
    }


    public SmartEnumerator(IEnumerator<T> target) {
        this.target = target;
    }

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


    public T Peek() {
        if (IsCompleted && !hasValue) throw new InvalidOperationException( "Enumerator is already completed" );

        RequestNextValueIfNeeded();
        return Value;
    }

    public T Pull() {
        if (IsCompleted && !hasValue) throw new InvalidOperationException( "Enumerator is already completed" );

        RequestNextValueIfNeeded();
        hasValue = false;
        return Value;
    }


    // Helpers
    private void RequestNextValueIfNeeded() {
        if (!hasValue) RequestNextValue();
    }

    private void RequestNextValue() {
        hasValue = target.MoveNext();
    }


}