Functional operators in JS/TS

Jonas Chapuis, Ph.D. nxlogo

Good ol' for loop

Let's use an API to retrieve some stock market data into an array:

In [13]:
$$async$$ = true;
const quotesReq = tradier.quotes("MSFT", "NFLX", "GOOG", "AAPL", "ABB", "ACIU", "CAJ", "HMC", "NVDA", "INTC");
console.log("retrieving quotes...")
var quotes: Quote[];
quotesReq.subscribe(r => { quotes = r; $$done$$(quotes); });
retrieving quotes...
Out[13]:
[ { symbol: 'MSFT',
    description: 'Microsoft Corp',
    date: 1513695966000,
    price: 85.9,
    volume: 3655668 },
  { symbol: 'NFLX',
    description: 'Netflix Inc',
    date: 1513695966000,
    price: 188.45,
    volume: 826255 },
  { symbol: 'GOOG',
    description: 'Alphabet Inc. - Class C Capital Stock',
    date: 1513695947000,
    price: 1073.44,
    volume: 195603 },
  { symbol: 'AAPL',
    description: 'Apple Inc',
    date: 1513695966000,
    price: 174.96,
    volume: 6870114 },
  { symbol: 'ABB',
    description: 'ABB Ltd Common Stock',
    date: 1513695959000,
    price: 26.565,
    volume: 192226 },
  { symbol: 'ACIU',
    description: 'AC Immune SA',
    date: 1513695664000,
    price: 12.85,
    volume: 6385 },
  { symbol: 'CAJ',
    description: 'Canon, Inc. American Depositary Shares',
    date: 1513695961000,
    price: 38.615,
    volume: 16996 },
  { symbol: 'HMC',
    description: 'Honda Motor Company, Ltd. Common Stock',
    date: 1513695921000,
    price: 34.3397,
    volume: 82936 },
  { symbol: 'NVDA',
    description: 'NVIDIA Corp',
    date: 1513695966000,
    price: 197.09,
    volume: 2816186 },
  { symbol: 'INTC',
    description: 'Intel Corp',
    date: 1513695966000,
    price: 46.395,
    volume: 7455006 } ]

Now, let's say we want to find out which ones have significant volume: we can do it like this:

In [14]:
const significantVolume = 10;

var quotesWithVolume: Quotes[] = [];
for(var i = 0; i < quotes.length; i++) {
    var quote = quotes[i];
    if (quote.volume > significantVolume) {
        quotesWithVolume.push(quote);
    }
}
quotesWithVolume
Out[14]:
[ { symbol: 'MSFT',
    description: 'Microsoft Corp',
    date: 1513695966000,
    price: 85.9,
    volume: 3655668 },
  { symbol: 'NFLX',
    description: 'Netflix Inc',
    date: 1513695966000,
    price: 188.45,
    volume: 826255 },
  { symbol: 'GOOG',
    description: 'Alphabet Inc. - Class C Capital Stock',
    date: 1513695947000,
    price: 1073.44,
    volume: 195603 },
  { symbol: 'AAPL',
    description: 'Apple Inc',
    date: 1513695966000,
    price: 174.96,
    volume: 6870114 },
  { symbol: 'ABB',
    description: 'ABB Ltd Common Stock',
    date: 1513695959000,
    price: 26.565,
    volume: 192226 },
  { symbol: 'ACIU',
    description: 'AC Immune SA',
    date: 1513695664000,
    price: 12.85,
    volume: 6385 },
  { symbol: 'CAJ',
    description: 'Canon, Inc. American Depositary Shares',
    date: 1513695961000,
    price: 38.615,
    volume: 16996 },
  { symbol: 'HMC',
    description: 'Honda Motor Company, Ltd. Common Stock',
    date: 1513695921000,
    price: 34.3397,
    volume: 82936 },
  { symbol: 'NVDA',
    description: 'NVIDIA Corp',
    date: 1513695966000,
    price: 197.09,
    volume: 2816186 },
  { symbol: 'INTC',
    description: 'Intel Corp',
    date: 1513695966000,
    price: 46.395,
    volume: 7455006 } ]

That's fine, but let's say we are actually interested in having this information updated in real-time as the stock market changes.

Typically, this is the kind of data that we would get from a real-time streaming stock API (the axis represents time):

Marble_Stock

Let's do a mind trick and imagine that we can represent these real-time quotes updates also with a collection:

var realTimeQuotes = ... // collection of quotes over time

Wouldn't that be great? However, would we really be able to iterate in such a collection like we did? That feels weird, since iteration is a synchronous process.

What we need is another way to describe our filtering operation, a more descriptive way...

filter() operator

Enter the filter() operator. How about expressing things like that:

quotes.filter(hasSignificantTradeVolume)

Compare with the for loop from before. Isn't it more expressive?

Also, would it make sense on our "over time" collection?

var filteredRealTimeQuotes = realTimeQuotes.filter(hasSignificantTradeVolume)

Conceptually, this feels legit, isn't it?

Implementation

It turns out that filter() was added to JavaScript in ES5.

But for the sake of the exercice, let's re-implement it:

In [15]:
declare global {
    interface Array<T> {
        filter(predicate: (value: T) => boolean): Array<T>;
    }
}

Array.prototype.filter = function filter<T>(predicate: (value: T) => boolean): Array<T> {
    var result : T[] = [];
    for(var i = 0; i < this.length; i++) {
        if (predicate(this[i])) {
            result.push(this[i]);
        }
    }
    return result;
}
Out[15]:
[Function: filter]

We can now express our previous query the way we wanted:

In [16]:
const hasSignificantTradeVolume = (q: Quote) => q.volume > significantVolume;
quotes.filter(hasSignificantTradeVolume);
Out[16]:
[ { symbol: 'MSFT',
    description: 'Microsoft Corp',
    date: 1513695966000,
    price: 85.9,
    volume: 3655668 },
  { symbol: 'NFLX',
    description: 'Netflix Inc',
    date: 1513695966000,
    price: 188.45,
    volume: 826255 },
  { symbol: 'GOOG',
    description: 'Alphabet Inc. - Class C Capital Stock',
    date: 1513695947000,
    price: 1073.44,
    volume: 195603 },
  { symbol: 'AAPL',
    description: 'Apple Inc',
    date: 1513695966000,
    price: 174.96,
    volume: 6870114 },
  { symbol: 'ABB',
    description: 'ABB Ltd Common Stock',
    date: 1513695959000,
    price: 26.565,
    volume: 192226 },
  { symbol: 'ACIU',
    description: 'AC Immune SA',
    date: 1513695664000,
    price: 12.85,
    volume: 6385 },
  { symbol: 'CAJ',
    description: 'Canon, Inc. American Depositary Shares',
    date: 1513695961000,
    price: 38.615,
    volume: 16996 },
  { symbol: 'HMC',
    description: 'Honda Motor Company, Ltd. Common Stock',
    date: 1513695921000,
    price: 34.3397,
    volume: 82936 },
  { symbol: 'NVDA',
    description: 'NVIDIA Corp',
    date: 1513695966000,
    price: 197.09,
    volume: 2816186 },
  { symbol: 'INTC',
    description: 'Intel Corp',
    date: 1513695966000,
    price: 46.395,
    volume: 7455006 } ]

And maybe we can actually reuse this to fullfil our real-time vision...but before that, let's look at other operators.

map() operator

Another important operation in computing is the one of transforming something into something else.

For instance, let's say we want to produce a summary for each quote, in such a format:

AAPL - Apple Inc: 169.32$ (6622)

This is a formatting operation that transforms the Quote structure into a string.

Once again, we can implement this in the classical looping fashion:

In [17]:
var quotesSummary: string[] = [];
for(var i = 0; i < quotes.length; i++) {
    const quote = quotes[i];
    const summary = `${quote.symbol} - ${quote.description}: ${quote.price}$ (${quote.volume})`
    quotesSummary.push(summary);
}
quotesSummary
Out[17]:
[ 'MSFT - Microsoft Corp: 85.9$ (3655668)',
  'NFLX - Netflix Inc: 188.45$ (826255)',
  'GOOG - Alphabet Inc. - Class C Capital Stock: 1073.44$ (195603)',
  'AAPL - Apple Inc: 174.96$ (6870114)',
  'ABB - ABB Ltd Common Stock: 26.565$ (192226)',
  'ACIU - AC Immune SA: 12.85$ (6385)',
  'CAJ - Canon, Inc. American Depositary Shares: 38.615$ (16996)',
  'HMC - Honda Motor Company, Ltd. Common Stock: 34.3397$ (82936)',
  'NVDA - NVIDIA Corp: 197.09$ (2816186)',
  'INTC - Intel Corp: 46.395$ (7455006)' ]

Here again, we can abstract this looping logic and define a higher-order function which accepts the transformation function.

Enter the map() operator. Let's use the built-in ES5 one:

In [18]:
quotes.map(q => `${q.symbol} - ${q.description}: ${q.price}$ (${q.volume})`);
Out[18]:
[ 'MSFT - Microsoft Corp: 85.9$ (3655668)',
  'NFLX - Netflix Inc: 188.45$ (826255)',
  'GOOG - Alphabet Inc. - Class C Capital Stock: 1073.44$ (195603)',
  'AAPL - Apple Inc: 174.96$ (6870114)',
  'ABB - ABB Ltd Common Stock: 26.565$ (192226)',
  'ACIU - AC Immune SA: 12.85$ (6385)',
  'CAJ - Canon, Inc. American Depositary Shares: 38.615$ (16996)',
  'HMC - Honda Motor Company, Ltd. Common Stock: 34.3397$ (82936)',
  'NVDA - NVIDIA Corp: 197.09$ (2816186)',
  'INTC - Intel Corp: 46.395$ (7455006)' ]

reduce() operator

Using reduce(), we can express computations (aggregations in particular) over our collections.

