Add vote percentage to poll options.
GitOrigin-RevId: e53ba947dc781a8db031f06bd69d03b9b9956f77
This commit is contained in:
parent
8fcb64e579
commit
f6a3e9037f
@ -192,8 +192,8 @@ maskPointChin = MaskPoint;
|
||||
maskPosition point:MaskPoint x_shift:double y_shift:double scale:double = MaskPosition;
|
||||
|
||||
|
||||
//@description Describes one answer option of a poll @text Option text, 1-100 characters @voter_count Number of voters for this option @is_chosen True, if the option was chosen by the user
|
||||
pollOption text:string voter_count:int32 is_chosen:Bool = PollOption;
|
||||
//@description Describes one answer option of a poll @text Option text, 1-100 characters @voter_count Number of voters for this option @vote_percentage The percentage of votes for this option, 0-100 @is_chosen True, if the option was chosen by the user
|
||||
pollOption text:string voter_count:int32 vote_percentage:int32 is_chosen:Bool = PollOption;
|
||||
|
||||
|
||||
//@description Describes an animation file. The animation must be encoded in GIF or MPEG4 format @duration Duration of the animation, in seconds; as defined by the sender @width Width of the animation @height Height of the animation
|
||||
|
Binary file not shown.
@ -308,7 +308,98 @@ PollManager::Poll *PollManager::get_poll_force(PollId poll_id) {
|
||||
}
|
||||
|
||||
td_api::object_ptr<td_api::pollOption> PollManager::get_poll_option_object(const PollOption &poll_option) {
|
||||
return td_api::make_object<td_api::pollOption>(poll_option.text, poll_option.voter_count, poll_option.is_chosen);
|
||||
return td_api::make_object<td_api::pollOption>(poll_option.text, poll_option.voter_count, 0, poll_option.is_chosen);
|
||||
}
|
||||
|
||||
vector<int32> PollManager::get_vote_percentage(const vector<int32> &voter_counts, int32 total_voter_count) {
|
||||
vector<int32> result(voter_counts.size(), 0);
|
||||
if (total_voter_count == 0 || voter_counts.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
int32 sum = 0;
|
||||
for (auto voter_count : voter_counts) {
|
||||
CHECK(0 <= voter_count);
|
||||
CHECK(voter_count <= std::numeric_limits<int32>::max() - sum);
|
||||
sum += voter_count;
|
||||
}
|
||||
CHECK(total_voter_count <= sum);
|
||||
if (total_voter_count != sum) {
|
||||
// just round to the nearest
|
||||
for (size_t i = 0; i < result.size(); i++) {
|
||||
result[i] =
|
||||
static_cast<int32>((static_cast<int64_t>(voter_counts[i]) * 200 + total_voter_count) / total_voter_count / 2);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// make sure that options with equal votes have equal percent and total sum is less than 100%
|
||||
int32 percent_sum = 0;
|
||||
vector<int32> gap(voter_counts.size(), 0);
|
||||
for (size_t i = 0; i < result.size(); i++) {
|
||||
auto multiplied_voter_count = static_cast<int64_t>(voter_counts[i]) * 100;
|
||||
result[i] = static_cast<int32>(multiplied_voter_count / total_voter_count);
|
||||
CHECK(0 <= result[i] && result[i] <= 100);
|
||||
gap[i] = static_cast<int32>(static_cast<int64_t>(result[i] + 1) * total_voter_count - multiplied_voter_count);
|
||||
CHECK(0 <= gap[i] && gap[i] <= total_voter_count);
|
||||
percent_sum += result[i];
|
||||
}
|
||||
CHECK(0 <= percent_sum && percent_sum <= 100);
|
||||
if (percent_sum == 100) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// now we need to choose up to (100 - percent_sum) options with minimum total gap, such so
|
||||
// any two options with the same voter_count are chosen or not chosen simultaneously
|
||||
struct Option {
|
||||
int32 pos = -1;
|
||||
int32 count = 0;
|
||||
};
|
||||
std::unordered_map<int32, Option> options;
|
||||
for (size_t i = 0; i < result.size(); i++) {
|
||||
auto &option = options[voter_counts[i]];
|
||||
option.pos = i;
|
||||
option.count++;
|
||||
}
|
||||
vector<Option> sorted_options;
|
||||
for (auto option : options) {
|
||||
auto pos = option.second.pos;
|
||||
if (gap[pos] > total_voter_count / 2) {
|
||||
// do not round to wrong direction
|
||||
continue;
|
||||
}
|
||||
if (gap[pos] == total_voter_count / 2 && result[pos] >= 50) {
|
||||
// round halves to the 50%
|
||||
continue;
|
||||
}
|
||||
sorted_options.push_back(option.second);
|
||||
}
|
||||
std::sort(sorted_options.begin(), sorted_options.end(), [&](const Option &lhs, const Option &rhs) {
|
||||
if (gap[lhs.pos] != gap[rhs.pos]) {
|
||||
// prefer options with smallest gap
|
||||
return gap[lhs.pos] < gap[rhs.pos];
|
||||
}
|
||||
return lhs.count > rhs.count; // prefer more popular options
|
||||
});
|
||||
|
||||
// dynamic programming or brute force can give perfect result, but for now we use simple gready approach
|
||||
int32 left_percent = 100 - percent_sum;
|
||||
for (auto option : sorted_options) {
|
||||
if (option.count <= left_percent) {
|
||||
left_percent -= option.count;
|
||||
|
||||
auto pos = option.pos;
|
||||
for (size_t i = 0; i < result.size(); i++) {
|
||||
if (voter_counts[i] == voter_counts[pos]) {
|
||||
result[i]++;
|
||||
}
|
||||
}
|
||||
if (left_percent == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id) const {
|
||||
@ -329,15 +420,27 @@ td_api::object_ptr<td_api::poll> PollManager::get_poll_object(PollId poll_id) co
|
||||
}
|
||||
poll_options.push_back(td_api::make_object<td_api::pollOption>(
|
||||
poll_option.text,
|
||||
poll_option.voter_count - static_cast<int32>(poll_option.is_chosen) + static_cast<int32>(is_chosen),
|
||||
poll_option.voter_count - static_cast<int32>(poll_option.is_chosen) + static_cast<int32>(is_chosen), 0,
|
||||
is_chosen));
|
||||
}
|
||||
if (!chosen_options.empty()) {
|
||||
voter_count_diff++;
|
||||
}
|
||||
}
|
||||
return td_api::make_object<td_api::poll>(poll->question, std::move(poll_options),
|
||||
poll->total_voter_count + voter_count_diff, poll->is_closed);
|
||||
auto total_voter_count = poll->total_voter_count + voter_count_diff;
|
||||
auto voter_counts = transform(poll_options, [](auto &poll_option) { return poll_option->voter_count_; });
|
||||
for (auto &voter_count : voter_counts) {
|
||||
if (total_voter_count < voter_count) {
|
||||
LOG(ERROR) << "Fix total voter count from " << total_voter_count << " to " << voter_count;
|
||||
total_voter_count = voter_count;
|
||||
}
|
||||
}
|
||||
auto vote_percentage = get_vote_percentage(voter_counts, total_voter_count);
|
||||
CHECK(poll_options.size() == vote_percentage.size());
|
||||
for (size_t i = 0; i < poll_options.size(); i++) {
|
||||
poll_options[i]->vote_percentage_ = vote_percentage[i];
|
||||
}
|
||||
return td_api::make_object<td_api::poll>(poll->question, std::move(poll_options), total_voter_count, poll->is_closed);
|
||||
}
|
||||
|
||||
telegram_api::object_ptr<telegram_api::pollAnswer> PollManager::get_input_poll_option(const PollOption &poll_option) {
|
||||
@ -771,10 +874,24 @@ PollId PollManager::on_get_poll(PollId poll_id, tl_object_ptr<telegram_api::poll
|
||||
<< " voters for an option";
|
||||
poll->total_voter_count = option.voter_count;
|
||||
}
|
||||
auto max_voter_count = std::numeric_limits<int32>::max() / narrow_cast<int32>(poll->options.size()) - 2;
|
||||
if (option.voter_count > max_voter_count) {
|
||||
LOG(ERROR) << "Have too much " << option.voter_count << " poll voters for an option";
|
||||
option.voter_count = max_voter_count;
|
||||
}
|
||||
is_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
int32 max_total_voter_count = 0;
|
||||
for (auto &option : poll->options) {
|
||||
max_total_voter_count += option.voter_count;
|
||||
}
|
||||
if (poll->total_voter_count > max_total_voter_count) {
|
||||
LOG(ERROR) << "Have only " << max_total_voter_count << " total poll voters, but there are "
|
||||
<< poll->total_voter_count << " voters in the poll";
|
||||
poll->total_voter_count = max_total_voter_count;
|
||||
}
|
||||
|
||||
if (!td_->auth_manager_->is_bot() && !poll->is_closed) {
|
||||
auto timeout = get_polling_timeout();
|
||||
|
@ -68,6 +68,8 @@ class PollManager : public Actor {
|
||||
|
||||
void on_get_poll_results_failed(PollId poll_id);
|
||||
|
||||
static vector<int32> get_vote_percentage(const vector<int32> &voter_counts, int32 total_voter_count);
|
||||
|
||||
template <class StorerT>
|
||||
void store_poll(PollId poll_id, StorerT &storer) const;
|
||||
|
||||
|
@ -6,6 +6,7 @@ set(TD_TEST_SOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/http.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/mtproto.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/message_entities.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/poll.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/secret.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/secure_storage.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/set_with_position.cpp
|
||||
|
Reference in New Issue
Block a user