Need to replace multiple independent if statements - java

I am a beginner to Java and I have a health insurance program, which returns a total quote based on if the customer has any health conditions already present or not. Each health condition increases the total amount by a different %, and there can be more than one health condition present, in which case the total will be increased according to the order of the if statements. For example, the customer may have "Bone marrow", in which case the total is multiplied by 20%, or they may have "Bone marrow" and "Cancer" in which case the total is increased by 20% and then 25% in that order.
I have this written in multiple independent if statements because unlike with an if else statement, there can be more than one health condition present. Is there a way I can write this in a way that's more elegant than just a long list of if statements?
if (customer.getHealthConditions().equals("Bone Marrow")) {
total *= 1.2;
}
if (customer.getHealthConditions().equals("Cancer")) {
total *= 1.25;
}
if (customer.getHealthConditions().equals("Cardiovascular Disease")) {
total *= 1.3;
}
if (customer.getHealthConditions().equals("Gastrointestinal")) {
total *= 1.1;
}
if (customer.getHealthConditions().equals("Infections")) {
total *= 1.1;
}
if (customer.getHealthConditions().equals("Kidneys")) {
total *= 1.25;
}
if (customer.getHealthConditions().equals("Lungs")) {
total *= 1.25;
}
if (customer.getHealthConditions().equals("Musculoskeletal")) {
total *= 1.3;
}

It seems that switch statement is more appropriate in this case:
double quotient = 1.0;
switch(customer.getHealthConditions()) {
case "Bone Marrow":
quotient = 1.2; break;
case "Cancer":
case "Kidneys":
case "Lungs":
quotient = 1.25; break;
case "Cardiovascular Disease":
case "Musculoskeletal":
quotient = 1.3; break;
case "Gastrointestinal":
case "Infections":
quotient = 1.1; break;
}
total *= quotient;
In Java 12+ switch statement was enhanced with multiple cases and arrow -> so it may be written as:
total *= switch(customer.getHealthConditions()) {
case "Bone Marrow" -> 1.2;
case "Cancer", "Kidneys", "Lungs" -> 1.25;
case "Cardiovascular Disease", "Musculoskeletal" -> 1.3;
case "Gastrointestinal", "Infections" -> 1.1;
default -> 1.0;
}
Update
If health conditions are multiple, then equals is not applicable at all, instead String::contains or Collection::contains should be used and it would be better to have a map or enum of the disease to quotient:
Map<String, Double> quotients = Map.of(
"Bone Marrow", 1.2,
"Cancer", 1.25,
"Kidneys", 1.25,
"Lungs", 1.25
// ...
);
total *= quotients.entrySet().stream()
.filter(e -> customer.getHealthConditions().contains(e.getKey()))
.map(Map.Entry::getValue)
.reduce(1.0, (p, v) -> p * v);

