Fellow Stranger
Fellow Stranger

Reputation: 34113

How to interleave multiple ActiveRecord association queries?

I have five categories with about five posts each. E.g.

=> category_a.posts
=> #<Post id:01>, #<Post id:02>, #<Post id:03> etc

=> category_b.posts
=> #<Post id:10>, #<Post id:11>, #<Post id:12> etc

=> category_c.posts
=> #<Post id:20>, #<Post id:21>, #<Post id:22> etc

I'd like a collection of children where they are interleaved (as evenly as possible), i.e.

#<Post id:01>, #<Post id:10>, #<Post id:20>, #<Post id:02>, #<Post id:11> etc

How could I accomplish this?

Upvotes: 1

Views: 91

Answers (2)

user2251284
user2251284

Reputation: 417

The answer by Dave Schweisguth is ok and probably lets you know what you need to know to solve the problem. But, that solution does not let you choose the order the items get interleaved in. Here is a, hopefully, more general solution:

def full_zip(*args)
  max_len = args.map(&:length).max
  ([nil] * max_len).zip(*args).flatten.compact
end

and then this would be used like full_zip(category_a.posts, category_b.posts, category_c.posts) or full_zip(*[cat_a, cat_z].map(&:posts)). Use the categories in whatever order you want them in.

Edit: one problem with this one is if the original lists have nils in them you want to keep.

Upvotes: 2

Dave Schweisguth
Dave Schweisguth

Reputation: 37657

It's only five queries and around 15 models, so it would be fine to do it in memory:

 posts =
   [category_a, category_b, category_c, category_d, category_e].
   map(&:posts)
   sort_by(&:length)
 posts.first.zip(*posts[1..-1]).flatten.compact

Putting the longest array first prevents zip from dropping elements from the arrays in its arguments that are longer than the array it's called on. compact removes any nils created by zip if the arrays in its arguments are longer than the array it's called on.

Upvotes: 1

Related Questions