user479870
user479870

Reputation:

Can I do a reduce on a list comprehension into two lists, based on two values?

I've got the following code.

sum_review = reduce(add,[book['rw'] for book in books])
sum_rating = reduce(add,[book['rg'] for book in books])
items = len(books)
avg_review = sum_review/items
avg_rating = sum_rating/items

What I'd like is this.

sum_review,sum_rating = reduce(add,([book['rw'],[book['rg']) for book in books])
items = len(books)
avg_review = sum_review/items
avg_rating = sum_rating/items

Obviously this doesn't work. How can I solve this redundancy, without a regular loop?

Upvotes: 3

Views: 3247

Answers (5)

tokland
tokland

Reputation: 67870

There are two typical approaches to simplify code:

  1. Top-down: get the values first and then transpose them with zip(*iterable). It's also cool because it only iterates the collection once:

    values = ((book["rw"], book["rg"]) for book in books)
    avg_review, avg_rating = [sum(xs) / len(books) for xs in zip(*values)]
    
  2. Bottom-up: create a function to abstract the operation:

    get_avg = lambda xs, attr: sum(x[attr] for x in xs) / len(xs)
    avg_review = get_avg(books, "rw")
    avg_rating = get_avg(books, "rg")
    

Upvotes: 3

Steven Rumbalski
Steven Rumbalski

Reputation: 45542

You should prefer clarity over optimization. In 3 years of using Python, I have only had to profile to discover performance bottlenecks twice. Your original code is clear and efficient. Compressing the first two lines into one hurts readability and barely impacts performance.

If I had to revise your code, it would like this:

avg_review = sum(book['rw'] for book in books) / len(books)
avg_rating = sum(book['rg'] for book in books) / len(books)

(That's five lines of code down to two with an improvement of clarity.)

Upvotes: 2

Karl Knechtel
Karl Knechtel

Reputation: 61526

How can I solve this redundancy

By making a function, of course:

def average_value(items, key):
  values = [x[key] for x in items]
  return sum(items) / len(items)

avg_review, avg_rating = average_value(books, 'rw'), average_value(books, 'rg')

Upvotes: 1

Mark Byers
Mark Byers

Reputation: 838336

I'd avoid using reduce here. For something so simple use sum:

sum_review = sum(book['rw'] for book in books)
sum_rating = sum(book['rg'] for book in books)

In my opinion this simpler version doesn't need refactoring to remove redundancy. With just two items (rw and rg) I think it's best to just leave it as it is.

Upvotes: 9

jsbueno
jsbueno

Reputation: 110311

sum_review, sum_rating = reduce(lambda a,b: (a[0] + b[0], a[1]+b[1]), ((book['rw'], book['rg']) for book in books), (0,0) )
items = len(books)
avg_review = sum_review/items
avg_rating = sum_rating/items

(tested)

Upvotes: 2

Related Questions