I think here an enum could be useful, we need not only to sum all the values but also the order may be important (the progressive premium increase changes otherwise)
import java.util.Comparator;
import java.util.Optional;
import static java.util.Arrays.*;
enum HealthConditionPremium {
boneMarrow(1,1.2, "Bone Marrow"),
cancer(2,1.25, "Cancer"),
cardiovascularDisease(3,1.3, "Cardiovascular Disease"),
gastrointestinal(4,1.1, "Gastrointestinal"),
infections(5,1.1, "Infections"),
kidneys(6,1.25, "Kidneys"),
lungs(7,1.25, "Lungs"),
musculoskeletal(8,1.3, "Musculoskeletal");
public final int order;
public final double premiumIncrease;
public final String matchString;
HealthConditionPremium(int order, double premiumIncrease, String matchString) {
this.order = order;
this.premiumIncrease = premiumIncrease;
this.matchString = matchString;
}
static Optional<HealthConditionPremium> of(String condition) {
return stream(values()).filter(healthCondition -> healthCondition.matchString.equals(condition)).findAny();
}
public static double totalForHealthConditions(String ...conditions) {
return stream(conditions).
filter(condition -> condition != null && !condition.isEmpty()).
map(HealthConditionPremium::of).
filter(Optional::isPresent).
map(Optional::get).
sorted(Comparator.comparingInt(hc -> hc.order)).
map(healthConditionPremium -> healthConditionPremium.premiumIncrease).
reduce(1.0, (total, additionalPremium) -> total * additionalPremium);
}
}
class Scratch {
public static void main(String[] args) {
Customer customer = new Customer("Gastrointestinal");
double total = 1;
if (customer.getHealthConditions().equals("Bone Marrow")) {
total *= 1.2;
}
if (customer.getHealthConditions().equals("Cancer")) {
total *= 1.25;
}
if (customer.getHealthConditions().equals("Cardiovascular Disease")) {
total *= 1.3;
}
if (customer.getHealthConditions().equals("Gastrointestinal")) {
total *= 1.1;
}
if (customer.getHealthConditions().equals("Infections")) {
total *= 1.1;
}
if (customer.getHealthConditions().equals("Kidneys")) {
total *= 1.25;
}
if (customer.getHealthConditions().equals("Lungs")) {
total *= 1.25;
}
if (customer.getHealthConditions().equals("Musculoskeletal")) {
total *= 1.3;
}
System.out.println("total = " + total);
System.out.println("----------------------------------");
double totalWithEnum = HealthConditionPremium.totalForHealthConditions("Gastrointestinal");
System.out.println("totalWithEnum = " + totalWithEnum);
System.out.println("----------------------------------");
double totalManyWithEnum = HealthConditionPremium.totalForHealthConditions("Gastrointestinal", "Cancer", "Kidneys");
System.out.println("totalManyWithEnum = " + totalManyWithEnum);
double totalManyWithEnumDifferentOrder = HealthConditionPremium.totalForHealthConditions("Cancer", "Gastrointestinal", "Kidneys");
System.out.println("totalManyWithEnumDifferentOrder = " + totalManyWithEnumDifferentOrder);
}
static class Customer {
private final String condition;
Customer(String condition) {
this.condition = condition;
}
public String getHealthConditions() {
return condition;
}
}
}
Output
total = 1.1
----------------------------------
totalWithEnum = 1.1
----------------------------------
totalManyWithEnum = 1.71875
totalManyWithEnumDifferentOrder = 1.71875
Some words of advice :)
Using a Double for premium (Money related) calculation is not advised, use BigDecimal Instead
The Reason for order in the enum is to make the order explicit and NOT rely on ordinal (order of definition of enum)
This way the definition of Health Conditions and Their Premiums is gathered in one place for easy reading.
The Assumption (and probably a big one) customer.getHealthConditions() actually returns an array of Strings to pass to totalForHealthConditions

Related

Turning a Queue into a Priority Queue

