1

I have a statistics module for my library class that shows the most popular books and top readers

module Statistics
  def top_reader
    orders_grouped = orders.group_by(&:reader)
    tab = orders_grouped.max_by(3) { |_k, v| v.count }.map(&:first)
    puts "#{tab.map(&:name).join(', ')} - are the top readers"
  end

  def top_book
    orders_grouped = orders.group_by(&:book)
    tab = orders_grouped.max_by(2) { |_k, v| v.count }.map(&:first)
    puts "#{tab.map(&:title).join(', ')} - are the top books"
  end
end

That's how i get top reader and top book I need to get amount of unique readers of the top book now, i tried to do it this way but it's not working correctly and the code are not very clean

  def top_book_readers
    puts @orders.select { |order| books.include? order.book }.uniq(&:reader).size
  end

Expected result is just the amount of unique readers that purchased top book

Edit:

  def top_book_readers
    orders_grouped = orders.group_by(&:book)
    top_orders_grouped = orders_grouped.max_by(1) { |_k, v| v.count }

    top_orders_grouped.each do |book, orders|
      num_readers = orders.map(&:reader).uniq.count
      puts "#{book.title} has #{num_readers} readers"
    end
  end

This works now, number of readers of top books are also configurable

3
  • Do you want to get the readers of the top book, or the top two books as produced by your top_book method? Commented Apr 19, 2021 at 17:20
  • It appears you want something like @orders.group_by { ... }.count { |_,v| v.size == 1 }. Commented Apr 19, 2021 at 17:24
  • @maxpleaner i want to get readers of the top books i get. But as you can see the number of top books is configurable. Would appreciate if you could also help with 1 top book readers Commented Apr 19, 2021 at 17:44

2 Answers 2

2

If understood correctly, it should work like this:


# same like yours
top_books = orders.
  group_by(&:book).
  max_by(3) { |_k, v| v.count }.
  map(&:first)

# Hash of { <Book 1> => 30, <Book 2> => 10 }
reader_count_per_book = top_books.
  map { |book| [book, book.readers.uniq.count] }.
  to_h

# Or: uniq readers, that read at least one of the top books
uniq_readers_of_top_books = top_books.flat_map(&:readers).uniq.count

In general, if you have a database attached, and using ActiveRecord (your question is tagged as Rails), then it might be better to do all the work in sql (without knowning about your models/relationships/column names):

top_books = Order.
  joins(:book).
  order('count(orders.id) desc').
  limit(3).
  group("books.id").
  count

uniq_readers_of_top_books = Book.
  joins(:readers).
  where(id: top_books.keys).
  count('distinct readers.id')

reader_count_per_book = Book.
  joins(:readers).
  where(id: top_books.keys).
  group('books.title').
  count('distinct readers.id')
Sign up to request clarification or add additional context in comments.

Comments

2

You can re-use some of that group_by logic from the other methods.

orders_grouped = orders.group_by(&:book)
top_orders_grouped = orders_grouped.max_by(2) { |_k, v| v.count }

Then to get the readers from the top_orders_grouped, you just get the unique reader values:

top_orders_grouped.each do |book, orders|
  num_readers = orders.map(&:reader).uniq.count
  puts "#{book} has #{num_readers} readers"
end

I should also mention, it's usually a good idea to make your methods return data instead of only printing output. This enables you to re-use the methods more easily.

3 Comments

Most of the time #map + #flatten = #flat_map (that is, if you expect to flatten 1 level)
But i just need to get the number, not an array. For example: "top book was purchased by #{top_book_readers} readers" And number would be an expected result. It's an application
Seems like offloading the group and limit to the database would be a far better option IMO.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.