To illustrate this, we are now going to look at historical values for a specific company. Let's retrieve the recent google ones:

In [19]:
$$async$$ = true;

var yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
var monthAgo = new Date();
monthAgo.setDate( monthAgo.getDate() - 30 );

const historyReq = tradier.history("GOOG", monthAgo, yesterday);
console.log("retrieving quotes for last month...")
var googLastMonth: DailyQuote[];
historyReq.subscribe(r => { googLastMonth = r; $$done$$(r); });
retrieving quotes for last month...
Out[19]:
[ { symbol: 'GOOG',
    price: 1018.38,
    date: '2017-11-20',
    volume: 953470 },
  { symbol: 'GOOG',
    price: 1034.49,
    date: '2017-11-21',
    volume: 1096999 },
  { symbol: 'GOOG',
    price: 1035.96,
    date: '2017-11-22',
    volume: 746878 },
  { symbol: 'GOOG',
    price: 1040.61,
    date: '2017-11-24',
    volume: 536996 },
  { symbol: 'GOOG',
    price: 1054.21,
    date: '2017-11-27',
    volume: 1307881 },
  { symbol: 'GOOG',
    price: 1047.41,
    date: '2017-11-28',
    volume: 1424394 },
  { symbol: 'GOOG',
    price: 1021.66,
    date: '2017-11-29',
    volume: 2459426 },
  { symbol: 'GOOG',
    price: 1021.41,
    date: '2017-11-30',
    volume: 1724031 },
  { symbol: 'GOOG',
    price: 1010.17,
    date: '2017-12-01',
    volume: 1909566 },
  { symbol: 'GOOG',
    price: 998.68,
    date: '2017-12-04',
    volume: 1906439 },
  { symbol: 'GOOG',
    price: 1005.15,
    date: '2017-12-05',
    volume: 2067318 },
  { symbol: 'GOOG',
    price: 1018.38,
    date: '2017-12-06',
    volume: 1271964 },
  { symbol: 'GOOG',
    price: 1030.93,
    date: '2017-12-07',
    volume: 1458242 },
  { symbol: 'GOOG',
    price: 1037.05,
    date: '2017-12-08',
    volume: 1290774 },
  { symbol: 'GOOG',
    price: 1041.1,
    date: '2017-12-11',
    volume: 1192838 },
  { symbol: 'GOOG',
    price: 1040.48,
    date: '2017-12-12',
    volume: 1279659 },
  { symbol: 'GOOG',
    price: 1040.61,
    date: '2017-12-13',
    volume: 1282677 },
  { symbol: 'GOOG',
    price: 1049.15,
    date: '2017-12-14',
    volume: 1558835 },
  { symbol: 'GOOG',
    price: 1064.19,
    date: '2017-12-15',
    volume: 3275837 },
  { symbol: 'GOOG',
    price: 1077.14,
    date: '2017-12-18',
    volume: 1554542 } ]

Now, let's say we are interested in the total traded volume for google over the last month. Here's how we can formulate this with reduce():

In [20]:
googLastMonth.reduce((acc: number, q: DailyQuote) => acc + q.volume, 0);
Out[20]:
30298766

zip() operator

We are also interested in the day-to-day evolution of the price for a certain ticker.

In order to do that, we need to traverse the days with a "sliding window" of size two, like this:

sliding window of 2

This is easy to express with a cool new operator zip(), which acts like a zipper, where each side is an array, and each tooth is an item: zipper

Since zip() is not defined in ES5, we need to implement it ourselves:

In [21]:
declare global {
    interface Array<T1> {
        zip<T2, R>(
            left: Array<T1>, 
            right: Array<T2>, 
            combiner: (l: T1, r: T2) => R): Array<R>;
    }
}

Array.prototype.zip = function zip<T1, T2, R>(right: Array<T2>, combiner: (l: T1, r: T2) => R): Array<R> {
    var results: R[] = [];
    for(var i = 0; i < Math.min(this.length, right.length); i++) {
        results.push(combiner(this[i], right[i]));
    }
    return results;
}
Out[21]:
[Function: zip]

Quizz: Any idea how we could now take advantage of zip() to express our query (day-to-day price evolution)?

We'll pull out a little trick: we'll zip the sequence upon itself, but shifted by one element! Like this:

In [22]:
googLastMonth.zip(googLastMonth.slice(1), (l, r) => r.price - l.price);
Out[22]:
[ 16.110000000000014,
  1.4700000000000273,
  4.649999999999864,
  13.600000000000136,
  -6.7999999999999545,
  -25.750000000000114,
  -0.25,
  -11.240000000000009,
  -11.490000000000009,
  6.470000000000027,
  13.230000000000018,
  12.550000000000068,
  6.119999999999891,
  4.0499999999999545,
  -0.6199999999998909,
  0.12999999999988177,
  8.540000000000191,
  15.039999999999964,
  12.950000000000045 ]

flatten() operator

Sometimes, in addition to flat arrays, we need to work with arrays of arrays.

Let's suppose for instance that we are interested in gathering quotes from the last month for various companies into a continuous list.

Let's define a flatten() function, which iterates over each sub-array in the array and collects the results in a new, flat array.

In [23]:
declare global {
    interface Array<T> {
        flatten(): Array<T>;
    }  
}

Array.prototype.flatten = function flatten<T>(): Array<T> {
    var results = [];
    for(var i = 0; i < this.length; i++) {
        for (var j = 0; j < this[i].length; j++){
            results.push(this[i][j]);
        }
    }
    return results;
};
Out[23]:
[Function: flatten]

Let's load up daily quotes for all our symbols over the last month into a map (skipping over the details over how this is done for now):

In [24]:
$$async$$ = true;

const symbols = Observable.of("MSFT", "NFLX", "GOOG", "AAPL", "ABB", "ACIU", "CAJ", "HMC", "NVDA", "INTC");
const historyPerSymbol = new Map<string, DailyQuote[]>();
symbols.delay(500).flatMap(s => tradier.history(s, monthAgo, yesterday).do(r => historyPerSymbol.set(s, r)))
  .subscribe(n => console.log(n), e => console.log(e), () => $$done$$("all loaded"));
[ { symbol: 'CAJ', price: 38.35, date: '2017-11-20', volume: 81126 },
  { symbol: 'CAJ',
    price: 38.48,
    date: '2017-11-21',
    volume: 175089 },
  { symbol: 'CAJ',
    price: 38.51,
    date: '2017-11-22',
    volume: 172901 },
  { symbol: 'CAJ', price: 38.75, date: '2017-11-24', volume: 47437 },
  { symbol: 'CAJ',
    price: 38.73,
    date: '2017-11-27',
    volume: 126759 },
  { symbol: 'CAJ',
    price: 38.86,
    date: '2017-11-28',
    volume: 109848 },
  { symbol: 'CAJ',
    price: 38.31,
    date: '2017-11-29',
    volume: 118351 },
  { symbol: 'CAJ',
    price: 38.37,
    date: '2017-11-30',
    volume: 110021 },
  { symbol: 'CAJ',
    price: 37.96,
    date: '2017-12-01',
    volume: 150261 },
  { symbol: 'CAJ',
    price: 37.78,
    date: '2017-12-04',
    volume: 184705 },
  { symbol: 'CAJ',
    price: 37.88,
    date: '2017-12-05',
    volume: 136155 },
  { symbol: 'CAJ', price: 38.05, date: '2017-12-06', volume: 91453 },
  { symbol: 'CAJ', price: 38.2, date: '2017-12-07', volume: 95387 },
  { symbol: 'CAJ',
    price: 38.35,
    date: '2017-12-08',
    volume: 116933 },
  { symbol: 'CAJ', price: 38.48, date: '2017-12-11', volume: 98800 },
  { symbol: 'CAJ', price: 38.65, date: '2017-12-12', volume: 70686 },
  { symbol: 'CAJ', price: 38.67, date: '2017-12-13', volume: 55646 },
  { symbol: 'CAJ',
    price: 38.44,
    date: '2017-12-14',
    volume: 114745 },
  { symbol: 'CAJ',
    price: 38.45,
    date: '2017-12-15',
    volume: 108478 },
  { symbol: 'CAJ', price: 38.67, date: '2017-12-18', volume: 99864 } ]
[ { symbol: 'AAPL',
    price: 169.98,
    date: '2017-11-20',
    volume: 16262447 },
  { symbol: 'AAPL',
    price: 173.14,
    date: '2017-11-21',
    volume: 25131295 },
  { symbol: 'AAPL',
    price: 174.96,
    date: '2017-11-22',
    volume: 25588925 },
  { symbol: 'AAPL',
    price: 174.97,
    date: '2017-11-24',
    volume: 14026673 },
  { symbol: 'AAPL',
    price: 174.09,
    date: '2017-11-27',
    volume: 20716802 },
  { symbol: 'AAPL',
    price: 173.07,
    date: '2017-11-28',
    volume: 26428802 },
  { symbol: 'AAPL',
    price: 169.48,
    date: '2017-11-29',
    volume: 41666364 },
  { symbol: 'AAPL',
    price: 171.85,
    date: '2017-11-30',
    volume: 41527218 },
  { symbol: 'AAPL',
    price: 171.05,
    date: '2017-12-01',
    volume: 39759288 },
  { symbol: 'AAPL',
    price: 169.8,
    date: '2017-12-04',
    volume: 32542385 },
  { symbol: 'AAPL',
    price: 169.64,
    date: '2017-12-05',
    volume: 27350154 },
  { symbol: 'AAPL',
    price: 169.01,
    date: '2017-12-06',
    volume: 28560000 },
  { symbol: 'AAPL',
    price: 169.32,
    date: '2017-12-07',
    volume: 25673308 },
  { symbol: 'AAPL',
    price: 169.37,
    date: '2017-12-08',
    volume: 23355231 },
  { symbol: 'AAPL',
    price: 172.67,
    date: '2017-12-11',
    volume: 35273759 },
  { symbol: 'AAPL',
    price: 171.7,
    date: '2017-12-12',
    volume: 19409230 },
  { symbol: 'AAPL',
    price: 172.27,
    date: '2017-12-13',
    volume: 23818447 },
  { symbol: 'AAPL',
    price: 172.22,
    date: '2017-12-14',
    volume: 20475861 },
  { symbol: 'AAPL',
    price: 173.97,
    date: '2017-12-15',
    volume: 40167041 },
  { symbol: 'AAPL',
    price: 176.42,
    date: '2017-12-18',
    volume: 29414449 } ]