In the CarWash program that I have right now there is currently a normal queue that I would like to change into a priority queue. My goal is to take one of the basic server characteristics and use that for priority but I am lost on how to do that. In previous attempts I have tried to change the normal queue into a priority queue and have ran into issues on how I am supposed to base it off a server characteristic.
public class CarWash {
public static void main(String[ ] args) {
Scanner kb = new Scanner (System.in);
System.out.println("Enter wash time: ");
int WASHTIME = kb.nextInt();
System.out.println("Enter arrival probability: ");
double ARRIVALPROB = kb.nextDouble();
System.out.println("enter time for simulation: ");
int TOTALTIME = kb.nextInt();
carWashSimulate(WASHTIME, ARRIVALPROB, TOTALTIME);
}
public static void carWashSimulate(int washTime, double arrivalProb, int totalTime) { //simulates the car wash
Queue<Integer> arrivalTimes = new LinkedList<Integer>( );
int next;
ClientGenerator arrival = new ClientGenerator(arrivalProb);
Server machine = new Server(washTime);
ExpressServer newM = new ExpressServer(washTime);
Averager waitTimes = new Averager( );
Averager lostCustomer = new Averager();
int currentSecond;
// Write the parameters to System.out.
System.out.println("Seconds to wash one car: " + washTime);
System.out.print("Probability of customer arrival during a second: ");
System.out.println(arrivalProb);
System.out.println("Total simulation seconds: " + totalTime);
// Check the precondition:
if (washTime <= 0 || arrivalProb < 0 || arrivalProb > 1 || totalTime < 0)
throw new IllegalArgumentException("Values out of range");
for (currentSecond = 0; currentSecond < totalTime; currentSecond++) {
// Simulate the passage of one second of time
// Check whether a new customer has arrived.
if (arrival.query( )){
System.out.println("Customer arrived at " + currentSecond);
if(arrivalTimes.size() <= 8){
arrivalTimes.add(currentSecond);
}
else{
System.out.println("They left, line was too long");
lostCustomer.addNumber(1);
}
// Check whether we can start washing another car.
if ((!machine.isBusy( )) && (!arrivalTimes.isEmpty( )))
{
next = arrivalTimes.remove( );
waitTimes.addNumber(currentSecond - next);
machine.start( );
System.out.println("Server started at " + currentSecond + " serving customer " + next);
}
// Subtract one second from the remaining time in the current wash cycle.
machine.reduceRemainingTime( );
} // end of for loop
// Write the summary information about the simulation.
System.out.println("Customers served: " + waitTimes.howManyNumbers( ));
if (waitTimes.howManyNumbers( ) > 0)
System.out.println("Average wait for customers served: " + waitTimes.average( ) + " sec");
System.out.println("The number of customers lost was " + lostCustomer);
}
}
}
Client Generator Class:
public class ClientGenerator {
private double probability;
// The approximate probability of query( ) returning true.
public ClientGenerator(double p) {
if ((p < 0) || (1 < p))
throw new IllegalArgumentException("Illegal p: " + p);
probability = p;
}
public void adjust(double a) {
if(a > 0 && a+probability < 1) {
probability = probability + a;
} else if (a < 0 && probability + a > 0) {
probability = probability + a;
}
}
public double getProbability() {
return probability;
}
public boolean query( ) {
return (Math.random( ) < probability);
}
}
Server Class:
public class Server {
private int secondsForService; // Seconds for a single wash
private int timeLeft; // Seconds until this Server is no longer busy
public Server(int s) {
secondsForService = s;
timeLeft =0;
}
public boolean isBusy( ) {
return (timeLeft > 0);
}
public void reduceRemainingTime( ) {
if (timeLeft > 0) timeLeft--;
}
public void start( ) {
if (timeLeft > 0)
throw new IllegalStateException("Server is already busy.");
timeLeft = secondsForService;
}
}
Averager class:
public class Averager
{
private int count; // How many numbers have been given to this averager
private double sum; // Sum of all the numbers given to this averager
public Averager( )
{
count =0;
sum = 0;
}
public void addNumber(double value)
{
if (count == Integer.MAX_VALUE)
throw new IllegalStateException("Too many numbers");
count++;
sum += value;
}
public double average( )
{
if (count == 0)
return Double.NaN;
else
return sum/count;
}
public int howManyNumbers( )
{
return count;
}
}
The question seems to be about how to configure the priority rules employed by a java.util.PriorityQueue. That's relatively straightforward. Depending on which constructor you use to instantiate one, PriorityQueue relies either on the natural order of its elements (see Comparable) or on the order defined by a specified Comparator. Whenever such a queue contains any elements, its head is the least with respect to the operative ordering, or among the least if there are multiple elements such that no other element is less.
In comments you clarified
my goal is to implement some way of randomly assigning a value that represents the type of car, which will then prioritize the luxury car before the other cars.
Note well that PriorityQueue uses the properties of the enqueued objects to establish their relative order. Right now you are enqueuing integer arrival times, which don't confer an ability to distinguish between classes of car. If you want to carry more information about each vehicle that arrives then you would probably want to create a new class for that, maybe something like this:
class ClientArrival {
enum Category { NORMAL, LUXURY }
Category category;
int arrivalTime;
// ...
}
You would then be able to create one or more implementations of Comparator<ClientArrival> to use to define the priority rule for a PriorityQueue<ClientArrival>. For example,
class LuxuryFirstComparator implements Comparator<ClientArrival> {
int compare(ClientArrival o1, ClientArrival o2) {
if (o1.getCategory() == o2.getCategory()) {
// ... order based on arrival time ...
} else if (o1.getCategory() == ClientArrival.Category.LUXURY) {
return -1;
} else {
return 1;
}
}
}
One might set up a PriorityQueue<ClientArrival> using that to determine priority via
Queue<ClientArrival> arrivals = new PriorityQueue<>(new LuxuryFirstComparator());

