是否有一个像Scan一样的运算符,但让我返回一个IObservable <TResult>而不是IObservable <TSource>?

Wat*_* v2 5 c# system.reactive

在这个纯粹用于练习的示例中,这是我想要返回的内容:

如果两个学生在指定的时间内(例如2秒)加入学校,那么我想要一个数据结构返回学生,他们加入的学校以及他们加入之间的时间间隔.

我一直在思考这些问题:

class Program
{
    static void Main(string[] args)
    {
        ObserveStudentsJoiningWithin(TimeSpan.FromSeconds(2));
    }

    static void ObserveStudentsJoiningWithin(TimeSpan timeSpan)
    {
        var school = new School("School 1");

        var admissionObservable =
            Observable.FromEventPattern<StudentAdmittedEventArgs>(school, "StudentAdmitted");

        var observable = admissionObservable.TimeInterval()
            .Scan((current, next) =>
            {
                if (next.Interval - current.Interval <= timeSpan)
                {
                    // But this won't work for me because
                    // this requires me to return a TSource
                    // and not a TResult
                }
            });

        var subscription = observable.Subscribe(TimeIntervalValueHandler);

        school.FillWithStudentsAsync(10, TimeSpan.FromSeconds(3));
        school.FillWithStudentsAsync(8, TimeSpan.FromSeconds(1));

        Console.WriteLine("Press any key to exit the program");
        Console.ReadKey();
        subscription.Dispose();
    }
}
Run Code Online (Sandbox Code Playgroud)

这是域名:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SchoolManagementSystem
{
    public class Student
    {
        private static int _studentNumber; 

        public Student(string name)
        {
            Name = name;
        }

        public string Name { get; set; }

        public static Student CreateRandom()
        {
            var name = string.Format($"Student {++_studentNumber}");

            return new Student(name);
        }

        public override string ToString()
        {
            return Name;
        }
    }

    public class School: IEnumerable<Student>
    {
        private List<Student> _students;

        public event StudentAdmitted StudentAdmitted;

        public string Name { get; set; }

        public School(string name)
        {
            Name = name;
            _students = new List<Student>();
        }

        public void AdmitStudent(Student student)
        {
            if (!_students.Contains(student))
            {
                _students.Add(student);

                OnStudentAdmitted(this, student);
            }
        }

        protected virtual void OnStudentAdmitted(School school, Student student)
        {
            var args = new StudentAdmittedEventArgs(school, student);

            StudentAdmitted?.Invoke(this, args);
        }

        public IEnumerator<Student> GetEnumerator()
        {
            return _students.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    public delegate void StudentAdmitted(object sender, StudentAdmittedEventArgs args);

    public class StudentAdmittedEventArgs : EventArgs
    {
        public StudentAdmittedEventArgs(School school, Student student): base()
        {
            School = school;
            Student = student;
        }

        public School School { get; protected set; }
        public Student Student { get; protected set;  }
    }

    public static class Extensions
    {
        public async static void FillWithStudentsAsync(this School school, int howMany, TimeSpan gapBetweenEachAdmission)
        {
            if (school == null)
                throw new ArgumentNullException("school");

            if (howMany < 0)
                throw new ArgumentOutOfRangeException("howMany");

            if (howMany == 1)
            {
                school.AdmitStudent(Student.CreateRandom());
                return;
            }

            for (int i = 0; i < howMany; i++)
            {
                await Task.Delay(gapBetweenEachAdmission);

                school.AdmitStudent(Student.CreateRandom());
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,Scan操作符允许我仅返回相同的可观察量TSource.Select也不会在这里工作,因为我无法向前看(我可以做的事情Scan)并将当前项目和下一个项目一起投影,即使Select允许我转换TSourceTResult.

我正在寻找介于两者之间的东西.

And*_*toy 1

  1. 对于成对比较(原始- 将当前项目与下一个项目一起投影),您可以使用Buffer方法来构建成对的序列。
  2. 为了找出学生加入之间的时间间隔,使用Timestamp而不是TimeInterval方法可能更有用,因为以下行next.Interval - current.Interval <= timeSpan。你真正想要的是类似的东西pair[1].Timestamp - pair[0].Timestamp <= timeSpan

以下结果分为 4 对(学生 11、学生 12)、(学生 13、学生 14)、(学生 15、学生 16)、(学生 17、学生 18):

var admissionObservable = Observable
        .FromEventPattern<StudentAdmittedEventArgs>(school, "StudentAdmitted")
        .Timestamp()
        .Buffer(2)
        .Where(pair => pair[1].Timestamp - pair[0].Timestamp <= timeSpan)
        .Select(pair => new JoiningData
        {
            Students = Tuple.Create(pair[0].Value.EventArgs.Student, pair[1].Value.EventArgs.Student),
            School = pair[0].Value.EventArgs.School,
            Interval = pair[1].Timestamp - pair[0].Timestamp
        });
Run Code Online (Sandbox Code Playgroud)
  1. 正如@Enigmativity 提到的,最好将每个元素与下一个元素进行比较。为此,我们可以使用Zip方法:

以下结果分为 8 对(学生 10、学生 11)、(学生 11、学生 12)、(学生 12、学生 13)、(学生 13、学生 14)、(学生 14、学生 15)、(学生 15、学生 16) ), (学生 16, 学生 17), (学生 17, 学生 18):

var admissionObservable = Observable
     .FromEventPattern<StudentAdmittedEventArgs>(school, "StudentAdmitted")
     .Timestamp();        

admissionObservable
    .Zip(admissionObservable.Skip(1), (a, b) => Tuple.Create(a,b))        
    .Where(pair => pair.Item2.Timestamp - pair.Item1.Timestamp <= timeSpan)        
    .Select(pair => new JoiningData
    {
        Students = Tuple.Create(pair.Item1.Value.EventArgs.Student, pair.Item2.Value.EventArgs.Student),
        School = pair.Item1.Value.EventArgs.School,
        Interval = pair.Item2.Timestamp - pair.Item1.Timestamp
    });
Run Code Online (Sandbox Code Playgroud)