[ { symbol: 'NFLX',
    price: 194.1,
    date: '2017-11-20',
    volume: 3827474 },
  { symbol: 'NFLX',
    price: 196.23,
    date: '2017-11-21',
    volume: 4787311 },
  { symbol: 'NFLX',
    price: 196.32,
    date: '2017-11-22',
    volume: 5895402 },
  { symbol: 'NFLX',
    price: 195.75,
    date: '2017-11-24',
    volume: 2160535 },
  { symbol: 'NFLX',
    price: 195.05,
    date: '2017-11-27',
    volume: 3210145 },
  { symbol: 'NFLX',
    price: 199.18,
    date: '2017-11-28',
    volume: 6981094 },
  { symbol: 'NFLX',
    price: 188.15,
    date: '2017-11-29',
    volume: 14202713 },
  { symbol: 'NFLX',
    price: 187.58,
    date: '2017-11-30',
    volume: 6630051 },
  { symbol: 'NFLX',
    price: 186.82,
    date: '2017-12-01',
    volume: 6219531 },
  { symbol: 'NFLX',
    price: 184.04,
    date: '2017-12-04',
    volume: 9069765 },
  { symbol: 'NFLX',
    price: 184.21,
    date: '2017-12-05',
    volume: 5783697 },
  { symbol: 'NFLX',
    price: 185.3,
    date: '2017-12-06',
    volume: 5490088 },
  { symbol: 'NFLX',
    price: 185.2,
    date: '2017-12-07',
    volume: 4659542 },
  { symbol: 'NFLX',
    price: 188.54,
    date: '2017-12-08',
    volume: 4987270 },
  { symbol: 'NFLX',
    price: 186.22,
    date: '2017-12-11',
    volume: 5298583 },
  { symbol: 'NFLX',
    price: 185.73,
    date: '2017-12-12',
    volume: 4265907 },
  { symbol: 'NFLX',
    price: 187.86,
    date: '2017-12-13',
    volume: 4710041 },
  { symbol: 'NFLX',
    price: 189.56,
    date: '2017-12-14',
    volume: 7792699 },
  { symbol: 'NFLX',
    price: 190.12,
    date: '2017-12-15',
    volume: 7285513 },
  { symbol: 'NFLX',
    price: 190.42,
    date: '2017-12-18',
    volume: 5010911 } ]
[ { symbol: 'INTC',
    price: 44.62,
    date: '2017-11-20',
    volume: 22420003 },
  { symbol: 'INTC',
    price: 44.94,
    date: '2017-11-21',
    volume: 21871027 },
  { symbol: 'INTC',
    price: 44.65,
    date: '2017-11-22',
    volume: 19538331 },
  { symbol: 'INTC',
    price: 44.75,
    date: '2017-11-24',
    volume: 6465815 },
  { symbol: 'INTC',
    price: 44.49,
    date: '2017-11-27',
    volume: 18202115 },
  { symbol: 'INTC',
    price: 44.73,
    date: '2017-11-28',
    volume: 20194062 },
  { symbol: 'INTC',
    price: 43.95,
    date: '2017-11-29',
    volume: 27036870 },
  { symbol: 'INTC',
    price: 44.84,
    date: '2017-11-30',
    volume: 34145303 },
  { symbol: 'INTC',
    price: 44.68,
    date: '2017-12-01',
    volume: 26656272 },
  { symbol: 'INTC',
    price: 44.49,
    date: '2017-12-04',
    volume: 28000791 },
  { symbol: 'INTC',
    price: 43.44,
    date: '2017-12-05',
    volume: 30626850 },
  { symbol: 'INTC',
    price: 43.45,
    date: '2017-12-06',
    volume: 27710812 },
  { symbol: 'INTC',
    price: 43.08,
    date: '2017-12-07',
    volume: 32708441 },
  { symbol: 'INTC',
    price: 43.35,
    date: '2017-12-08',
    volume: 23154749 },
  { symbol: 'INTC',
    price: 43.66,
    date: '2017-12-11',
    volume: 20425900 },
  { symbol: 'INTC',
    price: 43.33,
    date: '2017-12-12',
    volume: 16557398 },
  { symbol: 'INTC',
    price: 43.34,
    date: '2017-12-13',
    volume: 21399460 },
  { symbol: 'INTC',
    price: 43.26,
    date: '2017-12-14',
    volume: 19644617 },
  { symbol: 'INTC',
    price: 44.56,
    date: '2017-12-15',
    volume: 47476452 },
  { symbol: 'INTC',
    price: 46.26,
    date: '2017-12-18',
    volume: 50368897 } ]
[ { symbol: 'NVDA',
    price: 214.08,
    date: '2017-11-20',
    volume: 9902511 },
  { symbol: 'NVDA',
    price: 216.05,
    date: '2017-11-21',
    volume: 9979358 },
  { symbol: 'NVDA',
    price: 214.93,
    date: '2017-11-22',
    volume: 8915998 },
  { symbol: 'NVDA',
    price: 216.96,
    date: '2017-11-24',
    volume: 4518800 },
  { symbol: 'NVDA',
    price: 214.14,
    date: '2017-11-27',
    volume: 10572364 },
  { symbol: 'NVDA',
    price: 210.71,
    date: '2017-11-28',
    volume: 12818804 },
  { symbol: 'NVDA',
    price: 196.42,
    date: '2017-11-29',
    volume: 34919741 },
  { symbol: 'NVDA',
    price: 200.71,
    date: '2017-11-30',
    volume: 20594199 },
  { symbol: 'NVDA',
    price: 197.68,
    date: '2017-12-01',
    volume: 20288748 },
  { symbol: 'NVDA',
    price: 186.66,
    date: '2017-12-04',
    volume: 31021123 },
  { symbol: 'NVDA',
    price: 187.74,
    date: '2017-12-05',
    volume: 24537194 },
  { symbol: 'NVDA',
    price: 189.26,
    date: '2017-12-06',
    volume: 11675760 },
  { symbol: 'NVDA',
    price: 191.99,
    date: '2017-12-07',
    volume: 13564988 },
  { symbol: 'NVDA',
    price: 191.49,
    date: '2017-12-08',
    volume: 11671856 },
  { symbol: 'NVDA',
    price: 194.66,
    date: '2017-12-11',
    volume: 9318741 },
  { symbol: 'NVDA',
    price: 190.84,
    date: '2017-12-12',
    volume: 11375445 },
  { symbol: 'NVDA',
    price: 186.18,
    date: '2017-12-13',
    volume: 13856992 },
  { symbol: 'NVDA',
    price: 186.47,
    date: '2017-12-14',
    volume: 10949596 },
  { symbol: 'NVDA',
    price: 191.56,
    date: '2017-12-15',
    volume: 16737162 },
  { symbol: 'NVDA',
    price: 197.9,
    date: '2017-12-18',
    volume: 11935797 } ]
[ { symbol: 'HMC',
    price: 32.92,
    date: '2017-11-20',
    volume: 635504 },
  { symbol: 'HMC',
    price: 33.12,
    date: '2017-11-21',
    volume: 389000 },
  { symbol: 'HMC',
    price: 33.16,
    date: '2017-11-22',
    volume: 379427 },
  { symbol: 'HMC',
    price: 33.13,
    date: '2017-11-24',
    volume: 203001 },
  { symbol: 'HMC',
    price: 32.98,
    date: '2017-11-27',
    volume: 829400 },
  { symbol: 'HMC', price: 33.4, date: '2017-11-28', volume: 752132 },
  { symbol: 'HMC', price: 33.2, date: '2017-11-29', volume: 538498 },
  { symbol: 'HMC',
    price: 33.34,
    date: '2017-11-30',
    volume: 728922 },
  { symbol: 'HMC',
    price: 33.38,
    date: '2017-12-01',
    volume: 678030 },
  { symbol: 'HMC', price: 33.4, date: '2017-12-04', volume: 551990 },
  { symbol: 'HMC', price: 33.2, date: '2017-12-05', volume: 392621 },
  { symbol: 'HMC',
    price: 33.13,
    date: '2017-12-06',
    volume: 636920 },
  { symbol: 'HMC',
    price: 33.35,
    date: '2017-12-07',
    volume: 684633 },
  { symbol: 'HMC',
    price: 33.29,
    date: '2017-12-08',
    volume: 448433 },
  { symbol: 'HMC',
    price: 33.33,
    date: '2017-12-11',
    volume: 238483 },
  { symbol: 'HMC',
    price: 33.52,
    date: '2017-12-12',
    volume: 258687 },
  { symbol: 'HMC',
    price: 33.73,
    date: '2017-12-13',
    volume: 490300 },
  { symbol: 'HMC',
    price: 33.66,
    date: '2017-12-14',
    volume: 512948 },
  { symbol: 'HMC',
    price: 33.68,
    date: '2017-12-15',
    volume: 446919 },
  { symbol: 'HMC',
    price: 34.03,
    date: '2017-12-18',
    volume: 403642 } ]
