FirstOrLast IObservable Extension - c#

I want to run through an IObservable<T> looking for an element that matches a predicate, and if not found, return the last element of the IObservable<T>. I don't want to have to store the entire contents of the IObservable<T>, and I don't want to loop through the IObservable twice, so I've set up an extension method
public static class ObservableExtensions
{
public static IObservable<T> FirstOrLastAsync<T>(this IObservable<T> source, Func<T, bool> pred)
{
return Observable.Create<T>(o =>
{
var hot = source.Publish();
var store = new AsyncSubject<T>();
var d1 = hot.Subscribe(store);
var d2 = hot.FirstAsync(x => pred(x)).Amb(store).Subscribe(o);
var d3 = hot.Connect();
return new CompositeDisposable(d1, d2, d3);
});
}
public static T FirstOrLast<T>(this IObservable<T> source, Func<T, bool> pred)
{
return source.FirstOrLastAsync(pred).Wait();
}
}
The Async method creates a hot observable from a potentially cold one passed in. It subscribes an AsyncSubject<T> to remember the last element, and an IObservable<T> that looks for the element. It then takes the first element from either of those IObservable<T>s, which ever returns a value first via .Amb (AsyncSubject<T> doesn't return a value until it gets an .OnCompleted message).
My questions are the following:
Can this be written better or more concisely using different Observable methods?
Do all of those disposables need to be included in the CompositeDisposable?
When the hot observable is completed without finding a matching element, is there a race condition between FirstAsync throwing an exception, and the AsyncSubject propagating its value?
If so, do I need to change the line to:
var d2 = hot.Where(x => pred(x)).Take(1).Amb(store).Subscribe(o);
I'm pretty new to RX, and this is my first extension on IObservable.
EDIT
I ended up going with
public static class ObservableExtensions
{
public static IObservable<T> FirstOrLastAsync<T>(this IObservable<T> source, Func<T, bool> pred)
{
var hot = source.Publish().RefCount();
return hot.TakeLast(1).Amb(hot.Where(pred).Take(1).Concat(Observable.Never<T>()));
}
public static T FirstOrLast<T>(this IObservable<T> source, Func<T, bool> pred)
{
return source.FirstOrLastAsync(pred).First();
}
}

You could Amb the two cases you want together.
If your source observable is cold, you can do a Publish|Refcount.
public static IObservable<T> FirstOrLast<T>(this IObservable<T> source, Func<T, bool> predicate)
{
return source.TakeLast(1).Amb(source.Where(predicate).Take(1));
}
Test:
var source = Observable.Interval(TimeSpan.FromSeconds(0.1))
.Take(10)
.Publish()
.RefCount();
FirstOrLast(source, i => i == 5).Subscribe(Console.WriteLine); //5
FirstOrLast(source, i => i == 11).Subscribe(Console.WriteLine); //9

I've tried to produce a "simpler" query that works and so far nothing.
If I stick with your basic structure I can offer a slight improvement. Try this:
public static IObservable<T> FirstOrLastAsync<T>(
this IObservable<T> source, Func<T, bool> pred)
{
return Observable.Create<T>(o =>
{
var hot = source.Publish();
var store = new AsyncSubject<T>();
var d1 = hot.Subscribe(store);
var d2 =
hot
.Where(x => pred(x))
.Concat(store)
.Take(1)
.Subscribe(o);
var d3 = hot.Connect();
return new CompositeDisposable(d1, d2, d3);
});
}
It's not hugely better, but I like it better than using Amb. It's just a tad cleaner I think.

Related

Reuse result from first observable in second in c#

The result of the first observable (itemsCache.GetAllKeys()) is a list of strings. If that list contains certain string (DOKUMENTI_KEY), then the second observable should be called. If the first observable doesn't contain the string, then an empty list should be returned.
public static IObservable<TResult> If<TSource, TResult>(
this IObservable<TSource> source,
Func<TSource, bool> predicate,
Func<TSource, IObservable<TResult>> thenSource,
Func<TSource, IObservable<TResult>> elseSource)
{
return source
.SelectMany(
value => predicate(value)
? thenSource(value)
: elseSource(value));
}
public IObservable<List<Dokument>> GetDokumenti()
{
return Observable.If(
() => itemsCache.GetAllKeys().SelectMany(x => x).Contains(DOKUMENTI_KEY).GetAwaiter().GetResult(),
itemsCache.GetAllKeys().SelectMany(y => y).Where(a => a == DOKUMENTI_KEY).SelectMany(z => itemsCache.GetObject<List<Dokument>>(z)),
Observable.Return(new List<Dokument>())
);
}
Is there a better way to do this?

C# - match ID to filename using functional principles

I'm rewriting an old C# project from scratch trying to figure out how it can be improved with the use of functional design. So far I've stuck with a couple of principles (except where the GUI is concerned):
Set every variable in every class as readonly and assign it a value only once.
Use immutable collections
Don't write code with side effects.
Now I'm trying to create a function which, given a folder, using yield return, enumerates a list of objects, one for each file in the given folder. Each object contains a unique ID, starting from firstAssignedID, and a filename.
Thing is, I'm not sure how to approach the problem at all. Is what I've just described even the right way of thinking about it? My code so far is a half-baked, incomplete mess. Is it possible to use a lambda here? Would that help, or is there a better way?
The FileObject class simply contains a string fileName and an int id, and the FileObject constructor simply and naively creates an instance given those two values.
public IEnumerable<FileObject> EnumerateImagesInPath(string folderPath, int firstAssignedID)
{
foreach (string path in Directory.EnumerateFiles(folderPath)
{
yield return new FileObject(Path.GetFileName(imagePath) , );
}
}
The most functional way of doing what you want is this:
IEnumerable<FileObject> EnumerateImagesInPath(string path, int firstAssignedID) =>
Enumerable.Zip(
Enumerable.Range(firstAssignedID, Int32.MaxValue),
Directory.EnumerateFiles(path),
FileObject.New);
With a FileObject type defined like so:
public class FileObject
{
public readonly int Id;
public readonly string Filename;
FileObject(int id, string fileName)
{
Id = id;
Filename = fileName;
}
public static FileObject New(int id, string fileName) =>
new FileObject(id, fileName);
}
It doesn't use yield, but that doesn't matter because Enumerable.Range and Enumerable.Zip do, so it's a lazy function just like your original example.
I use Enumerable.Range to create a lazy list of integers from firstAssignedId to Int32.MaxValue. This is zipped together with the enumerable of files in the directory. FileObject.New(id. path) is invoked as part of the zip computation.
There is no in-place state modification like the accepted answer (firstAssignedID++), and the whole function can be represented as an expression.
The other way of achieving your goal is to use the fold pattern. It is the most common way of aggregating state in functional programming. This is how to define it for IEnumerable
public static class EnumerableExt
{
public static S Fold<S, T>(this IEnumerable<T> self, S state, Func<S, T, S> folder) =>
self.Any()
? Fold(self.Skip(1), folder(state, self.First()), folder)
: state;
}
You should be able to see that its a recursive function that runs a delegate (folder) on the head of the list if there is one, then uses that as new state when calling recursively calling Fold. If it reaches the end of the list, then the aggregate state is returned.
You may notice that the implementation of EnumerableExt.Fold can blow up the stack in C# (because of a lack of tail-call optimisation). So a better way of implementing the Fold function is to do so imperatively:
public static S Fold<S, T>(this IEnumerable<T> self, S state, Func<S, T, S> folder)
{
foreach(var x in self)
{
state = folder(state, x);
}
return state;
}
There is a dual to Fold known as FoldBack (sometimes they're called 'fold left' and 'fold right'). FoldBack essentially aggregates from the tail of the list to the head, where Fold is from the head to the tail.
public static S FoldBack<S, T>(this IEnumerable<T> self, S state, Func<S, T, S> folder)
{
foreach(var x in self.Reverse()) // Note the Reverse()
{
state = folder(state, x);
}
return state;
}
Fold is so flexible, for example you could implement Count for an enumerable in terms of fold like so:
int Count<T>(this IEnumerable<T> self) =>
self.Fold(0, (state, item) => state + 1);
Or Sum like so:
int Sum<int>(this IEnumerable<int> self) =>
self.Fold(0, (state, item) => state + item);
Or most of the IEnumerable API!
public static bool Any<T>(this IEnumerable<T> self) =>
self.Fold(false, (state, item) => true);
public static bool Exists<T>(this IEnumerable<T> self, Func<T, bool> predicate) =>
self.Fold(false, (state, item) => state || predicate(item));
public static bool ForAll<T>(this IEnumerable<T> self, Func<T, bool> predicate) =>
self.Fold(true, (state, item) => state && predicate(item));
public static IEnumerable<R> Select<T, R>(this IEnumerable<T> self, Func<T, R> map) =>
self.FoldBack(Enumerable.Empty<R>(), (state, item) => map(item).Cons(state));
public static IEnumerable<T> Where<T>(this IEnumerable<T> self, Func<T, bool> predicate) =>
self.FoldBack(Enumerable.Empty<T>(), (state, item) =>
predicate(item)
? item.Cons(state)
: state);
It's very powerful, and allows the aggregation of state for a collection (so this allows us to do firstAssignedId++ without an imperative in-place state modification).
Our FileObject example is a little more complex than Count or Sum, because we need to maintain two pieces of state: the aggregate ID and the resulting IEnumerable<FileObject>. So our state is a Tuple<int, IEnumerable<FileObject>>
IEnumerable<FileObject> FoldImagesInPath(string folderPath, int firstAssignedID) =>
Directory.EnumerateFiles(folderPath)
.Fold(
Tuple.Create(firstAssignedID, Enumerable.Empty<FileObject>()),
(state, path) => Tuple.Create(state.Item1 + 1, FileObject.New(state.Item1, path).Cons(state.Item2)))
.Item2;
You can make this even more declarative by providing some extension and static methods for Tuple<int, IEnumerable<FileObject>>:
public static class FileObjectsState
{
// Creates a tuple with state ID of zero (Item1) and an empty FileObject enumerable (Item2)
public static readonly Tuple<int, IEnumerable<FileObject>> Zero =
Tuple.Create(0, Enumerable.Empty<FileObject>());
// Returns a new tuple with the ID (Item1) set to the supplied argument
public static Tuple<int, IEnumerable<FileObject>> SetId(this Tuple<int, IEnumerable<FileObject>> self, int id) =>
Tuple.Create(id, self.Item2);
// Returns the important part of the result, the enumerable of FileObjects
public static IEnumerable<FileObject> Result(this Tuple<int, IEnumerable<FileObject>> self) =>
self.Item2;
// Adds a new path to the aggregate state and increases the ID by one.
public static Tuple<int, IEnumerable<FileObject>> Add(this Tuple<int, IEnumerable<FileObject>> self, string path) =>
Tuple.Create(self.Item1 + 1, FileObject.New(self.Item1, path).Cons(self.Item2));
}
The extension methods capture the operations on the aggregate state and make the resulting fold computation very clear:
IEnumerable<FileObject> FoldImagesInPath(string folderPath, int firstAssignedID) =>
Directory.EnumerateFiles(folderPath)
.Fold(
FileObjectsState.Zero.SetId(firstAssignedID),
FileObjectsState.Add)
.Result();
Obviously using Fold for the use-case you provided is overkill, and that's why I used Zip instead. But the more general problem you were struggling with (functional aggregate state) is what Fold is for.
There is one more extension method I used in the example above: Cons:
public static IEnumerable<T> Cons<T>(this T x, IEnumerable<T> xs)
{
yield return x;
foreach(var a in xs)
{
yield return a;
}
}
More info on cons can be found here
If you want to learn more about using functional technique in C#, please check my library: language-ext. It will give you a ton of stuff that the C# BCL is missing.
Using yeild seems unnecessary:
public IEnumerable<FileObject> EnumerateImagesInPath(string folderPath, int firstAssignedID)
{
foreach (FileObject File in Directory.EnumerateFiles(folderPath)
.Select(FileName => new FileObject(FileName, firstAssignedID++)))
{
yield return File;
}
}

Using and Maintaining IGroupedObservable

In my Rx code, I'm using GroupBy to create an IObservable<IGroupedObservable<T>>. From there I am looking to perform some transformations on the IGroupedObservable, while maintaining knowledge of the group (Key).
For example,
IObservable<IGroupedObservable<T>> CreateGroup(this IObservable<T> obs)
{
return obs.GroupBy(o => o.Something);
}
IGroupedObservable<A> Foo(this IGroupedObservable<T> obs)
{
return obs.Select(o => new A(o));
}
IGroupedObservable<B> Bar(this IGroupedObservable<A> obs)
{
return obs.Select(o => new B(o));
}
IObservable<IGroupedObservable<B>> stillGrouped = initialObservable.CreateGroup().Select(grouped => grouped.Foo().Bar());
Of course I can't do this, because the Select() in Foo loses the IGroupedObservable-ness.
Has anyone seen any solutions to this?
EDIT TLDR I'm trying to compose operations that depend on an observable already being grouped, and I'd like the type system to enforce this for me. I suppose I could make these operations take the group Key, and perform a Where on that Key up front, but that's messier and slower.
Hmm.
One brute force way is to replicate the observable methods you need. Something like this:
private class GroupedObservable<TKey, TElement> : IGroupedObservable<TKey, TElement>
{
private readonly IObservable<TElement> _o;
private readonly TKey _k;
public TKey Key { get { return _k } }
public GroupedObservable(TKey key, IObservable<TElement> o)
{
_key = key;
_o = ;
}
public IDisposable Subscribe(IObserver<TElement> observer) { return _o.Subscribe(observer); }
}
public static IGroupedObservable<TKey, TResult> Select<TKey, TSource, TResult>(this IGroupedObservable<TKey, TSource> source, Func<TSource, TResult> selector)
{
return new GroupedObservable<TKey, TResult>(source.Key, ((IObservable<TSource>)source).Select(selector));
}
Over time you'll build up a library of the methods you need.
Another simpler way is to just use Select to transform your IGroupedObservable<TKey, TElement> into IObservable<KeyValuePair<TKey, TElement>>:
public static IObservable<KeyValuePair<TKey, TValue>> ToKV<TKey, TValue>(this IGroupedObservable<TKey, TValue> g)
{
return g.Select(v => new KeyValuePair<TKey, TValue>(g.Key, v));
}
initialObservable.CreateGroup().Select(group => group.ToKV());

Passing an expression as a parameter

I'm trying to build a generic GroupBy Method, I guess it should be something like this
var result = GenericGroupBy<List<DTO>>(dataList, g=>g.Id);
public object GenericGroupBy<T>(object data, Func<T, bool> groupByExpression)
{
return ((List<T>)data).GroupBy(groupByExpression);
}
But I can not make it work.
How to pass expression like g=>g.Id?
Currently there are two problems:
Your method expects a Func<T, bool> and I suspect g => g.Id fails that because your Id property isn't a bool
You're currently specifying List<DTO> as the type argument, when I suspect you really want just DTO.
Given your comments, this will work:
var result = GenericGroupBy<DTO>(dataList, g => g.Id);
public object GenericGroupBy<T>(object data, Func<T, int> groupByExpression)
{
return ((List<T>)data).GroupBy(groupByExpression);
}
... but I'd make it a bit more general unless you always want to group by int:
var result = GenericGroupBy<DTO, int>(dataList, g => g.Id);
public object GenericGroupBy<TElement, TKey>
(object data, Func<TElement, TKey> groupByExpression)
{
return ((IEnumerable<TElement>)data).GroupBy(groupByExpression);
}
Note how I've also changed the cast from List<T> to IEnumerable<T> - you don't need it to be a List<T>, so why cast to that?

How to find the max id of any table.

I would like something like this:
public int NumberStudent()
{
int i = 0;
if (db.Tbl_Student.ToList().Count() > 0)
i = db. Tbl_Student.Max(d => d.id);
return i;
}
However, I would like to use it on any table:
public int FindMaxId(string TableName)
{
int i =0;
if ('db.'+TableName+'.ToList().Count() > 0' )
i = db. TableName.Max(d => d.id);
return i ;
}
I know it is wrong, but I'm not sure how to do it.
You can use the IEnumerable/IQueryable extension method DefaultIfEmpty for this.
var maxId = db.Tbl_Student.Select(x => x.Id).DefaultIfEmpty(0).Max();
In general, if you do Q.DefaultIfEmpty(D), it means:
If Q isn't empty, give me Q; otherwise, give me [ D ].
Below I have written a simple wrapper around the existing Max extension method that allows you provide an empty source (the table you were talking about).
Instead of throwing an exception, it will just return the default value of zero.
Original
public static class Extensions
{
public static int MaxId<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
if (source.Any())
{
return source.Max(selector);
}
return 0;
}
}
This was my attempt, which as noted by Timothy is actually quite inferior. This is because the sequence will be enumerated twice. Once when calling Any to check if the source sequence has any elements, and again when calling Max.
Improved
public static class Extensions
{
public static int MaxId<TSource>(this IQueryable<TSource> source, Func<TSource, int> selector)
{
return source.Select(selector).DefaultIfEmpty(0).Max();
}
}
This implementation uses Timothy's approach. By calling DefaultIfEmpty, we are making use of deferred execution and the sequence will only be enumerated when calling Max. In addition we are now using IQueryable instead of IEnumerable which means we don't have to enumerate the source before calling this method. As Scott said, should you need it you can create an overload that uses IEnumerable too.
In order to use the extension method, you just need to provide a delegate that returns the id of the source type, exactly the same way you would for Max.
public class Program
{
YourContext context = new YourContext();
public int MaxStudentId()
{
return context.Student.MaxId(s => s.Id);
}
public static void Main(string[] args)
{
Console.WriteLine("Max student id: {0}", MaxStudentId());
}
}
public static class Extensions
{
public static int MaxId<TSource>(this IQueryable<TSource> source, Func<TSource, int> selector)
{
return source.Select(selector).DefaultIfEmpty(0).Max();
}
}
db.Tbl_Student.Aggregate(0, (maxId, s) => Math.Max(maxId, s.Id))
or
db.Tbl_Student.Max(s => (int?)s.Id) ?? 0

Categories