12

Given a function that generates random numbers, how would you create an infinite Observable that produces random numbers at random intervals?

function getRandomNumber() {
  // Assume this function returns a random number, e.g. 198
}

function getRandomDelay() {
  // Assume this function returns a random delay in ms, e.g. 2000
}

Here is an example of a desired Observable:

---198--------64-------3---2----------18------->  (indefinitely)

3ms     7ms       6ms   3ms     10ms
Misha Moroshko
  • 166,356
  • 226
  • 505
  • 746

4 Answers4

14

As an alternative, if you don't want to live in a confusing timeout-world, you could write this entirely as a stream:

// the stream
const randomizer$ = Rx.Observable.of("")
  .switchMap(() => Rx.Observable
             .timer(getRandomDelay())
             .mapTo(getRandomNumber()))
  .repeat();

// subscribe to it
randomizer$.subscribe(num => console.log("Random number after random delay" + num));


// your utility functions
function getRandomNumber() {
  return ~~(Math.random() * 200)
}

function getRandomDelay() {
  return Math.random() * 1000
}

Working example here: http://jsbin.com/zipocaneya/edit?js,console


Alternative: Create the random number first and then add a delay (if the time of execution does not matter)

// the stream
const randomizer$ = Rx.Observable.of("")
  .switchMap(() => Rx.Observable
             .of(getRandomNumber())
             .delay(getRandomDelay()
  )
  .repeat();

// subscribe to it
randomizer$.subscribe(num => console.log("Random number after random delay" + num));

Additional note: Since there is no concurrency or async-operation going on outside of the stream, instead of switchMap you could just as well use concatMap or flatMap - in this case they all work the same.

olsn
  • 16,644
  • 6
  • 59
  • 65
  • Thanks! This looks a bit simpler: `Rx.Observable.of('').concatMap(() => Rx.Observable.of(getRandomNumber()).delay(getRandomDelay())).repeat()` – Misha Moroshko Jan 03 '17 at 06:16
  • Its different though - if your random number creation is depending on some existing, mutable data then it does matter __when__ the actual logic is executed: before or after the delay and the more common case is to have an execution done __after__ a delay - but i'll edit the answer later and add it as an option – olsn Jan 03 '17 at 07:10
  • Not sure I understand. In the "Alternative" solution, the number is emitted after the delay, not before. [Example](http://jsbin.com/fizolibuli/1/edit?js,console) – Misha Moroshko Jan 03 '17 at 19:49
  • 1
    The time of _emit_ is the same, yes - but the time of _generating_ the random number is different - not sure if this makes a difference for your case, but I have had other cases, where `delay` was added after a complex calculation and by the time of _emit_ the calculated value was already outdated - in that case it is better to first have the delay and then do the calculation (most likely for you it won't make a difference, especially not when you are just using `Math.random()`) – olsn Jan 03 '17 at 20:45
  • You do NOT need the repeat() here, it is already an infinite stream. – Zolcsi Jun 08 '17 at 13:17
  • @Zolcsi Please check the jsbin-example, remove the `repeat()` and see that it does only emit 1 value, instead of inifinite values. – olsn Jun 08 '17 at 13:29
  • Why not simply `Rx.Observable.of(getRandomNumber()).delay(getRandomDelay()).repeat()` ? Otherwise great idea :)! – maxime1992 Sep 18 '18 at 08:52
  • @maxime1992 because it will always emit same random number at the very same delay. – olsn Sep 18 '18 at 09:08
9
const { Observable }  = require("rxjs");

const ob = new Observable(sub => {
  let timeout = null;
  
  // recursively send a random number to the subscriber
  // after a random delay
  (function push() {
    timeout = setTimeout(
      () => {
        sub.next(getRandomNumber());
        push();
      },
      getRandomDelay()
    );
  })();
  
  // clear any pending timeout on teardown
  return () => clearTimeout(timeout);
});

ob.subscribe(console.log);
Lucas Colombo
  • 513
  • 2
  • 5
  • 17
nickclaw
  • 708
  • 5
  • 11
0

Would you take a look at https://github.com/cszredwan/crazyObservable? I am sure it gonna be of help, this is a custom observable to emit (randomly!) a fixed amount of data over a fixed time horizon.

The problem with your question is that randomly is being used with imprecise meaning. crazyObservable draws a single stream of data of all possibilities of emitting data over the time horizon specified. Of course, you can concat multiples instances of crazyObservable to get desired behavior.

0

In RxPY 3.0 you can use the following construction:

res = rx.generate(0, lambda x: True, lambda x: x + 1).pipe(
         ops.map(lambda x: rx.timer(random.random() * 0.4).pipe(ops.map(lambda y: x))),
         ops.merge(max_concurrent=1),
         ops.map(lambda x: {'count': x, 'value': random.randint(0, 5)}))

This produces an infinite stream of random integers between 0 and 5 at random times with interarrival time uniformly distributed on [0, 0.4].

In RxPY 3.0, operations like switchmap or concatmap are not implemented (as in @olsn's reply). The concat_all operation can be achieved by merge with max_concurrent=1.

Edit:

rx.generate(0, lambda x: True, lambda x: x + 1)

is blocking. Using an infinite vanilla python generator such as

import itertools
r = rx.from_iterable(_ for _ in itertools.count(start=0, step=1))

is also blocking. You can add some scheduler e.g.

from rx.scheduler.eventloop import AsyncIOScheduler
from rx.scheduler import ThreadPoolScheduler
import multiprocessing

scheduler = AsyncIOScheduler(asyncio.get_event_loop())
# scheduler = ThreadPoolScheduler(multiprocessing.cpu_count()) # alternatively

res = rx.generate(0, lambda x: True, lambda x: x + 1).pipe(
         ops.map(lambda x: rx.timer(random.random() * 0.4).pipe(ops.map(lambda y: x))),
         ops.merge(max_concurrent=1),
         ops.map(lambda x: {'count': x, 'value': random.randint(0, 5)}),
         ops.subscribe_on(scheduler)
)
shashlik
  • 1
  • 1