[ { symbol: 'ABB',
    price: 25.38,
    date: '2017-11-20',
    volume: 1140181 },
  { symbol: 'ABB',
    price: 25.55,
    date: '2017-11-21',
    volume: 1408020 },
  { symbol: 'ABB',
    price: 25.4,
    date: '2017-11-22',
    volume: 2258103 },
  { symbol: 'ABB',
    price: 25.63,
    date: '2017-11-24',
    volume: 2616537 },
  { symbol: 'ABB',
    price: 25.63,
    date: '2017-11-27',
    volume: 1981328 },
  { symbol: 'ABB',
    price: 25.83,
    date: '2017-11-28',
    volume: 2138659 },
  { symbol: 'ABB',
    price: 25.5,
    date: '2017-11-29',
    volume: 1588255 },
  { symbol: 'ABB',
    price: 25.74,
    date: '2017-11-30',
    volume: 2234750 },
  { symbol: 'ABB',
    price: 25.66,
    date: '2017-12-01',
    volume: 1989879 },
  { symbol: 'ABB',
    price: 25.73,
    date: '2017-12-04',
    volume: 2311878 },
  { symbol: 'ABB',
    price: 25.42,
    date: '2017-12-05',
    volume: 1223003 },
  { symbol: 'ABB',
    price: 25.65,
    date: '2017-12-06',
    volume: 1152817 },
  { symbol: 'ABB',
    price: 25.81,
    date: '2017-12-07',
    volume: 3091628 },
  { symbol: 'ABB',
    price: 26.2,
    date: '2017-12-08',
    volume: 4293390 },
  { symbol: 'ABB', price: 26, date: '2017-12-11', volume: 1209575 },
  { symbol: 'ABB',
    price: 26.03,
    date: '2017-12-12',
    volume: 991862 },
  { symbol: 'ABB',
    price: 26.1,
    date: '2017-12-13',
    volume: 2215385 },
  { symbol: 'ABB',
    price: 26.1,
    date: '2017-12-14',
    volume: 3057492 },
  { symbol: 'ABB', price: 26, date: '2017-12-15', volume: 2438490 },
  { symbol: 'ABB',
    price: 26.43,
    date: '2017-12-18',
    volume: 1569974 } ]
[ { symbol: 'MSFT',
    price: 82.53,
    date: '2017-11-20',
    volume: 16314978 },
  { symbol: 'MSFT',
    price: 83.72,
    date: '2017-11-21',
    volume: 21237454 },
  { symbol: 'MSFT',
    price: 83.11,
    date: '2017-11-22',
    volume: 20553089 },
  { symbol: 'MSFT',
    price: 83.26,
    date: '2017-11-24',
    volume: 7425603 },
  { symbol: 'MSFT',
    price: 83.87,
    date: '2017-11-27',
    volume: 18265242 },
  { symbol: 'MSFT',
    price: 84.88,
    date: '2017-11-28',
    volume: 21925959 },
  { symbol: 'MSFT',
    price: 83.34,
    date: '2017-11-29',
    volume: 27381109 },
  { symbol: 'MSFT',
    price: 84.17,
    date: '2017-11-30',
    volume: 33054647 },
  { symbol: 'MSFT',
    price: 84.26,
    date: '2017-12-01',
    volume: 29532132 },
  { symbol: 'MSFT',
    price: 81.08,
    date: '2017-12-04',
    volume: 39094880 },
  { symbol: 'MSFT',
    price: 81.59,
    date: '2017-12-05',
    volume: 26152261 },
  { symbol: 'MSFT',
    price: 82.78,
    date: '2017-12-06',
    volume: 26162054 },
  { symbol: 'MSFT',
    price: 82.49,
    date: '2017-12-07',
    volume: 23184547 },
  { symbol: 'MSFT',
    price: 84.16,
    date: '2017-12-08',
    volume: 24489106 },
  { symbol: 'MSFT',
    price: 85.23,
    date: '2017-12-11',
    volume: 22857854 },
  { symbol: 'MSFT',
    price: 85.58,
    date: '2017-12-12',
    volume: 23924105 },
  { symbol: 'MSFT',
    price: 85.35,
    date: '2017-12-13',
    volume: 22062679 },
  { symbol: 'MSFT',
    price: 84.69,
    date: '2017-12-14',
    volume: 19305591 },
  { symbol: 'MSFT',
    price: 86.85,
    date: '2017-12-15',
    volume: 53936642 },
  { symbol: 'MSFT',
    price: 86.38,
    date: '2017-12-18',
    volume: 22283697 } ]
[ { symbol: 'ACIU',
    price: 10.37,
    date: '2017-11-20',
    volume: 41701 },
  { symbol: 'ACIU',
    price: 11.73,
    date: '2017-11-21',
    volume: 143953 },
  { symbol: 'ACIU',
    price: 11.37,
    date: '2017-11-22',
    volume: 51075 },
  { symbol: 'ACIU',
    price: 11.48,
    date: '2017-11-24',
    volume: 19778 },
  { symbol: 'ACIU',
    price: 11.31,
    date: '2017-11-27',
    volume: 19434 },
  { symbol: 'ACIU',
    price: 11.7,
    date: '2017-11-28',
    volume: 130485 },
  { symbol: 'ACIU',
    price: 11.72,
    date: '2017-11-29',
    volume: 71999 },
  { symbol: 'ACIU',
    price: 11.69,
    date: '2017-11-30',
    volume: 146218 },
  { symbol: 'ACIU',
    price: 11.85,
    date: '2017-12-01',
    volume: 41716 },
  { symbol: 'ACIU',
    price: 11.75,
    date: '2017-12-04',
    volume: 320960 },
  { symbol: 'ACIU',
    price: 11.92,
    date: '2017-12-05',
    volume: 71448 },
  { symbol: 'ACIU',
    price: 12.03,
    date: '2017-12-06',
    volume: 108999 },
  { symbol: 'ACIU', price: 12, date: '2017-12-07', volume: 169354 },
  { symbol: 'ACIU',
    price: 12.27,
    date: '2017-12-08',
    volume: 46754 },
  { symbol: 'ACIU', price: 12, date: '2017-12-11', volume: 73385 },
  { symbol: 'ACIU',
    price: 12.36,
    date: '2017-12-12',
    volume: 167603 },
  { symbol: 'ACIU',
    price: 12.17,
    date: '2017-12-13',
    volume: 195800 },
  { symbol: 'ACIU', price: 12.3, date: '2017-12-14', volume: 98021 },
  { symbol: 'ACIU',
    price: 12.63,
    date: '2017-12-15',
    volume: 39321 },
  { symbol: 'ACIU',
    price: 12.85,
    date: '2017-12-18',
    volume: 160696 } ]
[ { symbol: 'GOOG',
    price: 1018.38,
    date: '2017-11-20',
    volume: 953470 },
  { symbol: 'GOOG',
    price: 1034.49,
    date: '2017-11-21',
    volume: 1096999 },
  { symbol: 'GOOG',
    price: 1035.96,
    date: '2017-11-22',
    volume: 746878 },
  { symbol: 'GOOG',
    price: 1040.61,
    date: '2017-11-24',
    volume: 536996 },
  { symbol: 'GOOG',
    price: 1054.21,
    date: '2017-11-27',
    volume: 1307881 },
  { symbol: 'GOOG',
    price: 1047.41,
    date: '2017-11-28',
    volume: 1424394 },
  { symbol: 'GOOG',
    price: 1021.66,
    date: '2017-11-29',
    volume: 2459426 },
  { symbol: 'GOOG',
    price: 1021.41,
    date: '2017-11-30',
    volume: 1724031 },
  { symbol: 'GOOG',
    price: 1010.17,
    date: '2017-12-01',
    volume: 1909566 },
  { symbol: 'GOOG',
    price: 998.68,
    date: '2017-12-04',
    volume: 1906439 },
  { symbol: 'GOOG',
    price: 1005.15,
    date: '2017-12-05',
    volume: 2067318 },
  { symbol: 'GOOG',
    price: 1018.38,
    date: '2017-12-06',
    volume: 1271964 },
  { symbol: 'GOOG',
    price: 1030.93,
    date: '2017-12-07',
    volume: 1458242 },
  { symbol: 'GOOG',
    price: 1037.05,
    date: '2017-12-08',
    volume: 1290774 },
  { symbol: 'GOOG',
    price: 1041.1,
    date: '2017-12-11',
    volume: 1192838 },
  { symbol: 'GOOG',
    price: 1040.48,
    date: '2017-12-12',
    volume: 1279659 },
  { symbol: 'GOOG',
    price: 1040.61,
    date: '2017-12-13',
    volume: 1282677 },
  { symbol: 'GOOG',
    price: 1049.15,
    date: '2017-12-14',
    volume: 1558835 },
  { symbol: 'GOOG',
    price: 1064.19,
    date: '2017-12-15',
    volume: 3275837 },
  { symbol: 'GOOG',
    price: 1077.14,
    date: '2017-12-18',
    volume: 1554542 } ]
Out[24]:
'all loaded'

Now, using flatten() let's integrate all our quotes into a continuous list:

