# Is there an easier way to use Rx's GroupBy operator?

I'm just playing with the concept of GroupBy inside Rx. So, I wondered how hard it would be to write a console application that continuously reads lines, groups them by similar words and just prints out the word among the current count of how often the word was written before. It really was a breeze to do that but I wonder if my attempt could be rewritten in a more elegant way. Especially if I can get rid of the nested Subscribes.

Here is what I have:

static void Main(string[] args)
{
var subject = new Subject<string>();

var my = subject.GroupBy(x => x);

my.Subscribe(x => x.Scan(new { Chars = string.Empty, Count = 0},
(a, chars) => new { Chars = chars, Count = a.Count + 1})
.Subscribe(result => Console.WriteLine("You typed {0} {1} times", result.Chars,
result.Count)));

while (true)
{
}
}


Result

test
you typed test 1 times
test
you typed test 2 times
hallo
you typed test 1 times
test
you typed test 3 times
...

– Christoph
Nov 12, 2011 at 17:58
• I think you got downvotes because you're not even asking a question.
– Otiel
Nov 12, 2011 at 18:02
• It would help to know what you would consider more elegant. I would suggest more newlines in the lambda you pass to Subscribe would make a bigger difference. Nov 12, 2011 at 18:03
• @Otiel, well the question is if the code snippet can be improved. I should point out that I'm striving for improvement in terms of mathematical beauty. For example, in my first answer I improved it with the SelectMany so that the code is more in line with the whole functional programming approach. In the second answer I avoided passing the string information through the Scan() method in favor for the Zip.
– Christoph
Nov 12, 2011 at 18:16
• @Christoph and I added it back. I hope you don't mind, but it really does need to be tagged C#, and since the question is right on topic here at CodeReview, I don't think you'll have any further downvote issues.
Sep 8, 2012 at 8:50

You could use also use a counter provider per grouping observable, in the form of zipping a large stream of integers, like so:

static void Main(string[] args)
{
var input = GetConsoleLines().ToObservable().TakeWhile(line => !string.IsNullOrEmpty(line));
input.GroupBy(word => word)
.SelectMany(grouping => grouping.Zip(Observable.Range(1, int.MaxValue), (s, i) => new Tuple<string, int>(s, i)))
.Subscribe(r => Console.WriteLine(\$"you typed {r.Item1} {r.Item2} times"));
}

public static IEnumerable<string> GetConsoleLines()
{
while(true)
}


Ok, this looks a lot better! Can anyone even do better than that?

    static void Main(string[] args)
{
var subject = new Subject<string>();

subject
.GroupBy(x => x)
.SelectMany(x => x.Scan(new { Chars = string.Empty, Count = 0},(a, chars) => new { Chars = chars, Count = a.Count + 1}))
.Subscribe(result => Console.WriteLine("You typed {0} {1} times", result.Chars, result.Count));

while (true)
{
}
}

• I never used Rx. However, having used LINQ, I should be able to follow this code ... and I do not really. I can barely tell what it does and how fast it runs. I like an explicit dictionary-based approach. If I had to debug/maintain this method, I would rewrite it. Apr 11, 2012 at 3:03

Ok, here is another one! I think this one is easier to read since we don't depend on the Scan method to carry the string through the computation:

    static void Main(string[] args)
{
var subject = new Subject<string>();

subject
.GroupBy(x => x)
.SelectMany(x => x.Scan(0, (count, _) => ++count).Zip(x, (count, chars) => new { Chars = chars, Count = count}))
.Subscribe(result => Console.WriteLine("You typed {0} {1} times", result.Chars, result.Count));

while (true)
{
}
}


Can anyone do even better?

This looks a lot cleaner to me. Should also showcase how much more helpful it is when you give more descriptive names to your variables.

static void Main(string[] args)
{

.Subscribe(lineGroup =>
{
lineGroup.Scan(0, (acc, _) => ++acc)
.Subscribe(count =>
{
var line = lineGroup.Key;
var timeSuffix = count == 1 ? "" : "s";
Console.WriteLine("You typed {0} {1} time{2}.", line, count, timeSuffix);
});
});