How to iterate to find the lowest value

Struggling to understand where I went wrong with the iteration at the get best fare method
The array holds [5.77, 2.44, 2.35] and should return the second index, however it seems that it is stuck at the double lowestPrice = lowestPriceRide[0];
I thought that maybe I was putting the return out of scope, but it didn't work.
> import java.lang.*;
import java.util.Arrays;
public class TransitCalculator {
double numberOfDays = 0.0;
double numberOfRides = 0.0;
double pricePerRide = 2.75;
double pricePerWeek = 33.00;
double priceUnlimited = 127.00;
double perRide = 0.00;
public TransitCalculator(double days, double rides){
numberOfDays = days;
numberOfRides = rides;
}
public double unlimited7Price(){
double numOfWeeks = Math.ceil(numberOfDays/7) ; // Math.ceil will return the largest integer that is divisble without a remainder //
double totalPrice = numOfWeeks * pricePerWeek;
return totalPrice / numberOfRides;
}
public double[] getRidePrices(){ // 28/06/2020 Sunday. Math is verified.
double perRide = pricePerRide * numberOfRides / numberOfDays;
double perWeek = unlimited7Price();
double unlimited = priceUnlimited / numberOfRides;
double ridePrices[]; // Declared Array //
ridePrices = new double[] {perRide, perWeek, unlimited}; // New array, with added elements. Could be a mistake since I failed to declare elements//
return ridePrices;
}
public String getBestFare(){ // Error in the iteration and lowest value find! //
double lowestPriceRide[];
lowestPriceRide = getRidePrices();
double lowestPrice = lowestPriceRide[0];
for(int i = 0; i< lowestPriceRide.length; i++) {
if (lowestPrice < lowestPriceRide[i]) {
lowestPriceRide[i] = lowestPrice;
}
}
if(lowestPrice == lowestPriceRide[0]){
System.out.println("You should take the 'Pay per Ride' option in our NYC transit");
}
else if(lowestPrice == lowestPriceRide[1]){
System.out.println("You should take the 'Weekly Unlimited' plan in our NYC Transit");
}
else if(lowestPrice == lowestPriceRide[2]){
System.out.println("You should take the Unlimited ride plan in our NYC Transit");
}
return "at " + lowestPrice + "$ per Ride";
}
public static void main(String[] args){
TransitCalculator test = new TransitCalculator(26, 54);
System.out.println(test.getBestFare()); //
}
}
You are not setting the right value; currently, you set the element in the array to the lowest price instead of setting the lowest price to the element of the array. You also compare against the wrong value; you should check that the current array element is less than the best price, instead of the other way around.
Change
if(lowestPrice < lowestPriceRide[i])
lowestPriceRide[i] = lowestPrice;
To
if(lowestPriceRide[i] < lowestPrice)
lowestPrice = lowestPriceRide[i];
See the updated code in action here.
Note that it is unnecessary to import java.lang, as the package is implicitly imported.
The problem is in your if condition:
if (lowestPrice < lowestPriceRide[i]) {
lowestPriceRide[i] = lowestPrice;
}
You need to see if the current lowestPriceRide[i] is less than the already existing lowestPrice then update your existing lowestPrice. So the condition would be now:
if (lowestPriceRide[i] < lowestPrice) {
lowestPrice = lowestPriceRide[i];
}
This should be your comparison for lowest price :
double lowestPrice = lowestPriceRide[0];
for(int i = 0; i< lowestPriceRide.length; i++) {
if (lowestPriceRide[i] < lowestPrice) {
lowestPrice = lowestPriceRide[i];
}
}