In [25]:
const selection = ["MSFT", "NFLX", "GOOG", "AAPL"]
selection.map(s => historyPerSymbol.get(s)).flatten();
Out[25]:
[ { symbol: 'MSFT',
    price: 82.53,
    date: '2017-11-20',
    volume: 16314978 },
  { symbol: 'MSFT',
    price: 83.72,
    date: '2017-11-21',
    volume: 21237454 },
  { symbol: 'MSFT',
    price: 83.11,
    date: '2017-11-22',
    volume: 20553089 },
  { symbol: 'MSFT',
    price: 83.26,
    date: '2017-11-24',
    volume: 7425603 },
  { symbol: 'MSFT',
    price: 83.87,
    date: '2017-11-27',
    volume: 18265242 },
  { symbol: 'MSFT',
    price: 84.88,
    date: '2017-11-28',
    volume: 21925959 },
  { symbol: 'MSFT',
    price: 83.34,
    date: '2017-11-29',
    volume: 27381109 },
  { symbol: 'MSFT',
    price: 84.17,
    date: '2017-11-30',
    volume: 33054647 },
  { symbol: 'MSFT',
    price: 84.26,
    date: '2017-12-01',
    volume: 29532132 },
  { symbol: 'MSFT',
    price: 81.08,
    date: '2017-12-04',
    volume: 39094880 },
  { symbol: 'MSFT',
    price: 81.59,
    date: '2017-12-05',
    volume: 26152261 },
  { symbol: 'MSFT',
    price: 82.78,
    date: '2017-12-06',
    volume: 26162054 },
  { symbol: 'MSFT',
    price: 82.49,
    date: '2017-12-07',
    volume: 23184547 },
  { symbol: 'MSFT',
    price: 84.16,
    date: '2017-12-08',
    volume: 24489106 },
  { symbol: 'MSFT',
    price: 85.23,
    date: '2017-12-11',
    volume: 22857854 },
  { symbol: 'MSFT',
    price: 85.58,
    date: '2017-12-12',
    volume: 23924105 },
  { symbol: 'MSFT',
    price: 85.35,
    date: '2017-12-13',
    volume: 22062679 },
  { symbol: 'MSFT',
    price: 84.69,
    date: '2017-12-14',
    volume: 19305591 },
  { symbol: 'MSFT',
    price: 86.85,
    date: '2017-12-15',
    volume: 53936642 },
  { symbol: 'MSFT',
    price: 86.38,
    date: '2017-12-18',
    volume: 22283697 },
  { symbol: 'NFLX',
    price: 194.1,
    date: '2017-11-20',
    volume: 3827474 },
  { symbol: 'NFLX',
    price: 196.23,
    date: '2017-11-21',
    volume: 4787311 },
  { symbol: 'NFLX',
    price: 196.32,
    date: '2017-11-22',
    volume: 5895402 },
  { symbol: 'NFLX',
    price: 195.75,
    date: '2017-11-24',
    volume: 2160535 },
  { symbol: 'NFLX',
    price: 195.05,
    date: '2017-11-27',
    volume: 3210145 },
  { symbol: 'NFLX',
    price: 199.18,
    date: '2017-11-28',
    volume: 6981094 },
  { symbol: 'NFLX',
    price: 188.15,
    date: '2017-11-29',
    volume: 14202713 },
  { symbol: 'NFLX',
    price: 187.58,
    date: '2017-11-30',
    volume: 6630051 },
  { symbol: 'NFLX',
    price: 186.82,
    date: '2017-12-01',
    volume: 6219531 },
  { symbol: 'NFLX',
    price: 184.04,
    date: '2017-12-04',
    volume: 9069765 },
  { symbol: 'NFLX',
    price: 184.21,
    date: '2017-12-05',
    volume: 5783697 },
  { symbol: 'NFLX',
    price: 185.3,
    date: '2017-12-06',
    volume: 5490088 },
  { symbol: 'NFLX',
    price: 185.2,
    date: '2017-12-07',
    volume: 4659542 },
  { symbol: 'NFLX',
    price: 188.54,
    date: '2017-12-08',
    volume: 4987270 },
  { symbol: 'NFLX',
    price: 186.22,
    date: '2017-12-11',
    volume: 5298583 },
  { symbol: 'NFLX',
    price: 185.73,
    date: '2017-12-12',
    volume: 4265907 },
  { symbol: 'NFLX',
    price: 187.86,
    date: '2017-12-13',
    volume: 4710041 },
  { symbol: 'NFLX',
    price: 189.56,
    date: '2017-12-14',
    volume: 7792699 },
  { symbol: 'NFLX',
    price: 190.12,
    date: '2017-12-15',
    volume: 7285513 },
  { symbol: 'NFLX',
    price: 190.42,
    date: '2017-12-18',
    volume: 5010911 },
  { symbol: 'GOOG',
    price: 1018.38,
    date: '2017-11-20',
    volume: 953470 },
  { symbol: 'GOOG',
    price: 1034.49,
    date: '2017-11-21',
    volume: 1096999 },
  { symbol: 'GOOG',
    price: 1035.96,
    date: '2017-11-22',
    volume: 746878 },
  { symbol: 'GOOG',
    price: 1040.61,
    date: '2017-11-24',
    volume: 536996 },
  { symbol: 'GOOG',
    price: 1054.21,
    date: '2017-11-27',
    volume: 1307881 },
  { symbol: 'GOOG',
    price: 1047.41,
    date: '2017-11-28',
    volume: 1424394 },
  { symbol: 'GOOG',
    price: 1021.66,
    date: '2017-11-29',
    volume: 2459426 },
  { symbol: 'GOOG',
    price: 1021.41,
    date: '2017-11-30',
    volume: 1724031 },
  { symbol: 'GOOG',
    price: 1010.17,
    date: '2017-12-01',
    volume: 1909566 },
  { symbol: 'GOOG',
    price: 998.68,
    date: '2017-12-04',
    volume: 1906439 },
  { symbol: 'GOOG',
    price: 1005.15,
    date: '2017-12-05',
    volume: 2067318 },
  { symbol: 'GOOG',
    price: 1018.38,
    date: '2017-12-06',
    volume: 1271964 },
  { symbol: 'GOOG',
    price: 1030.93,
    date: '2017-12-07',
    volume: 1458242 },
  { symbol: 'GOOG',
    price: 1037.05,
    date: '2017-12-08',
    volume: 1290774 },
  { symbol: 'GOOG',
    price: 1041.1,
    date: '2017-12-11',
    volume: 1192838 },
  { symbol: 'GOOG',
    price: 1040.48,
    date: '2017-12-12',
    volume: 1279659 },
  { symbol: 'GOOG',
    price: 1040.61,
    date: '2017-12-13',
    volume: 1282677 },
  { symbol: 'GOOG',
    price: 1049.15,
    date: '2017-12-14',
    volume: 1558835 },
  { symbol: 'GOOG',
    price: 1064.19,
    date: '2017-12-15',
    volume: 3275837 },
  { symbol: 'GOOG',
    price: 1077.14,
    date: '2017-12-18',
    volume: 1554542 },
  { symbol: 'AAPL',
    price: 169.98,
    date: '2017-11-20',
    volume: 16262447 },
  { symbol: 'AAPL',
    price: 173.14,
    date: '2017-11-21',
    volume: 25131295 },
  { symbol: 'AAPL',
    price: 174.96,
    date: '2017-11-22',
    volume: 25588925 },
  { symbol: 'AAPL',
    price: 174.97,
    date: '2017-11-24',
    volume: 14026673 },
  { symbol: 'AAPL',
    price: 174.09,
    date: '2017-11-27',
    volume: 20716802 },
  { symbol: 'AAPL',
    price: 173.07,
    date: '2017-11-28',
    volume: 26428802 },
  { symbol: 'AAPL',
    price: 169.48,
    date: '2017-11-29',
    volume: 41666364 },
  { symbol: 'AAPL',
    price: 171.85,
    date: '2017-11-30',
    volume: 41527218 },
  { symbol: 'AAPL',
    price: 171.05,
    date: '2017-12-01',
    volume: 39759288 },
  { symbol: 'AAPL',
    price: 169.8,
    date: '2017-12-04',
    volume: 32542385 },
  { symbol: 'AAPL',
    price: 169.64,
    date: '2017-12-05',
    volume: 27350154 },
  { symbol: 'AAPL',
    price: 169.01,
    date: '2017-12-06',
    volume: 28560000 },
  { symbol: 'AAPL',
    price: 169.32,
    date: '2017-12-07',
    volume: 25673308 },
  { symbol: 'AAPL',
    price: 169.37,
    date: '2017-12-08',
    volume: 23355231 },
  { symbol: 'AAPL',
    price: 172.67,
    date: '2017-12-11',
    volume: 35273759 },
  { symbol: 'AAPL',
    price: 171.7,
    date: '2017-12-12',
    volume: 19409230 },
  { symbol: 'AAPL',
    price: 172.27,
    date: '2017-12-13',
    volume: 23818447 },
  { symbol: 'AAPL',
    price: 172.22,
    date: '2017-12-14',
    volume: 20475861 },
  { symbol: 'AAPL',
    price: 173.97,
    date: '2017-12-15',
    volume: 40167041 },
  { symbol: 'AAPL',
    price: 176.42,
    date: '2017-12-18',
    volume: 29414449 } ]
In [26]:
historyPerSymbol.get('MSFT').map(s => s.price)
Out[26]:
[ 82.53,
  83.72,
  83.11,
  83.26,
  83.87,
  84.88,
  83.34,
  84.17,
  84.26,
  81.08,
  81.59,
  82.78,
  82.49,
  84.16,
  85.23,
  85.58,
  85.35,
  84.69,
  86.85,
  86.38 ]

let's sort them by date:

In [27]:
selection
    .map(s => historyPerSymbol.get(s))
    .flatten()
    .sort((a: DailyQuote, b: DailyQuote) => a.date.localeCompare(b.date));
