このコミットが含まれているのは:
@@ -0,0 +1,92 @@
|
||||
class TheatrePostSelector
|
||||
Candidate = Struct.new(:post, :weight, :penalty, :tags, keyword_init: true)
|
||||
|
||||
def initialize(theatre:)
|
||||
@theatre = theatre
|
||||
end
|
||||
|
||||
def select
|
||||
candidates = weighted_candidates
|
||||
return nil if candidates.empty?
|
||||
|
||||
total = candidates.sum(&:weight)
|
||||
target = rand * total
|
||||
|
||||
candidates.each do |candidate|
|
||||
target -= candidate.weight
|
||||
return candidate.post if target <= 0
|
||||
end
|
||||
|
||||
candidates.last.post
|
||||
end
|
||||
|
||||
def weight_json(limit: 20)
|
||||
candidates = weighted_candidates
|
||||
sorted = candidates.sort_by { |candidate| [candidate.weight, candidate.post.id] }
|
||||
|
||||
{ tag_penalties: tag_penalty_json,
|
||||
lightest_posts: post_weight_json(sorted.first(limit)),
|
||||
heaviest_posts: post_weight_json(sorted.reverse.first(limit)) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :theatre
|
||||
|
||||
def weighted_candidates
|
||||
@weighted_candidates ||= begin
|
||||
penalties = tag_penalties
|
||||
posts = eligible_posts.includes(tags: :tag_name).to_a
|
||||
|
||||
posts.map do |post|
|
||||
post_tags = post.tags.to_a
|
||||
penalty = post_tags.sum { |tag| penalties[tag.id].to_i }
|
||||
Candidate.new(post:, penalty:, tags: post_tags, weight: 1.0 / (1.0 + penalty))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def eligible_posts
|
||||
posts = Post.where("url LIKE '%nicovideo.jp%'")
|
||||
posts = posts.where.not(id: theatre.current_post_id) if theatre.current_post_id
|
||||
posts
|
||||
end
|
||||
|
||||
def active_user_ids
|
||||
@active_user_ids ||= theatre.watching_users.ids
|
||||
end
|
||||
|
||||
def tag_penalties
|
||||
@tag_penalties ||=
|
||||
if active_user_ids.empty?
|
||||
{}
|
||||
else
|
||||
TheatreSkipEventVoter
|
||||
.joins(theatre_skip_event: :event_tags)
|
||||
.where(user_id: active_user_ids)
|
||||
.group('theatre_skip_event_tags.tag_id')
|
||||
.count
|
||||
end
|
||||
end
|
||||
|
||||
def tag_penalty_json
|
||||
return [] if tag_penalties.empty?
|
||||
|
||||
tags = Tag.where(id: tag_penalties.keys).includes(:tag_name).index_by(&:id)
|
||||
tag_penalties.map { |tag_id, penalty|
|
||||
tag = tags[tag_id]
|
||||
next unless tag
|
||||
|
||||
{ tag: TagRepr.inline(tag), penalty: }
|
||||
}.compact.sort_by { |row| [-row[:penalty], row[:tag]['name'].to_s] }
|
||||
end
|
||||
|
||||
def post_weight_json(candidates)
|
||||
candidates.map { |candidate|
|
||||
{ post: PostRepr.base(candidate.post),
|
||||
weight: candidate.weight,
|
||||
penalty: candidate.penalty,
|
||||
tags: candidate.tags.map { |tag| TagRepr.inline(tag) } }
|
||||
}
|
||||
end
|
||||
end
|
||||
新しい課題から参照
ユーザをブロックする