Calculate total percent from a suite of trading orders

I try to sum up percent from a suite of simple trading orders in Java.
Let say that an "order" is composed of a position (BUY/SELL), a price and a quantity.
If I have:
TIME | POSITION | QTY |PRICE
DAY1: BUY 1 at 10$
DAY2: SELL 1 at 12$
I have made 20% and it's quite easy to program it.
But... taking another example, I'm not smart enough to find the right way to get the total percent:
TIME | POSITION | QTY | MARKET PRICE
DAY1: BUY 1 at 10$
DAY2: SELL 1 at 12$ -> (20%)
DAY3: BUY 2 at 10$ -> (0%)
DAY4: SELL 1 at 13$ -> (30%)
DAY5: SELL 1 at 14$ -> (40%)
So a total of 90%.
First question, can these percentages be sum up ? (is it mathematically correct ?)
Second, how you will do that in java ?
Here is a sample method which is just working for first example:
public static double getTotalPercent(List<MarketOrder> orders) {
double percent = 0;
MarketOrder previousOrder = orders.get(0);
for (int i = 1; i < orders.size(); i++) {
MarketOrder order = orders.get(i);
percent += getPercent(previousOrder, order);
previousOrder = order;
}
return percent;
}
Assuming the following:
You have some type MarketOrder that looks like this,
class MarketOrder {
public static enum OrderType {
BUY,
SELL,
}
private final MarketOrder.OrderType type;
private final int amount;
private final int money;
public MarketOrder(MarketOrder.OrderType type, int amount, int money) {
if (amount < 1 || money < 1) {
throw new IllegalArgumentException();
}
this.type = type;
this.amount = amount;
this.money = money;
}
}
and you have some Collection<MarketOrder> that represents transactions that you want to calculate a profit for, then declare a static method on MarketOrder that calculates the profit or loss of a Collection<MarketOrder>,
public static double calculateProfit(Collection<MarketOrder> orders) {
if (orders == null) {
throw new NullPointerException("orders is null");
}
int buyTotal = 0, sellTotal = 0, buyMoney = 0, sellMoney = 0;
for (MarketOrder order : orders) {
if (order.type == MarketOrder.OrderType.BUY) { // accumulate the units bought and money
buyTotal += order.amount;
buyMoney += order.money * order.amount;
} else {
sellTotal += order.amount;
sellMoney += order.money * order.amount;
}
}
if (buyTotal != sellTotal) {
throw new IllegalArgumentException("buyTotal != sellTotal");
}
return ((double)sellMoney/(double)buyMoney) - 1;
}
Obviously, the exact code you'll need to work out depends upon your MarketOrder type, but the key bits (accumlating the amounts of the transactions, doing the math) is the same.

Is there a Java equivalent for PowerScript "CHOOSE CASE"?