Out[27]:
[ { symbol: 'GOOG',
    price: 1018.38,
    date: '2017-11-20',
    volume: 953470 },
  { symbol: 'MSFT',
    price: 82.53,
    date: '2017-11-20',
    volume: 16314978 },
  { symbol: 'AAPL',
    price: 169.98,
    date: '2017-11-20',
    volume: 16262447 },
  { symbol: 'NFLX',
    price: 194.1,
    date: '2017-11-20',
    volume: 3827474 },
  { symbol: 'GOOG',
    price: 1034.49,
    date: '2017-11-21',
    volume: 1096999 },
  { symbol: 'AAPL',
    price: 173.14,
    date: '2017-11-21',
    volume: 25131295 },
  { symbol: 'NFLX',
    price: 196.23,
    date: '2017-11-21',
    volume: 4787311 },
  { symbol: 'MSFT',
    price: 83.72,
    date: '2017-11-21',
    volume: 21237454 },
  { symbol: 'MSFT',
    price: 83.11,
    date: '2017-11-22',
    volume: 20553089 },
  { symbol: 'AAPL',
    price: 174.96,
    date: '2017-11-22',
    volume: 25588925 },
  { symbol: 'NFLX',
    price: 196.32,
    date: '2017-11-22',
    volume: 5895402 },
  { symbol: 'GOOG',
    price: 1035.96,
    date: '2017-11-22',
    volume: 746878 },
  { symbol: 'NFLX',
    price: 195.75,
    date: '2017-11-24',
    volume: 2160535 },
  { symbol: 'AAPL',
    price: 174.97,
    date: '2017-11-24',
    volume: 14026673 },
  { symbol: 'GOOG',
    price: 1040.61,
    date: '2017-11-24',
    volume: 536996 },
  { symbol: 'MSFT',
    price: 83.26,
    date: '2017-11-24',
    volume: 7425603 },
  { symbol: 'MSFT',
    price: 83.87,
    date: '2017-11-27',
    volume: 18265242 },
  { symbol: 'AAPL',
    price: 174.09,
    date: '2017-11-27',
    volume: 20716802 },
  { symbol: 'GOOG',
    price: 1054.21,
    date: '2017-11-27',
    volume: 1307881 },
  { symbol: 'NFLX',
    price: 195.05,
    date: '2017-11-27',
    volume: 3210145 },
  { symbol: 'NFLX',
    price: 199.18,
    date: '2017-11-28',
    volume: 6981094 },
  { symbol: 'GOOG',
    price: 1047.41,
    date: '2017-11-28',
    volume: 1424394 },
  { symbol: 'MSFT',
    price: 84.88,
    date: '2017-11-28',
    volume: 21925959 },
  { symbol: 'AAPL',
    price: 173.07,
    date: '2017-11-28',
    volume: 26428802 },
  { symbol: 'MSFT',
    price: 83.34,
    date: '2017-11-29',
    volume: 27381109 },
  { symbol: 'NFLX',
    price: 188.15,
    date: '2017-11-29',
    volume: 14202713 },
  { symbol: 'GOOG',
    price: 1021.66,
    date: '2017-11-29',
    volume: 2459426 },
  { symbol: 'AAPL',
    price: 169.48,
    date: '2017-11-29',
    volume: 41666364 },
  { symbol: 'GOOG',
    price: 1021.41,
    date: '2017-11-30',
    volume: 1724031 },
  { symbol: 'AAPL',
    price: 171.85,
    date: '2017-11-30',
    volume: 41527218 },
  { symbol: 'NFLX',
    price: 187.58,
    date: '2017-11-30',
    volume: 6630051 },
  { symbol: 'MSFT',
    price: 84.17,
    date: '2017-11-30',
    volume: 33054647 },
  { symbol: 'AAPL',
    price: 171.05,
    date: '2017-12-01',
    volume: 39759288 },
  { symbol: 'GOOG',
    price: 1010.17,
    date: '2017-12-01',
    volume: 1909566 },
  { symbol: 'NFLX',
    price: 186.82,
    date: '2017-12-01',
    volume: 6219531 },
  { symbol: 'MSFT',
    price: 84.26,
    date: '2017-12-01',
    volume: 29532132 },
  { symbol: 'AAPL',
    price: 169.8,
    date: '2017-12-04',
    volume: 32542385 },
  { symbol: 'NFLX',
    price: 184.04,
    date: '2017-12-04',
    volume: 9069765 },
  { symbol: 'GOOG',
    price: 998.68,
    date: '2017-12-04',
    volume: 1906439 },
  { symbol: 'MSFT',
    price: 81.08,
    date: '2017-12-04',
    volume: 39094880 },
  { symbol: 'NFLX',
    price: 184.21,
    date: '2017-12-05',
    volume: 5783697 },
  { symbol: 'AAPL',
    price: 169.64,
    date: '2017-12-05',
    volume: 27350154 },
  { symbol: 'GOOG',
    price: 1005.15,
    date: '2017-12-05',
    volume: 2067318 },
  { symbol: 'MSFT',
    price: 81.59,
    date: '2017-12-05',
    volume: 26152261 },
  { symbol: 'GOOG',
    price: 1018.38,
    date: '2017-12-06',
    volume: 1271964 },
  { symbol: 'MSFT',
    price: 82.78,
    date: '2017-12-06',
    volume: 26162054 },
  { symbol: 'NFLX',
    price: 185.3,
    date: '2017-12-06',
    volume: 5490088 },
  { symbol: 'AAPL',
    price: 169.01,
    date: '2017-12-06',
    volume: 28560000 },
  { symbol: 'MSFT',
    price: 82.49,
    date: '2017-12-07',
    volume: 23184547 },
  { symbol: 'GOOG',
    price: 1030.93,
    date: '2017-12-07',
    volume: 1458242 },
  { symbol: 'NFLX',
    price: 185.2,
    date: '2017-12-07',
    volume: 4659542 },
  { symbol: 'AAPL',
    price: 169.32,
    date: '2017-12-07',
    volume: 25673308 },
  { symbol: 'GOOG',
    price: 1037.05,
    date: '2017-12-08',
    volume: 1290774 },
  { symbol: 'AAPL',
    price: 169.37,
    date: '2017-12-08',
    volume: 23355231 },
  { symbol: 'NFLX',
    price: 188.54,
    date: '2017-12-08',
    volume: 4987270 },
  { symbol: 'MSFT',
    price: 84.16,
    date: '2017-12-08',
    volume: 24489106 },
  { symbol: 'NFLX',
    price: 186.22,
    date: '2017-12-11',
    volume: 5298583 },
  { symbol: 'MSFT',
    price: 85.23,
    date: '2017-12-11',
    volume: 22857854 },
  { symbol: 'GOOG',
    price: 1041.1,
    date: '2017-12-11',
    volume: 1192838 },
  { symbol: 'AAPL',
    price: 172.67,
    date: '2017-12-11',
    volume: 35273759 },
  { symbol: 'NFLX',
    price: 185.73,
    date: '2017-12-12',
    volume: 4265907 },
  { symbol: 'AAPL',
    price: 171.7,
    date: '2017-12-12',
    volume: 19409230 },
  { symbol: 'GOOG',
    price: 1040.48,
    date: '2017-12-12',
    volume: 1279659 },
  { symbol: 'MSFT',
    price: 85.58,
    date: '2017-12-12',
    volume: 23924105 },
  { symbol: 'NFLX',
    price: 187.86,
    date: '2017-12-13',
    volume: 4710041 },
  { symbol: 'GOOG',
    price: 1040.61,
    date: '2017-12-13',
    volume: 1282677 },
  { symbol: 'AAPL',
    price: 172.27,
    date: '2017-12-13',
    volume: 23818447 },
  { symbol: 'MSFT',
    price: 85.35,
    date: '2017-12-13',
    volume: 22062679 },
  { symbol: 'GOOG',
    price: 1049.15,
    date: '2017-12-14',
    volume: 1558835 },
  { symbol: 'NFLX',
    price: 189.56,
    date: '2017-12-14',
    volume: 7792699 },
  { symbol: 'AAPL',
    price: 172.22,
    date: '2017-12-14',
    volume: 20475861 },
  { symbol: 'MSFT',
    price: 84.69,
    date: '2017-12-14',
    volume: 19305591 },
  { symbol: 'AAPL',
    price: 173.97,
    date: '2017-12-15',
    volume: 40167041 },
  { symbol: 'NFLX',
    price: 190.12,
    date: '2017-12-15',
    volume: 7285513 },
  { symbol: 'GOOG',
    price: 1064.19,
    date: '2017-12-15',
    volume: 3275837 },
  { symbol: 'MSFT',
    price: 86.85,
    date: '2017-12-15',
    volume: 53936642 },
  { symbol: 'MSFT',
    price: 86.38,
    date: '2017-12-18',
    volume: 22283697 },
  { symbol: 'GOOG',
    price: 1077.14,
    date: '2017-12-18',
    volume: 1554542 },
  { symbol: 'NFLX',
    price: 190.42,
    date: '2017-12-18',
    volume: 5010911 },
  { symbol: 'AAPL',
    price: 176.42,
    date: '2017-12-18',
    volume: 29414449 } ]

This is quite unreadable, so let's format this information a little bit, like we did before.

We can now use map() on the flattened array, like this:

In [28]:
selection
    .map(s => historyPerSymbol.get(s))
    .flatten()
    .sort((a: DailyQuote, b: DailyQuote) => a.date.localeCompare(b.date))
    .map(q => `${q.symbol}:${q.price}`)
