1

i'm trying to understand functional programming with rx for js.

i have an Rx.Observable that emits "post" objects:

every post looks like this :

{
title: "sometitle",
author: "someauthor"
text: "sometext",
date: "somedate",
tags: ['tag1', 'tag2', ..., 'tagN']

}

and i want to transform that sequence into a sequence that emits:

{ 
tag: 'tagname',
postCount: n
}

This is what i have so far:

function tags(post) {     
   return post
            .tags
            .map(function(tag) { return { 'tag': tag, 'count': 1});     
}

posts
  .flatMap(tags)
  .groupBy(function(tagged) { return tagged.tag }) 
  . // don't know how to continue 

as i said before, my goal is to create a sequence/observable that emits {tag: 'tagname', postCount: n } for each tag

thx in advance

edit:

i forgot to mention that i was looking for a "node oriented" answer.

this is what i have so far. it works, but i'm not sure about the { ..., count: 1 } part. i'm looking for a more "elegant" solution.

posts
    .flatMap(tags)
    .map((tag) => {return {name: tag, count: 1}})
    .groupBy((tagcount) => {return tagcount.name})
    .flatMap((taggroup) => {return taggroup.reduce((a,x) => {return {tag: x.name, count: (a.count + x.count)}})})

1 Answer 1

1

It would be something like this:

// sequesnce of posts sequence with 10ms interval
var posts = Rx.Observable
  .fromArray([
    { tags: ['tag1', 'tag2'] },
    { tags: ['tag1', 'tag3'] },
    { tags: ['tag1'] },
    { tags: ['tag1', 'tag2', 'tag3'] }
  ])
  .zip(Rx.Observable.interval(10), Rx.helpers.identity)
  .do(logger('post:'));

// sequence of post counts by tags, and count changes
var tagsCountChanges = posts.scan(
  function (acc, post) {
    var counts = acc.counts;
    var changes = [];
    post.tags.forEach(function (tag) {
      counts[tag] = (counts[tag] || 0) + 1;
      changes.push({ tag: tag, postsCount: counts[tag] });
    });
    return { counts, changes };
  }, { counts: {}, changes: [] })
  .map(acc => acc.changes)
  .do(logger('tagsCountChanges:'));

var tagCountUpdates = tagsCountChanges
  .concatMap(function (changes) {
    return Rx.Observable
      .fromArray(changes);
  });

tagCountUpdates
  .forEach(logger('tagPostCounts:'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
<pre id="log"></pre>
<script>
  var log = document.getElementById('log');

  function logger(label) {
    return function(item) {
      log.appendChild(document.createTextNode(label + ' ' + JSON.stringify(item, null, 2) + '\n'));
    };
  }
</script>

Update (in response to edit1):

It will work in node too:) also you can drop logger and interval for posts sequence - it is just to show nice log of items with intermediate observables while running snippet in browser.

i'm not sure about the { ..., count: 1 } part. i'm looking for a more "elegant" solution.

Actually you can drop { ..., count: 1 } part entirely:

posts
    .flatMap(post => post.tags)
    .groupBy(Rx.helpers.identity)
    .flatMap(taggroup$ => 
       taggroup$.reduce((acc,tag) => {return {tag, count: acc.count+1}}, {count:0})
    )

About elegance: I like your solution - I consider it more expressive, and simpler than my. However my solution would be more performant at larger tag counts(because it will not create an internal observable for each tag).

Also my solution is slightly different from your - it will emit stream of tag counts changes, not just final counts (after posts stream completion).

You solution can be easily modified to achieve the same result - just replace reduce with scan.

And visa a versa - if only total counts are required, my solution can be simplified a lot:

posts.reduce(
  (counts, post) => {
    post.tags.forEach(tag => {
      counts[tag] = (counts[tag] || 0) + 1;
    });
    return counts;
  }, {})
  .flatMap(counts => 
     Object.keys(counts).map(
        tag => ({tag, count: counts[tag]})
     )
  )
Sign up to request clarification or add additional context in comments.

1 Comment

thx for your answer. i forgot to mention about node. i have edited my question and posted a "solution" that's working for me. however i'm not sure about its "elegance"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.