I find the PowerScript's CHOOSE CASE statement very useful, as it make so that the code is more clearly than a lot of ifs and else ifs.
Here a example of how it works, from the above link:
CHOOSE CASE weight
CASE IS < 16
Postage=Weight*0.30
Method="USPS"
CASE 16 to 48
Postage=4.50
Method="UPS"
CASE ELSE
Postage=25.00
Method="FedEx"
END CHOOSE
a CASE 5 to 11 is the same as CASE 5, 6, 7, 8, 9, 10, 11
Note that the CHOOSE CASE is not equivalent to java's switch
In Java, you can use multiple case statements, but there isn't a nice way to specify an expression as the case qualifier, just literals:
switch(weight) {
case 1:
case 2:
case 3:
postage = weight * 0.30;
method = "USPS";
break;
case 4:
case 5:
case 6:
postage = 4.5;
method = "UPS";
break;
default:
postage = 25.0;
method = "FedEx";
break;
}
To get nice ranges, stick with if/else:
if(weight > 0 && weight <= 3) {
postage = weight * 0.30;
method = "USPS";
}
else if(weight > 3 && weight <= 6) {
postage = 4.5;
method = "UPS";
}
else {
postage = 25.0;
method = "FedEx";
}
If your objective is cleaning up the decision point, you could encapsulate the code that decides what case applies separately from the code that uses that decision, as in:
enum WeightClass { LOW, MEDIUM, HIGH };
public WeightClass determineWeightClass(int weight)
{
return (weight < 16)
? WeightClass.LOW
: (weight <= 48
? WeightClass.MEDIUM
: WeightClass.HIGH);
}
And at the decision point:
switch(determineWeightClass(weight))
{
case LOW:
...
break;
case MEDIUM:
...
break;
case HIGH:
...
break;
}
Not exactly the same. If you want to implement such fragment in Java, you have to use if-else[-if] statement.
Basically, it should look like this:
if (weight < 16) {
//something
} else if (weight >= 16 && weight <= 48) {
//something else
} else {
//some other thing
}
Hope it works for you. :)
If there are only 3 cases, a series of if/else is fine. If you have many conditions, you could use a Navigable map and couple it with an enum for a nice and slick design:
public class Test1 {
public static void main(String[] args) {
printDelivery(0);
printDelivery(5);
printDelivery(16);
printDelivery(48);
printDelivery(50);
}
private static void printDelivery(int weight) {
Delivery d = Delivery.getDelivery(weight);
System.out.println("Weight: " + weight + " => $" + d.getPostage(weight) + " with " + d.getMethod());
}
static enum Delivery {
LOW_WEIGHT(15) {
public double getPostage(int weight) { return 0.3 * weight; }
public String getMethod() { return "USPS"; }
}, MEDIUM_WEIGHT(47) {
public double getPostage(int weight) { return 4.5; }
public String getMethod() { return "UPS"; }
}, HIGH_WEIGHT(Integer.MAX_VALUE){
public double getPostage(int weight) { return 25.0; }
public String getMethod() { return "FedEx"; }
};
private static final NavigableMap<Integer, Delivery> deliveries = new TreeMap<> ();
static {
for (Delivery e : values()) {
deliveries.put(e.maxWeight, e);
}
}
private final int maxWeight;
Delivery(int maxWeight) {
this.maxWeight = maxWeight;
}
public static Delivery getDelivery(int weight) {
return deliveries.ceilingEntry(weight).getValue();
}
abstract double getPostage(int weight);
abstract String getMethod();
}
}
No. You would have to use a series of if-elseif-else statements.

Recursive function to calculate possible finish for darts

