首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何基于属性( zip,join)压缩2序列

如何基于属性( zip,join)压缩2序列
EN

Stack Overflow用户
提问于 2021-06-11 13:49:00
回答 3查看 234关注 0票数 2

我希望基于一个类似于使用枚举时加入它们的公共属性来压缩2个序列的项。怎样才能通过第二次考试?

代码语言:javascript
运行
复制
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;

public class SequenceTests
{
    private class Entry
    {
        public Entry(DateTime timestamp, string value)
        {
            Timestamp = timestamp;
            Value = value;
        }

        public DateTime Timestamp { get; }

        public string Value { get; }
    }

    private readonly IEnumerable<Entry> Tasks = new List<Entry>
    {
        new Entry(new DateTime(2021, 6, 6), "Do homework"),
        new Entry(new DateTime(2021, 6, 7), "Buy groceries"), // <-- This date is also in the People collection!
        new Entry(new DateTime(2021, 6, 8), "Walk the dog"),
    };

    private readonly IEnumerable<Entry> People = new List<Entry>
    {
        new Entry(new DateTime(2021, 6, 4), "Peter"),
        new Entry(new DateTime(2021, 6, 5), "Jane"),
        new Entry(new DateTime(2021, 6, 7), "Paul"), // <-- This date is also in the Tasks collection!
        new Entry(new DateTime(2021, 6, 9), "Mary"),
    };

    private class Assignment
    {
        public string Task { get; set; }

        public string Person { get; set; }
    }

    [Test]
    public void Join_two_collections_should_succeed()
    {
        var assignments = Tasks
            .Join(People, 
                task => task.Timestamp,
                person => person.Timestamp,
                (task, person) => new Assignment { Task = task.Value, Person = person.Value });

        Assert.AreEqual(1, assignments.Count());
        Assert.AreEqual("Buy groceries", assignments.First().Task);
        Assert.AreEqual("Paul", assignments.First().Person);
    }

    [Test]
    public async Task Zip_two_sequences_should_succeed()
    {
        var tasks = Observable.ToObservable(Tasks);
        var people = Observable.ToObservable(People);

        var sequence = tasks
            .Zip(people)
            .Select(pair => new Assignment { Task = pair.First.Value, Person = pair.Second.Value });

        var assignments = await sequence.ToList();

        Assert.AreEqual(1, assignments.Count);
        Assert.AreEqual("Buy groceries", assignments.First().Task);
        Assert.AreEqual("Paul", assignments.First().Person);
    }
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-06-13 05:07:44

可观察到的Zip运算符与可枚举版本的工作原理完全相同。你没有在第一次测试中使用它,所以它不像你需要的操作员。

您需要的只是SelectMany操作符。

请尝试以下查询:

代码语言:javascript
运行
复制
var sequence =
    from t in tasks
    from p in people
    where t.Timestamp == p.Timestamp
    select new Assignment { Task = t.Value, Person = p.Value };

这适用于你的测试。

票数 0
EN

Stack Overflow用户

发布于 2021-06-19 06:50:12

这里有一个自定义的Join操作符,可以用来解决这个问题。它基于MergeGroupByUntilSelectMany运算符:

代码语言:javascript
运行
复制
/// <summary>
/// Correlates the elements of two sequences based on matching keys. Results are
/// produced for all combinations of correlated elements that have an overlapping
/// duration.
/// </summary>
public static IObservable<TResult> Join<TLeft, TRight, TKey, TResult>(
    this IObservable<TLeft> left,
    IObservable<TRight> right,
    Func<TLeft, TKey> leftKeySelector,
    Func<TRight, TKey> rightKeySelector,
    Func<TLeft, TRight, TResult> resultSelector,
    TimeSpan? keyDuration = null,
    IEqualityComparer<TKey> keyComparer = null)
{
    // Arguments validation omitted
    keyComparer ??= EqualityComparer<TKey>.Default;
    var groupDuration = keyDuration.HasValue ?
        Observable.Timer(keyDuration.Value) : Observable.Never<long>();
    return left
        .Select(x => (x, (TRight)default, Type: 1, Key: leftKeySelector(x)))
        .Merge(right.Select(x => ((TLeft)default, x, Type: 2, Key: rightKeySelector(x))))
        .GroupByUntil(e => e.Key, _ => groupDuration, keyComparer)
        .Select(g => (
            g.Where(e => e.Type == 1).Select(e => e.Item1),
            g.Where(e => e.Type == 2).Select(e => e.Item2).Replay().AutoConnect(0)
        ))
        .SelectMany(g => g.Item1.SelectMany(_ => g.Item2, resultSelector));
}

用法示例:

代码语言:javascript
运行
复制
IObservable<Assignment> sequence = tasks
    .Join(people, t => t.Timestamp, p => p.Timestamp,
        (t, p) => new Assignment { Task = t.Value, Person = p.Value });

应该注意的是,如果不缓冲两个源序列产生的所有元素,就无法保证100%的正确性来解决这个问题。显然,如果序列包含无限个元素,这将不能很好地扩展。

如果牺牲绝对正确性以支持可伸缩性是可以接受的,则可以使用可选的keyDuration参数来配置存储密钥(及其相关元素)可以在内存中保留的最大持续时间。如果具有此密钥的新元素由leftright序列产生,则过期密钥可能会被重新生成。

上面的实现在包含大量元素的序列中表现得相当好。加入两个相同大小的序列,每个序列都有10万个元素,在我的电脑里需要8秒。

票数 1
EN

Stack Overflow用户

发布于 2021-06-20 15:57:36

我不喜欢任何一个贴出的答案。它们都是同一主题的变体:将两个序列的所有成员无限期地保存在内存中,并在新的左元素出现时遍历整个右序列,并在出现新的右元素时递增地检查左键。两者都无限期地回答了O(L + R)内存,并且都是O(R * L)时间复杂度(其中L和R是左序列和右序列的大小)。

如果我们处理的是集合(或可枚举的),这将是一个足够的答案。但我们不是:我们在处理可观察到的问题,答案应该承认这一点。实际用例之间可能存在很大的时间间隔。这个问题是由一个可枚举的测试用例提出的。如果它只是一个可枚举的,正确的答案是转换回可枚举并使用Linq的Join。如果有可能出现具有时间间隔的长时间运行的流程,那么答案应该承认,您可能只希望加入某个时间内已经发生的元素,从而释放进程中的内存。

这满足了测试答案,同时允许一个时间框:

代码语言:javascript
运行
复制
var sequence = tasks.Join(people,
        _ => Observable.Timer(TimeSpan.FromSeconds(.5)),
        _ => Observable.Timer(TimeSpan.FromSeconds(.5)),
        (t, p) => (task: t, person: p)
    )
    .Where(t => t.person.Timestamp == t.task.Timestamp)
    .Select(t => new Assignment { Task = t.task.Value, Person = t.person.Value });

这将为.5秒的每个元素创建一个窗口,这意味着如果左元素和右元素在彼此的.5秒内弹出,则它们将匹配。在.5秒之后,每个元素都从内存中释放出来。如果出于任何原因,您不希望从内存中释放并无限期地将所有对象保存在内存中,这就足够了:

代码语言:javascript
运行
复制
var sequence = tasks.Join(people,
        _ => Observable.Never<Unit>(),
        _ => Observable.Never<Unit>(),
        (t, p) => (task: t, person: p)
    )
    .Where(t => t.person.Timestamp == t.task.Timestamp)
    .Select(t => new Assignment { Task = t.task.Value, Person = t.person.Value });
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67938188

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档