3
\$\begingroup\$

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]
\$\endgroup\$
1
  • \$\begingroup\$ It would be good for people to test if you could add example values for series and nearZero. \$\endgroup\$ – tokland Jul 4 '13 at 21:01
4
\$\begingroup\$

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)
\$\endgroup\$
3
  • \$\begingroup\$ The only problem I have with this is that series does not get altered in your version. Its an unfortunately needed side-effect. \$\endgroup\$ – Fresheyeball Jul 8 '13 at 16:31
  • \$\begingroup\$ @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. \$\endgroup\$ – tokland Jul 8 '13 at 19:24
  • \$\begingroup\$ Thank you @tokland. Your coffee knowledge is formidably idomatic! \$\endgroup\$ – Fresheyeball Jul 8 '13 at 22:17
2
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.