Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elegantly do a sum of object attributes in CoffeeScript

Tags:

coffeescript

I have an array of items like so:

@items = [
  {price: 12, quantity:1}, 
  {price: 4, quantity:1}, 
  {price: 8, quantity:1}
]

And I am looking for something like this:

sumPrice: ->
  @items.sum (item) -> item.price * item.quantity

Or anything as close as possible to this, that makes it super-easy for everyone reading the code to understand whats happening.

So far I came up with:

sumPrice: ->
   (items.map (a) -> a.price * a.quantity).reduce (a, b) -> a + b
  • contains too much functional magic
  • loses descriptiveness

And:

sumPrice: ->
   sum = 0
   for item in items
     sum += item.price * item.quantity
   sum
  • which can be understood by novice JS/Coffee programmers
  • feels a bit stupid

I love CoffeeScript so I hope there is a nicer solution to this & similar scenarios that I miss.

like image 705
hakunin Avatar asked Dec 19 '12 10:12

hakunin


4 Answers

Functional style is not so bad. CoffeeScript allows you to prettify your code like this:

items
  .map (item) ->
    item.price * item.quantity
  .reduce (x,y) ->
    x+y

This code is easier for understanding than your one-liner.

If you don't like map you can use for instead. Like this:

(for item in items
  item.price * item.quantity)
  .reduce (x,y)->x+y

Or like this:

prods = for item in items
  item.price * item.quantity
prods.reduce (x,y)->x+y

Or you can add your own sum() method for arrays:

Array::sum = -> @reduce (x,y)->x+y
(item.price * item.quantity for item in items).sum()
like image 183
Leonid Beschastny Avatar answered Sep 29 '22 02:09

Leonid Beschastny


If you want to express the solution as @items.sum (item) -> item.price * item.quantity you can add a sum method to Array:

Array::sum = (fn = (x) -> x) ->
  @reduce ((a, b) -> a + fn b), 0

sum = @items.sum (item) -> item.price * item.quantity

Notice that i'm passing 0 as the initial value of reduce so the fn callback is called for every array value.


If you don't like extending the builtin objects, i guess you could express the sum as a single reduce elegantly if you extract the logic of calculating the total price for a single array item in its own function:

itemPrice = (item) -> item.price * item.quantity

sum = items.reduce ((total, item) -> total + itemPrice item), 0
like image 44
epidemian Avatar answered Sep 29 '22 03:09

epidemian


You can use destructuring to simplify the code slightly:

sumPrice: ->
    sum = 0
    sum += price * quantity for {price, quantity} in @items
    sum

I don't think there's any way to get rid of the explicit initialization of sum. While Coffeescript's for loop syntax tends to help simplify code that would otherwise use map(), it doesn't really have anything analogous that simplifies reduce()-type operations, which is what sumPrice is doing here.

As mentioned in the comments, one advantage this solution has over a call to reduce() or sum() is that it avoids the overhead of creating and repeatedly calling a function.

like image 39
int3 Avatar answered Sep 29 '22 03:09

int3


sum = 0
value = (item) ->
  item.price * item.quantity
sum += value(item) for item in @items
like image 36
Mudassir Ali Avatar answered Sep 29 '22 03:09

Mudassir Ali