Out[28]:
[ 'GOOG:1018.38',
  'MSFT:82.53',
  'AAPL:169.98',
  'NFLX:194.1',
  'GOOG:1034.49',
  'AAPL:173.14',
  'NFLX:196.23',
  'MSFT:83.72',
  'MSFT:83.11',
  'AAPL:174.96',
  'NFLX:196.32',
  'GOOG:1035.96',
  'NFLX:195.75',
  'AAPL:174.97',
  'GOOG:1040.61',
  'MSFT:83.26',
  'MSFT:83.87',
  'AAPL:174.09',
  'GOOG:1054.21',
  'NFLX:195.05',
  'NFLX:199.18',
  'GOOG:1047.41',
  'MSFT:84.88',
  'AAPL:173.07',
  'MSFT:83.34',
  'NFLX:188.15',
  'GOOG:1021.66',
  'AAPL:169.48',
  'GOOG:1021.41',
  'AAPL:171.85',
  'NFLX:187.58',
  'MSFT:84.17',
  'AAPL:171.05',
  'GOOG:1010.17',
  'NFLX:186.82',
  'MSFT:84.26',
  'AAPL:169.8',
  'NFLX:184.04',
  'GOOG:998.68',
  'MSFT:81.08',
  'NFLX:184.21',
  'AAPL:169.64',
  'GOOG:1005.15',
  'MSFT:81.59',
  'GOOG:1018.38',
  'MSFT:82.78',
  'NFLX:185.3',
  'AAPL:169.01',
  'MSFT:82.49',
  'GOOG:1030.93',
  'NFLX:185.2',
  'AAPL:169.32',
  'GOOG:1037.05',
  'AAPL:169.37',
  'NFLX:188.54',
  'MSFT:84.16',
  'NFLX:186.22',
  'MSFT:85.23',
  'GOOG:1041.1',
  'AAPL:172.67',
  'NFLX:185.73',
  'AAPL:171.7',
  'GOOG:1040.48',
  'MSFT:85.58',
  'NFLX:187.86',
  'GOOG:1040.61',
  'AAPL:172.27',
  'MSFT:85.35',
  'GOOG:1049.15',
  'NFLX:189.56',
  'AAPL:172.22',
  'MSFT:84.69',
  'AAPL:173.97',
  'NFLX:190.12',
  'GOOG:1064.19',
  'MSFT:86.85',
  'MSFT:86.38',
  'GOOG:1077.14',
  'NFLX:190.42',
  'AAPL:176.42' ]

flatMap() operator

It turns out mapping and flattening are two operations that are very often used together. We can actually define a flatMap() function which combines both operations into one call:

In [29]:
declare global {
    interface Array<T> {
        flatMap<R>(projectionFunction: (t: T) => R[]): Array<R>
    }  
}

Array.prototype.flatMap = function flatMap<T, R>(projectionFunction: (t: T) => R[]): Array<R> {
    return new Array<R>(...this.map(projectionFunction)).flatten();
};
Out[29]:
[Function: flatMap]

Let's rewrite our previous query using flatMap():

In [30]:
selection
    .flatMap(s => historyPerSymbol.get(s))
    .sort((a: DailyQuote, b: DailyQuote) => a.date.localeCompare(b.date))
    .map(q => `${q.symbol}:${q.price}`)
Out[30]:
[ 'GOOG:1018.38',
  'MSFT:82.53',
  'AAPL:169.98',
  'NFLX:194.1',
  'GOOG:1034.49',
  'AAPL:173.14',
  'NFLX:196.23',
  'MSFT:83.72',
  'MSFT:83.11',
  'AAPL:174.96',
  'NFLX:196.32',
  'GOOG:1035.96',
  'NFLX:195.75',
  'AAPL:174.97',
  'GOOG:1040.61',
  'MSFT:83.26',
  'MSFT:83.87',
  'AAPL:174.09',
  'GOOG:1054.21',
  'NFLX:195.05',
  'NFLX:199.18',
  'GOOG:1047.41',
  'MSFT:84.88',
  'AAPL:173.07',
  'MSFT:83.34',
  'NFLX:188.15',
  'GOOG:1021.66',
  'AAPL:169.48',
  'GOOG:1021.41',
  'AAPL:171.85',
  'NFLX:187.58',
  'MSFT:84.17',
  'AAPL:171.05',
  'GOOG:1010.17',
  'NFLX:186.82',
  'MSFT:84.26',
  'AAPL:169.8',
  'NFLX:184.04',
  'GOOG:998.68',
  'MSFT:81.08',
  'NFLX:184.21',
  'AAPL:169.64',
  'GOOG:1005.15',
  'MSFT:81.59',
  'GOOG:1018.38',
  'MSFT:82.78',
  'NFLX:185.3',
  'AAPL:169.01',
  'MSFT:82.49',
  'GOOG:1030.93',
  'NFLX:185.2',
  'AAPL:169.32',
  'GOOG:1037.05',
  'AAPL:169.37',
  'NFLX:188.54',
  'MSFT:84.16',
  'NFLX:186.22',
  'MSFT:85.23',
  'GOOG:1041.1',
  'AAPL:172.67',
  'NFLX:185.73',
  'AAPL:171.7',
  'GOOG:1040.48',
  'MSFT:85.58',
  'NFLX:187.86',
  'GOOG:1040.61',
  'AAPL:172.27',
  'MSFT:85.35',
  'GOOG:1049.15',
  'NFLX:189.56',
  'AAPL:172.22',
  'MSFT:84.69',
  'AAPL:173.97',
  'NFLX:190.12',
  'GOOG:1064.19',
  'MSFT:86.85',
  'MSFT:86.38',
  'GOOG:1077.14',
  'NFLX:190.42',
  'AAPL:176.42' ]

Streaming operators

We've now seen how to express queries and operations on collections in a declarative fashion, that is telling the machine what we want rather than how.

declarative-heaven

Initially, we stated that adopting this programming style would be the first step to allow us to work with asynchronous collections, or in more common terminology, streams.

We are now going to see if this promise holds true and how we can apply what we learnt to deal with the complexities of asynchrony.

Let's represent the continous stream of incoming stock quotes updates with a variable quotes$.

  • Note that we can ignore its type for now, and just consider it's some kind of asynchronous array.
  • Note that the "$" char is often used in lieu of "stream".

Let's define a fake stream which emits values following the marble diagram that we showed earlier:

Marble_Stock

We can actually specify this fake stream using marble testing techniques (more on that later):

In [31]:
// marble diagram definition
var streamMarbles = "-M---N-----A------G---V--H-C--B--|";
streamMarbles
Out[31]:
'-M---N-----A------G---V--H-C--B--|'
In [32]:
// marble values definition
var sampleQuotes = [{
    symbol: 'MSFT',
    description: 'Microsoft Corp',
    date: 1512767700000,
    price: 84.16,
    volume: 7400
},
{
    symbol: 'NFLX',
    description: 'Netflix Inc',
    date: 1512767700000,
    price: 188.54,
    volume: 0
},
{
    symbol: 'GOOG',
    description: 'Alphabet Inc. - Class C Capital Stock',
    date: 1512766800000,
    price: 1037.05,
    volume: 363
},
{
    symbol: 'AAPL',
    description: 'Apple Inc',
    date: 1512767700000,
    price: 169.37,
    volume: 27583
},
{
    symbol: 'ABB',
    description: 'ABB Ltd Common Stock',
    date: 1512766920000,
    price: 26.2,
    volume: 0
},
{
    symbol: 'ACIU',
    description: 'AC Immune SA',
    date: 1512766789000,
    price: 12.27,
    volume: 0
},
{
    symbol: 'CAJ',
    description: 'Canon, Inc. American Depositary Shares',
    date: 1512766921000,
    price: 38.35,
    volume: 0
},
{
    symbol: 'HMC',
    description: 'Honda Motor Company, Ltd. Common Stock',
    date: 1512766921000,
    price: 33.29,
    volume: 0
},
{
    symbol: 'NVDA',
    description: 'NVIDIA Corp',
    date: 1512767700000,
    price: 191.49,
    volume: 14757
},
{
    symbol: 'INTC',
    description: 'Intel Corp',
    date: 1512766800000,
    price: 43.35,
    volume: 1805
}]
var qMap = new Map<string, Quote>();
sampleQuotes.forEach(q => qMap.set(q.symbol, q));
const marbleValues = {
    M: qMap.get('MSFT'),
    N: qMap.get('NFLX'),
    A: qMap.get('AAPL'),
    G: qMap.get('GOOG'),
    I: qMap.get('INTC'),
    V: qMap.get('NVDA'),
    H: qMap.get('HMC'),
    C: qMap.get('CAJ'),
    B: qMap.get('ABB')
}
marbleValues
Out[32]:
{ M: 
   { symbol: 'MSFT',
     description: 'Microsoft Corp',
     date: 1512767700000,
     price: 84.16,
     volume: 7400 },
  N: 
   { symbol: 'NFLX',
     description: 'Netflix Inc',
     date: 1512767700000,
     price: 188.54,
     volume: 0 },
  A: 
   { symbol: 'AAPL',
     description: 'Apple Inc',
     date: 1512767700000,
     price: 169.37,
     volume: 27583 },
  G: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1037.05,
     volume: 363 },
  I: 
   { symbol: 'INTC',
     description: 'Intel Corp',
     date: 1512766800000,
     price: 43.35,
     volume: 1805 },
  V: 
   { symbol: 'NVDA',
     description: 'NVIDIA Corp',
     date: 1512767700000,
     price: 191.49,
     volume: 14757 },
  H: 
   { symbol: 'HMC',
     description: 'Honda Motor Company, Ltd. Common Stock',
     date: 1512766921000,
     price: 33.29,
     volume: 0 },
  C: 
   { symbol: 'CAJ',
     description: 'Canon, Inc. American Depositary Shares',
     date: 1512766921000,
     price: 38.35,
     volume: 0 },
  B: 
   { symbol: 'ABB',
     description: 'ABB Ltd Common Stock',
     date: 1512766920000,
     price: 26.2,
     volume: 0 } }
In [33]:
$$async$$ = true;

var quotes$ = createFakeStreamFromMarbles(streamMarbles, marbleValues);
simulateMarbles(quotes$)
{ symbol: 'MSFT',
  description: 'Microsoft Corp',
  date: 1512767700000,
  price: 84.16,
  volume: 7400 }
{ symbol: 'NFLX',
  description: 'Netflix Inc',
  date: 1512767700000,
  price: 188.54,
  volume: 0 }
{ symbol: 'AAPL',
  description: 'Apple Inc',
  date: 1512767700000,
  price: 169.37,
  volume: 27583 }
{ symbol: 'GOOG',
  description: 'Alphabet Inc. - Class C Capital Stock',
  date: 1512766800000,
  price: 1037.05,
  volume: 363 }
{ symbol: 'NVDA',
  description: 'NVIDIA Corp',
  date: 1512767700000,
  price: 191.49,
  volume: 14757 }
{ symbol: 'HMC',
  description: 'Honda Motor Company, Ltd. Common Stock',
  date: 1512766921000,
  price: 33.29,
  volume: 0 }
{ symbol: 'CAJ',
  description: 'Canon, Inc. American Depositary Shares',
  date: 1512766921000,
  price: 38.35,
  volume: 0 }
{ symbol: 'ABB',
  description: 'ABB Ltd Common Stock',
  date: 1512766920000,
  price: 26.2,
  volume: 0 }
