# Getting the outliers of both dates and values

This is working now, but it looks very inelegant. I'm sure there is a nicer way of doing this.

# Get the outliers of both the dates and values
maxDate   = []
minDate   = []
maxVal    = []
minVal    = []
for k, v of series
for d, i in v
series[k][i][1] = nearZero if d[1] is 0
series[k] = v = _.filter  v,    (d) -> not _.isNull d[1]
maxDate.push  _.max       v,    (d) -> d[0]
minDate.push  _.min       v,    (d) -> d[0]
maxVal .push  _.max       v,    (d) -> d[1]
minVal .push  _.min       v,    (d) -> d[1]
maxDate   = _.max _.map maxDate,  (d) -> d[0]
minDate   = _.min _.map minDate,  (d) -> d[0]
maxVal    = _.max _.map maxVal,   (d) -> d[1]
minVal    = _.min _.map minVal,   (d) -> d[1]

• It would be good for people to test if you could add example values for series and nearZero. – tokland Jul 4 '13 at 21:01

Some notes:

• I know many programmers like it, but this kind of perfectly aligned code looks pretty weird to me. Maybe my problem is that it's usually a sign that there is a repeated pattern that could be abstracted but has been prettified instead.

• maxDate = []. Using imperative programming in JS/CS is very common, indeed, but I'd definitely take a functional approach when doing maths or logic (not that I know of any coding that does not deal with maths or logic ;-)).

• (d) -> d[0]: Note that you can de-structure arrays: ([x, y]) -> x.

• _.map maxDate, (d) -> d[0]. In my opinion list-comprehensions are more declarative than maps that use lambdas: (d[0] for d in maxDate).

• Don't reuse variables: Those 4 accumulators hold two completely different structures in the course of the computation, that's not a good practice. Not even in imperative programming.

As I said, I'd write it in functional style, inmutable variables all the way through. It's a pity that CS has no real list-comprehensions and the results of nested loops must be flattened, but we'll have to live with it (_.flatten(xs, true) comes in handy as a one-level flattener). It would look something like this (you say you really need the modified series, so I'll use mash from my mixin):

nested_date_value_pairs =
for key, pairs of series
for [date, val] in pairs when val isnt null
[date, if val is 0 then nearZero else val]
modified_series = _.mash(_.zip(_.keys(series), nested_date_value_pairs))
dates_values = _.zip(_.flatten(nested_date_value_pairs, true)...)
[[minDate, maxDate], [minVal, maxVal]] = ([_.min(xs), _.max(xs)] for xs in dates_values)

• The only problem I have with this is that series does not get altered in your version. Its an unfortunately needed side-effect. – Fresheyeball Jul 8 '13 at 16:31
• @Fresheyeball: In-place updated aren't compulsory, you can always create a new object. Is a bit harder? yes, but the algorithm is saner. Updated. – tokland Jul 8 '13 at 19:24
• Thank you @tokland. Your coffee knowledge is formidably idomatic! – Fresheyeball Jul 8 '13 at 22:17

For the max/min part of the problem, a solution using list comprehensions might be:

foo1 = (fn, i) ->
# apply fn to i'th term of nested pairs
fn((fn(x[i] for x in v) for k, v of series))

[maxDate, minDate, maxVal, minVal] =
[foo1(_.max, 0), foo1(_.min, 0), foo1(_.max, 1), foo1(_.min, 1)]


or without underscore

foo2 = (fn, i) ->
fn((fn((x[i] for x in v)...) for k, v of series)...)
console.log (foo2(fn, i) for fn in [Math.max, Math.min] for i in [0,1])


Another with chaining:

wseries = _(series)
foo4 = (i)->
wseries.map((v)->_.map(v,i)).flatten().value()

[[maxDate, minDate], [maxVal, minVal]] =
(fn(foo4(i)) for fn in [_.max, _.min] for i in [0,1])


These were tested with:

series = {
1: [[0, .5],[4, -.1]],
2: [[1, .4]],
3: [[2, .2],[0, 0],[3, .7]]
}
nearZero = 0.1


producing

[ [ 4, 0 ], [ 0.7, -0.1 ] ]


for the initial filtering task, this appeals to my sense of aesthetics:

bar = (x)->
[x[0], (if x[1] is 0 then nearZero else x[1])]
for k, v of series
v = (bar(x) for x in v when x[1]!=null)
series[k] = v


though with large dimensions, and sparse changes I can imagine being more selective about changing v.