I'm trying to write a recursive function in Java, to determine how to finish for a game of Darts. Basically, you have a maximum of 3 darts, en you have to finish with a double.
If you don't know the rule of Darts x01 games with Double Out finishing, it's difficult to understand this question... Let me try to explain. For simplicity, I keep the Bull's eye out of the equation for now.
Rules:
1) You have three darts which you can throw at number 1 through 20
2) A single hit can have a single, double or triple score
E.g. you can hit:
single 20 = 20 points or
double 20 = 40 points or
triple 20 = 60 points
3) In one turn, you can score a maximum of 180 points (3x triple 20 = 3*60 = 180). Anything higher than 180 is impossible. This doesn't mean anything below 180 IS possible. 179 for example, is impossible as well, because the next best score is triple20+triple20+triple19 = 167
4) Normally, you start at 501, and you throw 3 darts, untill you have exactly 0 points left.
5) Now, in Double Out, it is required that the last dart hits a Double
E.g. if you have 180 points left, you cannot finish, because your last dart has to be a double. So the maximum (with ignoring the bulls eye) = triple20 + triple20 + double20 = 160
And if your score is 16, you can simply finish using 1 dart by hitting the double 8.
Another example, if your score is 61, you can hit triple17 + double5 (= 51 + 10)
Current Code
Anyway, below is what I have so far. I know it's far from what I need, but no matter what I try, i always get stuck. Perhaps someone can share his thoughts on an another approach
private class Score{
int number; // the actual number, can be 1...20
int amount; // multiplier, can be 1, 2 or 3
public Score(int number, int amount){
this.number = number; // the actual number, can be 1...20
this.amount = amount; // multiplier, can be 1, 2 or 3
}
public int value()
{
return number * amount; // the actual score
}
public void increment()
{
if(this.amount == 0)
this.amount = 1;
this.number++;
if(this.number >= 20)
{
this.number = 0;
this.amount++;
if(this.amount >= 3)
this.amount = 3;
}
}
}
public ArrayList<Score> canFinish(int desired, ArrayList<Score> score){
// If this is the case -> we have bingo
if(eval(score) == desired) return score;
// this is impossible -> return null
if(eval(score) > 170) return null;
// I can't figure out this part!!
Score dart3 = score.remove(2);
Score dart2 = score.remove(1);
if(dart2.eval() < 60){
dart2.increment();
}
else if(dart3.eval() < 60){
dart3.increment();
}
score.add(dart2);
score.add(dart3);
return canFinish(desired, score);
}
public int eval(ArrayList<Score> scores)
{
int total = 0;
for(Score score : scores){
total += score.value();
}
return total;
}
I want to simply call:
ArrayList<Score> dartsNeeded = new ArrayList<Score>();
dartsNeeded.add(new Score(16, 2)); // Add my favourite double
dartsNeeded.add(new Score(0, 0));
dartsNeeded.add(new Score(0, 0));
// and call the function
dartsNeeded = canFinish(66, dartsNeeded);
// In this example the returned values would be:
// [[16,2],[17,2],[0,0]] -> 2*16 + 2*17 + 0*0 = 66
// So I can finish, by throwing Double 17 + Double 16
So, if it is impossible to finish, the function would return null, but if there is any possible finish, i reveive that ArrayList with the 3 darts that I need to make my desired score...
Short Summary
The problem is that the above code only helps to find 1 dart, but not for the combination of the two darts. So canFinish(66, darts) works -> but canFinish(120, darts) gives a StackOverflow Exception. For 120, I would expect to get somthing like triple20, double14, double16 or any other valid combination for that matter.
If you log the scores that canFinish tries, you can see that there are a lot of possibilities missed out. Values of 20 are ignored, and one dart is incremented completely before the other dart values are modified.
Instead, it can be solved recursively as follows. canFinish(desired, score) returns any combination of darts that can be added to score to give the total of desired. Call it with a list of however many darts you know, or any empty list to find any possibility.
canFinish(desired, score)
if darts sum to desired, return desired
if there are fewer than 3 darts in score
for each possible value of a dart (if it's the last dart, check for a double)
add dart to score
if canFinish(desired, score) != null
return canFinish(desired, score)
end
remove dart from score
end
end
return null
end
I ended up using the following functions. Which kind of is a combination of switch statments and recursion... Hope someone finds it as usefull as I
public static void getCheckout(int score, int fav_double, ICheckOutEvent listener)
{
if(score > 170) return;
if(score == 170) listener.onCheckOut("T20 T20 Bull");
ArrayList<Dart> darts = new ArrayList<Dart>();
darts.add(new Dart(fav_double, 2));
darts.add(new Dart(0,0));
darts.add(new Dart(0,0));
darts = getDarts(score, darts);
if(darts != null) {
listener.onCheckOut(toString(darts));
return;
}
for(int dubble = 20 ; dubble >= 1 ; dubble--)
{
if(dubble == fav_double) continue;
darts = new ArrayList<Dart>();
darts.add(new Dart(dubble, 2));
darts.add(new Dart(0,0));
darts.add(new Dart(0,0));
darts = getDarts(score, darts);
if(darts != null){
listener.onCheckOut(toString(darts));
return;
}
}
}
public static ArrayList<Dart> getDarts(int desired, ArrayList<Dart> score)
{
Dart dart1 = canFinish(desired);
if(dart1 != null){
score.set(0, dart1);
return score;
}
int rest = desired - score.get(0).value();
Dart dart2 = canScore(rest);
if(dart2 != null)
{
score.set(0, score.get(0));
score.set(1, dart2);
return score;
}
Dart temp = score.get(1);
if(temp.increment())
{
rest = desired - score.get(0).value() - temp.value();
score.set(0, score.get(0));
score.set(1, temp);
Dart dart3 = canScore(rest);
if(dart3 != null)
{
score.set(2, dart3);
return score;
}
if(rest > 60 && temp.increment())
temp.estimate(rest / 2);
score.set(1, temp);
return getDarts(desired, score);
}
return null;
}
public static int eval(ArrayList<Dart> scores)
{
int total = 0;
for(Dart score : scores){
total += score.value();
}
return total;
}
public static Dart canFinish(int points)
{
switch(points)
{
case 2: return new Dart(1, 2);
case 4: return new Dart(2, 2);
case 6: return new Dart(3, 2);
case 8: return new Dart(4, 2);
case 10: return new Dart(5, 2);
case 12: return new Dart(6, 2);
case 14: return new Dart(7, 2);
// etc. etc.
case 40: return new Dart(20, 2);
case 50: return new Dart(25, 2);
}
return null;
}
public static Dart canScore(int points)
{
switch(points)
{
case 1: return new Dart(1, 1);
case 2: return new Dart(2, 1);
case 3: return new Dart(3, 1);
// etc. etc.
case 20: return new Dart(20, 1);
case 21: return new Dart(7, 3);
case 22: return new Dart(11, 2);
//case 23: impossible
case 24: return new Dart(12, 2);
// etc. etc.
case 57: return new Dart(19, 3);
case 60: return new Dart(20, 3);
}
return null;
}
And for completeness, here's the Dart class I created as a helper
private static class Dart{
int number;
int amount;
public Dart(int number, int amount){
this.number = number;
this.amount = amount;
}
public int value()
{
return number * amount;
}
public void estimate(int estimate)
{
Dart temp = canScore(estimate);
if(temp != null){
this.amount = temp.amount;
this.number = temp.number;
} else{
this.number = estimate / 3;
if(number >= 19)
this.number = 19;
this.amount = 3;
}
}
public boolean increment()
{
if(this.amount == 3 && this.number == 20)
return false;
if(this.amount == 0)
this.amount = 1;
this.number++;
if(this.number >= 20)
{
this.number = 20;
this.amount++;
if(this.amount >= 3){
this.amount = 3;
}
}
return true;
}
public String toString()
{
return "["+number+","+amount+"]";
}
}
class RecursiveDartboard {
public Set<Out> outsFor(int target) {
HashSet<Out> outs = new HashSet<>();
for (Score doubleScore : doubles()) {
List<Score> scores = new ArrayList();
scores.add(doubleScore);
outs.addAll(recursiveOutsFor(target, scores)
.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(toList())
);
}
return outs;
}
private List<Optional<Out>> recursiveOutsFor(int target, List<Score> scores) {
List<Optional<Out>> outs = new ArrayList<>();
Out possibleOut = new Out(scores);
if (possibleOut.target() == target) {
outs.add(of(possibleOut));
} else if (scores.size() == 3) {
outs.add(empty());
} else {
for (Score score : allPossibleScores()) {
List<Score> nextScores = new ArrayList<>();
nextScores.addAll(scores);
nextScores.add(score);
outs.addAll(recursiveOutsFor(target, nextScores));
}
}
return outs;
}
}

Categories

Resources