2016-07-21 15 views
5

Trong ví dụ này hoàn toàn là thực hành, đây là những gì tôi muốn quay trở lại:Có một toán tử hoạt động như Quét nhưng hãy để tôi trả về một IObservable <TResult> thay vì IObservable <TSource>?

Nếu hai học sinh tham gia một trường trong một khoảng thời gian nhất định, nói, 2 giây, sau đó tôi muốn cấu trúc dữ liệu trả về cả hai sinh viên, trường họ tham gia và khoảng thời gian giữa họ tham gia.

Tôi đã suy nghĩ cùng những dòng này:

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(); 
    } 
} 

Và đây là lĩnh vực:

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()); 
      } 
     } 
    } 
} 

Tuy nhiên, các nhà điều hành Scan cho phép tôi chỉ trả lại một quan sát được của cùng TSource. Select cũng sẽ không làm việc ở đây vì tôi không có thể nhìn về phía trước (một cái gì đó mà tôi có thể làm gì với Scan) và dự án mục hiện tại cùng với người tiếp theo, mặc dù Select cho phép tôi chuyển đổi TSource vào TResult.

Tôi đang tìm kiếm thứ gì đó ở giữa.

+3

['Observable.Scan'] (https://msdn.microsoft.com/en-us/library/hh212007.aspx) không cho phép bạn trả lại loại bộ tích lũy. – Lee

+0

Tệ của tôi. Bạn đúng. Nếu bạn đặt nó xuống như một câu trả lời? –

Trả lời

1
  1. Để so sánh cặp (gốc - dự án mục hiện hành cùng với người tiếp theo), bạn có thể sử dụng một phương pháp Buffer để xây dựng một chuỗi với cặp.
  2. Để tìm ra khoảng thời gian giữa các học sinh tham gia bằng cách sử dụng Timestamp thay vì phương pháp TimeInterval có thể hữu ích hơn vì dòng sau next.Interval - current.Interval <= timeSpan. Những gì bạn thực sự muốn một cái gì đó giống như pair[1].Timestamp - pair[0].Timestamp <= timeSpan

kết quả sau 4 cặp (Student 11, Sinh viên 12), (Sinh 13, Sinh viên 14), (Sinh 15, Sinh viên 16), là (Student 17, Sinh viên 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 
     }); 
  1. Như @Enigmativity đề cập đến nó sẽ tốt hơn nếu so sánh từng phần tử với người tiếp theo. Vì vậy, vì mục đích đó, chúng tôi có thể sử dụng phương thức Zip:

Kết quả sau 8 cặp (Sinh viên 10, Sinh viên 11) (Sinh viên 11, Sinh viên 12), (Sinh viên 13, Sinh viên 13) 14), (Sinh 14, Sinh viên 15), (Sinh 15, Sinh viên 16), (Sinh 16, Sinh viên 17), (Sinh 17, Sinh viên 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 
    }); 
+0

Thực sự rất đẹp. Cảm ơn nhiều. Tôi đã thực hiện nó bằng cách sử dụng 'Scan' và sẽ đăng nó sớm. Bạn nói đúng về điều kiện 'if'. Tôi đã phải thay đổi nó vì nó là sai lầm anyway.Nhưng đây là một phương pháp rất thú vị. Bởi vì trước khi hoàn thành giải pháp của tôi bằng cách sử dụng 'Scan', tôi đã thực sự tự hỏi liệu tôi có nên ghi lại' DateTime' một sinh viên được thừa nhận trong sự kiện 'Admitted' và gửi thông tin đó cùng với' EventArgs' hay không. Hóa ra tôi có thể làm mà không có điều đó chỉ với 'Quét 'nhưng điều này rất thú vị. –

+0

này nhóm truy vấn của học sinh theo cặp như '(0, 1), (2, 3), (4, 5)' vv, nhưng không kiểm tra '1 & 2',' 3 & 4', vv – Enigmativity

+0

@ Enigmativity - Có, nếu không một phần tử sẽ được lưu trữ hai lần nếu thích hợp, ví dụ như "Student 11" (Student 10, Student 11) và (Student 11, Student 12). Thêm một giải pháp cho phương pháp này. –

0

