use std::cmp::Ordering;
use num_traits::Zero;
#[cfg(feature = "rand")]
use rand::{thread_rng, Rng};
use crate::{AddSubSelf, Bid};
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct AuctionResult<'a, B: Bid> {
pub winning_bids: Vec<&'a B>,
pub payments: Vec<(&'a B::Name, B::Value)>,
}
#[cfg(feature = "rand")]
#[cfg_attr(docsrs, doc(cfg(feature = "rand")))]
pub fn vcg_auction<'a, B: Bid>(
items: &[(B::Item, B::Quantity)],
exclusive_bid_sets: &'a [Vec<B>],
) -> Option<AuctionResult<'a, B>> {
let tiebreaker = |options: &[Vec<&B>]| {
if !options.is_empty() {
thread_rng().gen_range::<usize, _>(0..options.len())
} else {
0
}
};
vcg_auction_with_tiebreaker(items, exclusive_bid_sets, tiebreaker)
}
pub fn vcg_auction_with_tiebreaker<'a, B: Bid>(
items: &[(B::Item, B::Quantity)],
exclusive_bid_sets: &'a [Vec<B>],
tiebreaker: impl FnOnce(&[Vec<&B>]) -> usize,
) -> Option<AuctionResult<'a, B>> {
let exclusive_bid_sets = exclusive_bid_sets
.iter()
.map(|bs| bs.iter().collect::<Vec<_>>())
.collect::<Vec<_>>();
let (highest_bid_sets, _highest_value) =
find_highest_value_bid_sets(items, &exclusive_bid_sets);
let winning_bid_set = if highest_bid_sets.len() <= 1 {
highest_bid_sets.get(0)?
} else {
highest_bid_sets.get(tiebreaker(&highest_bid_sets))?
};
let payments =
calculate_payments(winning_bid_set, items, &exclusive_bid_sets);
Some(AuctionResult {
winning_bids: winning_bid_set.to_vec(),
payments,
})
}
fn find_highest_value_bid_sets<'a, B: Bid>(
items: &[(B::Item, B::Quantity)],
exclusive_bid_sets: &[Vec<&'a B>], ) -> (Vec<Vec<&'a B>>, B::Value) {
let mut highest_value_bid_sets: Vec<Vec<&'a B>> = vec![]; let mut highest_value = B::Value::zero();
let items_selected = items
.iter()
.map(|(item, _)| (item, B::Quantity::zero()))
.collect::<Vec<_>>();
let bid_sets_remaining = exclusive_bid_sets
.iter()
.filter_map(|bs| {
bs.iter().map(|b| b.bid_value()).max().map(|max| (bs, max))
})
.collect::<Vec<_>>();
find_highest_value_helper(
items,
&items_selected,
&bid_sets_remaining,
&[],
B::Value::zero(),
&mut highest_value_bid_sets,
&mut highest_value,
);
(highest_value_bid_sets, highest_value)
}
fn find_highest_value_helper<'a, B: Bid>(
item_stock: &[(B::Item, B::Quantity)], items_selected: &[(&B::Item, B::Quantity)], bid_sets_remaining: &[(&Vec<&'a B>, &B::Value)], bids_selected: &[&'a B], selected_value: B::Value,
highest_value_bid_sets: &mut Vec<Vec<&'a B>>, highest_value: &mut B::Value, ) {
for i in 0..items_selected.len() {
if items_selected[i].1 > item_stock[i].1 {
return;
}
}
if bid_sets_remaining.is_empty() {
match selected_value.cmp(highest_value) {
Ordering::Greater => {
*highest_value_bid_sets = vec![bids_selected.to_vec()];
*highest_value = selected_value;
}
Ordering::Equal => {
highest_value_bid_sets.push(bids_selected.to_vec());
}
Ordering::Less => (),
}
return;
}
let max_remaining_value = bid_sets_remaining
.iter()
.fold(B::Value::zero(), |sum, (_bs, max_bid_value)| {
sum.add(max_bid_value)
});
let possible_value = selected_value.add(&max_remaining_value);
if possible_value < *highest_value {
return;
}
let (next_bid_set, _max_bid_value) = bid_sets_remaining[0];
for bid in next_bid_set {
let mut bids_selected_with_new_bid = bids_selected.to_vec();
bids_selected_with_new_bid.push(bid);
let mut items_selected_with_new_bid = items_selected
.iter()
.map(|(id, qty)| (*id, qty.clone()))
.collect::<Vec<_>>();
for (item, qty) in items_selected_with_new_bid.iter_mut() {
if let Some((_, bid_qty)) =
bid.bid_items().iter().find(|(id, _)| *id == **item)
{
*qty = qty.add(bid_qty)
}
}
find_highest_value_helper(
item_stock,
&items_selected_with_new_bid,
&bid_sets_remaining[1..],
&bids_selected_with_new_bid,
selected_value.add(bid.bid_value()),
highest_value_bid_sets,
highest_value,
);
}
find_highest_value_helper(
item_stock,
items_selected,
&bid_sets_remaining[1..],
bids_selected,
selected_value,
highest_value_bid_sets,
highest_value,
);
}
fn calculate_payments<'a, B: Bid>(
winning_bid_set: &[&'a B],
items: &[(B::Item, B::Quantity)],
exclusive_bid_sets: &[Vec<&'a B>], ) -> Vec<(&'a B::Name, B::Value)> {
let mut payments = vec![];
for winning_bid in winning_bid_set {
let bidder_name = winning_bid.bidder_name();
if payments.iter().any(|(name, _)| *name == bidder_name) {
continue;
}
let bid_sets_without_bidder = exclusive_bid_sets
.iter()
.map(|bs| {
bs.iter()
.filter(|b| *b.bidder_name() != *bidder_name)
.copied()
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let auction_value_without_bidder =
find_highest_value_bid_sets(items, &bid_sets_without_bidder).1;
let value_of_other_bids = winning_bid_set
.iter()
.filter(|b| *b.bidder_name() != *bidder_name)
.fold(B::Value::zero(), |acc, b| acc.add(b.bid_value()));
let payment = auction_value_without_bidder.sub(&value_of_other_bids);
payments.push((winning_bid.bidder_name(), payment));
}
payments
}