Out[33]:
'stream end'

filter() stream operator

Now this is when the magic happens: let's literally copy/paste the filtering operation we were doing on arrays and apply them to our stream:

magicwand

In [34]:
$$async$$ = true;

// const hasSignificantTradeVolume = (q: Quote) => q.volume > significantVolume;
var filteredQuotes$ = quotes$.filter(hasSignificantTradeVolume);

simulateMarbles(filteredQuotes$);
{ symbol: 'MSFT',
  description: 'Microsoft Corp',
  date: 1512767700000,
  price: 84.16,
  volume: 7400 }
{ symbol: 'AAPL',
  description: 'Apple Inc',
  date: 1512767700000,
  price: 169.37,
  volume: 27583 }
{ symbol: 'GOOG',
  description: 'Alphabet Inc. - Class C Capital Stock',
  date: 1512766800000,
  price: 1037.05,
  volume: 363 }
{ symbol: 'NVDA',
  description: 'NVIDIA Corp',
  date: 1512767700000,
  price: 191.49,
  volume: 14757 }
Out[34]:
'stream end'

map() stream operator

Like before, let's reuse what we did for arrays:

In [35]:
$$async$$ = true;

var prettyPrintedQuotes$ = quotes$.map(q => `${q.symbol} - ${q.description}: ${q.price}$ (${q.volume})`);

simulateMarbles(prettyPrintedQuotes$);
MSFT - Microsoft Corp: 84.16$ (7400)
NFLX - Netflix Inc: 188.54$ (0)
AAPL - Apple Inc: 169.37$ (27583)
GOOG - Alphabet Inc. - Class C Capital Stock: 1037.05$ (363)
NVDA - NVIDIA Corp: 191.49$ (14757)
HMC - Honda Motor Company, Ltd. Common Stock: 33.29$ (0)
CAJ - Canon, Inc. American Depositary Shares: 38.35$ (0)
ABB - ABB Ltd Common Stock: 26.2$ (0)
Out[35]:
'stream end'

reduce() and scan() stream operators

Things are now getting interesting: we can also aggregate values over time!

In our earlier example, we were loading up an array with historical values and summing the traded volume for google using reduce().

With streams both reduce() and scan(), we can operate on values in real-time as they come:

  • reduce(): emit the final value at the end of the stream
  • scan(): emit an update for each incoming value (effectively streaming the outcome of our aggregation)

To illustrate this, we are going to look at the combined trade volumes of software giants, first making use of filter() in combination with reduce():

In [36]:
$$async$$ = true;

var softwareGiants = ['MSFT', 'AAPL', 'GOOG'];

var tradeVolume$ = quotes$.filter(q => softwareGiants.includes(q.symbol)).reduce((acc, q) => acc + q.volume, 0);
simulateMarbles(tradeVolume$, fast);
35346
Out[36]:
'stream end'

reduce() gives us a final value at the end of the stream, but what if we want to have continuous values or the stream never finishes? We'll use scan() for that:

In [37]:
$$async$$ = true;

var realTimeTradeVolume$ = quotes$.filter(q => softwareGiants.includes(q.symbol)).scan((acc, q) => acc + q.volume, 0);
simulateMarbles(realTimeTradeVolume$, fast);
7400
34983
35346
Out[37]:
'stream end'

Note that we can also play with those two operators visually online to get a feel for what they do:

zip() stream operator

Remember the zipper from before? We can also use it on streams, and perform our trick of zipping with a former version of ourselves...

Let's augment our fake quotes stream with more values, with varying prices after each operation (like in a real stock market):

In [38]:
// marble diagram definition
var pricesMarbles = "-A-BC-D---E-F--G--I--J-K-L---M--N--O--P|";

// marble values definition
var googPrices =
    [1025.75, 1026, 1020.91, 1032.5, 1019.09, 1018.38, 1034.49, 1035.96, 1040.61, 1054.21, 1047.41, 1021.66, 1021.41, 1010.17, 998.68, 1005.15, 1018.38, 1030.93, 1037.05, 1041.1, 1040.48]

var msftPrices =
    [83.93, 84.05, 82.98, 83.2, 82.4, 82.53, 83.72, 83.11, 83.26, 83.87, 84.88, 83.34, 84.17, 84.26, 81.08, 81.59, 82.78, 82.49, 84.16, 85.23, 85.58]

function googWithPrice(price: number) {
    return {
        symbol: 'GOOG',
        description: 'Alphabet Inc. - Class C Capital Stock',
        date: 1512766800000,
        price: price,
        volume: 363
    };
}

function msftWithPrice(price: number) {
    return {
        symbol: 'MSFT',
        description: 'Microsoft Corp',
        date: 1512767700000,
        price: price,
        volume: 7400
    }
}

var googPrices = googPrices.map(googWithPrice);
var msftPrices = msftPrices.map(msftWithPrice);
const pricesValues = {
    A: googPrices[0],
    B: googPrices[1],
    C: msftPrices[0],
    D: googPrices[2],
    E: msftPrices[1],
    F: googPrices[3],
    G: msftPrices[2],
    I: googPrices[4],
    J: googPrices[5],
    K: msftPrices[3],
    L: googPrices[7],
    M: msftPrices[4],
    N: googPrices[8],
    O: googPrices[9],
    P: msftPrices[10]
}
pricesValues
Out[38]:
{ A: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1025.75,
     volume: 363 },
  B: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1026,
     volume: 363 },
  C: 
   { symbol: 'MSFT',
     description: 'Microsoft Corp',
     date: 1512767700000,
     price: 83.93,
     volume: 7400 },
  D: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1020.91,
     volume: 363 },
  E: 
   { symbol: 'MSFT',
     description: 'Microsoft Corp',
     date: 1512767700000,
     price: 84.05,
     volume: 7400 },
  F: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1032.5,
     volume: 363 },
  G: 
   { symbol: 'MSFT',
     description: 'Microsoft Corp',
     date: 1512767700000,
     price: 82.98,
     volume: 7400 },
  I: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1019.09,
     volume: 363 },
  J: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1018.38,
     volume: 363 },
  K: 
   { symbol: 'MSFT',
     description: 'Microsoft Corp',
     date: 1512767700000,
     price: 83.2,
     volume: 7400 },
  L: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1035.96,
     volume: 363 },
  M: 
   { symbol: 'MSFT',
     description: 'Microsoft Corp',
     date: 1512767700000,
     price: 82.4,
     volume: 7400 },
  N: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1040.61,
     volume: 363 },
  O: 
   { symbol: 'GOOG',
     description: 'Alphabet Inc. - Class C Capital Stock',
     date: 1512766800000,
     price: 1054.21,
     volume: 363 },
  P: 
   { symbol: 'MSFT',
     description: 'Microsoft Corp',
     date: 1512767700000,
     price: 84.88,
     volume: 7400 } }

Let's look at the real-time variation of prices for Google:

In [ ]:
var prices$ = createFakeStreamFromMarbles(pricesMarbles, pricesValues);

var googPrices$ = prices$.filter(q => q.symbol == "GOOG");
var googVariations$ = googPrices$.zip(googPrices$.skip(1), 
                                      (prev, next) => next.price - prev.price);

simulateMarbles(googVariations$);
0.25

and for Microsoft:

In [ ]:
$$async$$ = true;

var msftPrices$ = prices$.filter(q => q.symbol == "MSFT");
var msftVariations$ = msftPrices$.zip(msftPrices$.skip(1), (prev, next) => next.price - prev.price);

simulateMarbles(msftVariations$);

The interactive marble diagram for zip() is here: http://reactivex.io/documentation/operators/zip.html

mergeAll() operator (flatten() for streams)

Remember our flatten() operator, that allows us to squish arrays of arrays into one continuous array?

The equivalent operation on streams is mergeAll(), which although named differently is exactly the same.

To illustrate this operation, we are going to merge our two previous streams googPrices$ and msftPrices$ together back again:

In [ ]:
$$async$$ = true;

var msftUnionGoogPrices$ = Observable.from([googPrices$, msftPrices$]).mergeAll().map(q => `${q.symbol}: ${q.price}`);

simulateMarbles(msftUnionGoogPrices$);

Note that to conserve original emission order, concat() is to be used instead of merge().

flatMap() streaming operator

Remember our friend flatMap() from before? flatMap() is an essential operator in functional programming.

It is the defining operation of a Monad and a fundamental operator (>>=) in the Haskell programming language for instance.

flatMap() is what allows us to describe a sequence of operations within the context (effect) we are working with: streams in our case.

To make this clearer, let's look at a concrete example. Let's say that we want to lookup some information in wikipedia for each company, in order to decorate values in the stream.

For each element in the incoming quotes stream, we need to make another request to retrieve the corresponding data, then funnel back the combined quote and information into the final stream:

flatmap

In [ ]:
$$async$$ = true;

var quotesWithDetails$ = quotes$
    .flatMap(q => {
        var companyName = q.description.split(' ').slice(0, 2).join(' ');
        console.log(companyName);
        return wikipedia.pageContent(companyName).map(c => { return { quote: q, info: c}; });
    }).map(item => 
    `${item.quote.symbol} - ${item.quote.description}: ${item.quote.price}$\n\n${item.info.slice(0, 200)+'...'}\n`);

simulateMarbles(quotesWithDetails$, fast);

Further

There are many more operators! Actually, more than 400 operators exist (listed here).

Some of the most important ones (in addition to those we've seen):