Dưới đây là những gì tôi đã làm:

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

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

    var observable = admissionObservable.TimeInterval() 
     .Scan<TimeInterval<EventPattern<StudentAdmittedEventArgs>>, StudentPair>(null, (previousPair, current) => 
     { 
      Debug.Print(string.Format($"Student joined after {current.Interval.TotalSeconds} seconds, timeSpan = {timeSpan.TotalSeconds} seconds")); 

      var pair = new StudentPair(); 

      if (previousPair == null) 
      { 
       pair.FirstStudent = null; 
       pair.SecondStudent = current.Value.EventArgs.Student; 
       pair.IntervalBetweenJoining = current.Interval; 
       pair.School = current.Value.EventArgs.School; 

       return pair; 
      } 

      if (current.Interval <= timeSpan) 
      { 
       pair.FirstStudent = previousPair.SecondStudent; 
       pair.SecondStudent = current.Value.EventArgs.Student; 
       pair.IntervalBetweenJoining = current.Interval; 
       pair.School = current.Value.EventArgs.School; 

       return pair; 
      } 
      else 
      { 
       return default(StudentPair); 
      } 
     }) 
     .Where(p => (p != default(StudentPair)) && (p.FirstStudent != null)); 

    var subscription = observable.Subscribe(StudentPairValueHandler); 

    school.FillWithStudents(4, TimeSpan.FromSeconds(1)); 
    school.FillWithStudents(2, TimeSpan.FromSeconds(10)); 
    school.FillWithStudents(3, TimeSpan.FromSeconds(2)); 
    school.FillWithStudents(2, TimeSpan.FromSeconds(5)); 
    school.FillWithStudents(5, TimeSpan.FromSeconds(0.6)); 

    Console.WriteLine("Press any key to exit the program"); 
    Console.ReadKey(); 
    subscription.Dispose(); 
} 

static void StudentPairValueHandler(StudentPair pair) 
{ 
    if (pair != null && pair.FirstStudent != null) 
    { 
     Console.WriteLine($"{pair.SecondStudent.Name} joined {pair.School.Name} {Math.Round(pair.IntervalBetweenJoining.TotalSeconds, 2)} seconds after {pair.FirstStudent.Name}."); 
    } 
} 

... 

public class StudentPair 
{ 
    public Student FirstStudent; 
    public Student SecondStudent; 
    public School School; 
    public TimeSpan IntervalBetweenJoining; 
} 


public static class Extensions 
{ 
    public static void FillWithStudents(this School school, int howMany) 
    { 
     FillWithStudents(school, howMany, TimeSpan.Zero); 
    } 

    public static void FillWithStudents(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++) 
     { 
      Thread.Sleep((int)gapBetweenEachAdmission.TotalMilliseconds); 

      school.AdmitStudent(Student.CreateRandom()); 
     } 
    } 

    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()); 
     } 
    } 
} 
+0

Tôi thấy những gì bạn đang làm ở đây, nhưng nó không phải là rất thành ngữ Rx. Xem các câu trả lời khác cho thiết kế "tốt hơn". –

+0

@LeeCampbell Cảm ơn bạn. Tôi nhận ra điều đó và dần dần cải thiện cách suy nghĩ Rx của mình. Trên một lưu ý khác, tôi phải nói với bạn rằng tôi tìm thấy cuốn sách của bạn như là một nguồn tài nguyên học tập tuyệt vời. Đó là một trong những cuốn sách hay nhất tôi đã đọc trong một thời gian dài. Nó được viết tốt và tổng thể đẹp. Tôi học được rất nhiều từ việc đọc nó. Tôi chưa đọc nó đầy đủ bởi vì tôi đọc lại nhiều phần của nó một lần nữa và một lần nữa và thực hành mọi thứ từ từ nhiều lần. –

1

Bạn có thể thử điều này và xem liệu nó có cung cấp cho bạn những gì bạn muốn không?

IObservable<EventPattern<StudentAdmittedEventArgs>[]> observable = 
    admissionObservable 
     .Publish(pxs => 
      pxs 
       .Window(pxs, x => Observable.Timer(timeSpan)) 
       .Select(ys => ys.Take(2))) 
     .SelectMany(ys => ys.ToArray()) 
     .Where(ys => ys.Skip(1).Any()); 
+0

Vì ranh giới đóng cửa sổ dựa trên thời gian, sẽ không trả lại * tất cả các sinh viên đã tham gia trong vòng 2 giây * và sau đó lấy phần thứ hai của lô/cửa sổ đó? –

+0

@ WaterCoolerv2 - Vâng, đó là những gì '.Take (2)' làm - nó chỉ nhận được cặp đầu tiên trong cửa sổ. Nó sẽ tạo ra kết quả chính xác. Tôi đã tự mình thử nghiệm trước khi đăng. – Enigmativity

Các vấn đề liên quan