What are good practices of making code faster? - java

Problem statement: Sherlock and the Valid String
This code passes all tests for correctness, 15/20, however, some tests are failing because of the time limits.
What are good practices about making code faster? How can for loops be avoided?
static String isValid(String s) {
String yesOrNo;
//Step 1: count the frequency of each char and out in a map <char, number>
Map<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
int count = 0;
for (int c = 0; c < s.length(); c++) {
if (s.charAt(c) == s.charAt(i))
count++;
}
map.put(s.charAt(i), count);
}
//Step2: add all the numbers of occurrences of each char into a list
List<Integer> values = new ArrayList<>();
for (Map.Entry<Character, Integer> kv : map.entrySet()
) {
values.add(kv.getValue());
}
//Step 3: find the benchmark number
Map<Integer, Integer> occurPairs = new TreeMap<>();
for (int i = 0; i < values.size(); i++) {
occurPairs.put(values.get(i), Collections.frequency(values, values.get(i)));
}
Map.Entry<Integer, Integer> popVal = Collections.max(occurPairs.entrySet(), Map.Entry.comparingByValue());
Map.Entry<Integer, Integer> smallest = Collections.min(occurPairs.entrySet(), Map.Entry.comparingByValue());
//Step 4: compare each value with the benchmark
int numOfWrong = 0;
for (Integer value : values) {
if (!value.equals(popVal.getKey()))
numOfWrong += Math.abs(popVal.getKey() - value);
}
if (occurPairs.size() == 2 && smallest.getValue() == 1 && smallest.getKey() == 1)
yesOrNo = "YES";
else if (numOfWrong > 1)
yesOrNo = "NO";
else
yesOrNo = "YES";
System.out.println(yesOrNo);
return yesOrNo;
}

I won't tell you outright what's wrong but I'll give you a conceptual framework by which you can analyze your code.
Think about the number of steps you perform per letter in the input string.
If you perform a fixed number of steps then you'll get linear scaling. Let's say you do three steps per letter. If there are n letters and you perform 3n operations, you're in good shape.
However, if you perform n operations per letter then you'll get quadratic scaling with n² or 3n² or operations in total. (The constant in front is not important. It's the exponent that matters.) Let's say you have 1,000 letters. 3n scaling would mean 3 thousand operations. 3n² scaling would mean 3 million.
Quadratic basically means "doesn't scale". Instead of scaling in proportion to the input length, quadratic algorithms blow up when the input gets large. They work fine for normal workloads but fall apart when under pressure. Hacker Rank is very likely throwing really long input strings at your algorithm to detect quadratic blowup.
I talked about n above. In Java lingo n is s.length(). Can you spot the quadratic step in your code where you are performing s.length() * s.length() operations?
Yes, in step 1 I'm iterating twice over the s to count the frequency of each char.
That's right. Good. Now, how could you do step 1 in a single pass?
Think about how you'd do it on paper. You wouldn't scan the string over and over and over and over and over, right? You'd just look at each letter once and keep a running count of all the letters as you go. You'd probably have a table with letters and tally marks like:
A ||||
B
C |
D ||
E |||||||
F
...
Do the same in code and it'll cut the n² down to n.

In addition to #JohnKugelman answer, here is what you can do.
First of all, you are going through your string twice (with inner loop) to count the occurrences which is O(n^2). You already found a O(n) solution for that
Map<Character, Integer> occurences = new HashMap<>();
s.chars()
.forEach(e-> occurences.put((char)e, occurences.getOrDefault((char)e, 0) + 1));
Now we need to find a simple iteration to find out the answer.
Here are what we know about the "YES" cases.
All of the letters have same frequency e.g: aabbccddeeff
All of the letters have same frequency but a single letter which occurs 1 time more. e.g: aabbccddd
All of the letters have same frequency but a single letter with 1 occurrence. e.g: aaaabbbbcccce
So what we need to do is to go through values of our map and count the number of occurrences.
First, let's get our iterator
Iterator<Map.Entry<Character, Integer>> iterator = occurences
.entrySet()
.iterator();
Then choose first number as "benchmark" and define a variable and a count for first different value
int benchmark = iterator.next().getValue();
int benchmarkCount = 1;
int firstDifferent = -1;
int differentCount = 0;
Iterate through the numbers
while(iterator.hasNext()) {
int next = iterator.next().getValue();
if (next == benchmark) { // if the next number is same
benchmarkCount++; // just update our count
} else { // if it is different
// if we haven't found a different one yet or it is the same different value from earlier
if (firstDifferent == -1 || firstDifferent == next) {
firstDifferent = next;
differentCount++;
}
}
}
Now all we need to do is to analyze our numbers
int size = occurences.size();
if (benchmarkCount == size) return "YES"; // if all of the numbers are the same
if (benchmarkCount == size - 1) { // if we hit only single different
// either the diffent number is 1 or it is greater than our benchmark by value of 1
if (firstDifferent == 1 || firstDifferent - benchmark == 1) {
return "YES";
}
}
// same case with above
if (differentCount == size - 1) {
if (benchmark == 1 || benchmark - firstDifferent == 1) {
return "YES";
}
}
Full solution
static String isValid(String s) {
Map<Character, Integer> occurences = new HashMap<>();
s.chars().forEach(e-> occurences.put((char)e, occurences.getOrDefault((char)e, 0) + 1));
Iterator<Map.Entry<Character, Integer>> iterator = occurences
.entrySet()
.iterator();
int benchmark = iterator.next().getValue();
int benchmarkCount = 1;
int firstDifferent = -1;
int differentCount = 0;
while(iterator.hasNext()) {
int next = iterator.next().getValue();
if (next == benchmark) {
benchmarkCount++;
} else {
if (firstDifferent == -1 || firstDifferent == next) {
firstDifferent = next;
differentCount++;
}
}
}
int size = occurences.size();
if (benchmarkCount == size) return "YES";
if (benchmarkCount == size - 1) {
if (firstDifferent == 1 || firstDifferent - benchmark == 1) {
return "YES";
}
}
if (differentCount == size - 1) {
if (benchmark == 1 || benchmark - firstDifferent== 1) {
return "YES";
}
}
return "NO";
}

I find the other solutions far too complicated. Using the number of different characters (counts.length below) and their minimum frequency (min below), we know that the string length is at least counts.length * min.
When a single character occurs once more than min, we have a string longer by one.
When there are more such characters, the string gets longer than counts.length * min + 1. When any character occurs even more often than min + 1, the string gets longer than counts.length * min + 1, too.
As noted by Bunyamin Coskuner, my solution missed cases like "aabbc". This one works but it's no more as simple as wanted:
static String isValid(String s) {
if (s.isEmpty()) return "YES";
final int[] counts = s.chars()
.mapToObj(c -> c)
.collect(Collectors.groupingBy(c -> c, Collectors.counting()))
.values()
.stream()
.mapToInt(n -> n.intValue())
.toArray();
final int min = Arrays.stream(counts).min().getAsInt();
// Accounts for strings like "aabb" and "aabbb".
if (s.length() <= counts.length * min + 1) return "YES";
// Here, strings can be only valid when the minimum is one, like for "aabbc".
if (min != 1) return "NO";
final int minButOne = Arrays.stream(counts).filter(n -> n>1).min().getAsInt();
return s.length() == (counts.length - 1) * minButOne + 1 ? "YES" : "NO";
}

Related

Find max rating(number) in an array where we cannot skip over 1 or more consecutive number in array

we have an array of ratings, we have to find the max rating in such a away that we cannot skip 1 or more consecutive rating in an arrray
Example-1: {9,-1,-3,-4,-5} output = 9 + -1 + -4 = 4
Explanation: I took 9 the we have to took -1 or -3 we cannot jump to -4 directly as we cannot skip 1 or more consecutive number.
Example-2: {-1,-2,-3,-4,-5} output = -2 + -4 = -6
Example-3: {-3,2,-4,-1,-2,5} output = 2 + -1 + 5 = 6
Example-4: {9,-1,-3,4,5} output = 9 + -1 + 4 + 5 = 17
I tried below code but it is working in case of example: 2,3,4 but not for example 1 similarly failing for other scenario.
static int maximizeRatings(int[] ratings) {
int current = 0;
boolean result = false;
for(int j=0; j<ratings.length;j++){
if(ratings[j]<0){
result = true;
}else{
result = false;
}
}
if(result){
return allnegatine(ratings);
}
for(int i=0; i<ratings.length;i++){
if(i == ratings.length-1){
if(ratings[i] > 0)
current += ratings[i];
}else{
if(ratings[i] >0 && ratings[i+1]>0){
current = ratings[i]+ratings[i+1];
i++;
}
if(ratings[i] > ratings[i+1]){
current += ratings[i];
}else{
current += ratings[i+1];
i++;
}
}
}
return current;
}
private static int allnegatine(int[] ratings) {
int current =0;
for(int i=0; i<ratings.length;i++){
if(ratings.length%2==0){
if(i%2 == 0)
current += ratings[i];
}else{
if(i%2!=0)
current += ratings[i];
}
}
return current;
}
not getting excepted out for some scenarios like example 1 I am getting -6 instead of 4, I am trying to get proper code which will pass all scenarios. Thank you
This is a dynamic programming problem.
Let dp[i] be the max ratings which can be achieved considering only the part of the array that starts at zero, ends at i, and includes ratings[i].
dp[0]=ratings[0]
dp[1]=max(ratings[1],ratings[0]+ratings[1])
dp[i]=max(dp[i-1],dp[i-2])+ratings[i]
Answer: max(dp[n-1],dp[n-2]) where n is the size of the ratings array.
Also you can chose to do away with dp array and maintain 2 variables for dp[i-1] and dp[i-2].
this is typical recursion problem (as long as the input array is reasonably long). You should go thru items and try all possible combinations and then pick the best one.
Because it looks like typical school work I am not sure if I should paste my solution. You should figure it out yourself or at least understand what's going on to be able to implement it yourself next time.
public class RatingService {
public int calculate(List<Integer> input) {
return recursion(input, true, 0);
}
private int recursion(List<Integer> sublist, boolean canSkip, int sum) {
if (sublist.isEmpty()) {
return sum;
}
int skippedSum = Integer.MIN_VALUE;
int notSkippedSum;
Integer integer = sublist.get(0);
if (canSkip) {
skippedSum = recursion(sublist.subList(1, sublist.size()), false, sum);
}
notSkippedSum = recursion(sublist.subList(1, sublist.size()), true, integer + sum);
return skippedSum > notSkippedSum ? skippedSum : notSkippedSum;
}
}
I think you are doing the mistake while checking for all negative numbers in for loop. If the last element in the array is negative then the 'result' variable will be true means that all array is negative but actually its not.
You have to replace the for loop by :
for(int j=0;j<ratings.length;j++){
if(ratings[j]<0){
result=true;
}
else{
result = false;
break;
}
}
It will break the for loop at the index where it founds any positive number i-e: all elements of array are not negative.

Code optimization - BigInteger

The code works correctly until I give it a big value - it takes too much time to execute.
Can you give me some advice how to optimize it?
BigInteger type of n parameter is a must, it's a part of the task ;)
public static String oddity(BigInteger n) {
List<BigInteger> list = new ArrayList<BigInteger>();
String result = null;
for (BigInteger bi = BigInteger.valueOf(1);
bi.compareTo(n) <= 0;
bi = bi.add(BigInteger.ONE)) {
if (n.mod(bi).equals(BigInteger.ZERO))
list.add(bi);
}
if (list.size() % 2 == 0)
result = "even";
else result = "odd";
return result;
}
The purpose of this is to return 'odd' if the number of "n" divisors is odd. Otherwise return 'even'.
Thinking rather than just programming would help a lot. You don't need to find all divisors. You don't even need to count them. All you need is to find out if the count is odd.
But divisors always come in pairs: For every divisor i also n/i is a divisor.
So the count is always even, except when there's a divisor i equal to n/i. Use Guava sqrt ...
As you don't use the list except to get its final size, you could use an integer as a counter, i.e: do n++ instead of list.add(bi).
This is going to save huge amount of memory. Hence save time used to manage its allocation.
// Lambda
long counter = IntStream
.range(1, (int) Math.sqrt(n.longValue())+1)
.filter(i -> n.longValue() % i == 0 && n.longValue() / i == i)
.count();
return (counter % 2 == 0) ? "even" : "odd";
int counter = 0;
for (long i = 1; i <= Math.sqrt(n.longValue()); i++) {
if(n.longValue() % i == 0 && n.longValue()/ i == i){
counter++;
}
}
return (counter % 2 == 0) ? "even" : "odd";

Find number of occurrences of digits from 1 to N without using loop

For example, n=11 means, then the map should have 0-1, 1-4, 2-1, 3-1, 4-1, 5-1, 6-1, 7-1, 8-1, 9-1
public void countDigits(int n, Map map) {
while (n != 0) {
int d = n%10;
n /= 10;
map.put(d,map.get(d)++);
}
return result;
}
Other than the above method.
I want to get all the digit count from 1 to N.
Your code isn't compiles at all. Try replace map.put(d,map.get(d)++); with
Integer tmp = (Integer)map.get(d);
if(tmp == null) tmp = 0;
tmp++;
map.put(d,tmp);

What is the shortest code in java for accepting 2 binary numbers and returning their sum?

I need to add two binary numbers and return the sum. No base conversions are allowed. I know the long method, using arrays. But is there anything shorter ? And by shorter I mean "having smaller code length". Thanks in advance.
In case I was not explicit enough, here is an example:
Input:
1101
11
Output: 10000
The sum of two (binary) integers a and b can be computed as a+b, because all arithmetic is done in binary.
If your input is in human readable strings rather than binary, you can compute their sum in binary using the standard BigInteger class:
import java.math.BigInteger;
String sum(String a, String b) {
return new BigInteger(a, 2).add(new BigInteger(b, 2)).toString(2);
}
Represent the binary numbers as two strings. Reverse the two strings. Then, you can iterate along both strings simultaneously, adding values to three arrays, two which represent the binary digit being added from the strings and the third to represent the carry digit. Create a fourth array representing the answer (you might have to find the limit for how long the answer can possibly be).
fill the answer array by using standard binary adding:
0 + 0 = 0 in the same position,
1 + 0 = 0 + 1 = 1 in the same position,
1 + 1 = 0 in the same position, and carry a 1 to the next position,
1 + 1 + 1 = 1 in the same position, and carry a 1 to the next position.
Reverse the array and you'll have the answer as a binary number.
Here are a couple options, not using any utility methods provided by Java. These don't account for sign (leading +/-) so they only handle whole numbers.
This first method converts the binary strings to integers, adds the integers, then converts the result back to binary. It uses a method-local inner class Convert to avoid duplicating the binaryToInt() code for each of the parameters.
static String binaryAdd1(String binary1, String binary2) {
class Convert {
int binaryToInt(String binary) {
int result = 0;
for (int i = 0; i < binary.length(); i++) {
char c = binary.charAt(i);
result *= 2;
if (c == '1') {
result++;
} else if (c != '0') {
throw new IllegalArgumentException(binary);
}
}
return result;
}
}
final Convert convert = new Convert();
int int1 = convert.binaryToInt(binary1);
int int2 = convert.binaryToInt(binary2);
String result = "";
int temp = int1 + int2;
do {
result = ((temp & 1) == 1 ? '1' : '0') + result;
temp >>= 1;
} while (temp > 0);
return result;
}
This second method uses binary addition logic, as specified by JHaps in his answer, to directly add together the two parameters. No intermediate conversion to integers here.
static String binaryAdd2(String binary1, String binary2) {
final String validDigits = "01";
String binarySum = "";
// pad the binary strings with one more significant digit for carrying
String bin1 = '0' + binary1;
String bin2 = '0' + binary2;
// add them together starting from least significant digit
int index1 = bin1.length() - 1;
int index2 = bin2.length() - 1;
boolean carry = false;
while (index1 >= 0 || index2 >= 0) {
char char1 = bin1.charAt(index1 >= 0 ? index1 : 0);
char char2 = bin2.charAt(index2 >= 0 ? index2 : 0);
if (validDigits.indexOf(char1) < 0)
throw new NumberFormatException(binary1);
if (validDigits.indexOf(char2) < 0)
throw new NumberFormatException(binary2);
if (char1 == char2) {
binarySum = (carry ? '1' : '0') + binarySum;
carry = char1 == '1';
} else {
binarySum = (carry ? '0' : '1') + binarySum;
}
index1--;
index2--;
}
if (binarySum.length() > 1 && binarySum.charAt(0) == '0') {
binarySum = binarySum.substring(1);
}
String result = binarySum.toString();
return result;
}

Building a math game in Java

I am building a math game for java and I'm stuck at this part as per the details of my assignment. The rules are simple: you have to use each number only once and only the 4 numbers that were read from the user to find one equation to obtain 24.
For example, for the numbers 4,7,8,8, a possible solution is: (7-(8/8))*4=24.
Most sets of 4 digits can be used in multiple equations that result in 24. For example the input 2,2,4,7 can be used in multiple ways to obtain 24:
2+2*(4+7) = 24
2+2*(7+4) = 24
(2+2)*7-4 = 24
(2*2)*7-4 = 24
2*(2*7)-4 = 24
There are also combinations of 4 numbers that cannot result into any equation equal with 24. For example 1,1,1,1. In this case, your program should return that there is no possible equation equal with 24.
Note: Although we will enter 4 integers between 1 and 9, we will use doubles to compute all the operations. For example, the numbers 3,3,8,8 can be combined into the formula: 8/(3-8/3) = 24.
Workflow: Your program should read the 4 numbers from the user and output a formula that results in 24. The algorithm should enumerate all the possible orders of 4 numbers, all the possible combinations and all the possible formulas.
Which leads me to 24 permutations of Numbers a,b,c,d and 64 permutations of operators +-/*. How I came to this conclusion was 4^3 4 operators only 3 fill spots in the equation. Except today I am having trouble writing the evaluation method and also accounting for parentases in the equations.
Here is my code:
public static void evaluate(cbar [][] operations , double [][] operands)
{
/*
This is the part that gets me how am I supposed to account
for parentases and bring all these expressions togather to
actually form and equation.
*/
}
This problem presents several challenges. My solution below is about two hundred lines long. It's probably a little longer than the assignment requires because I generalized it to any number of terms. I encourage you to study the algorithm and write your own solution.
The main obstacles we must overcome are the following.
How do we generate permutations without repetition?
How do we build and evaluate arithmetic expressions?
How do we convert the expressions into unique strings?
There are many ways to generate permutations. I chose a recursive approach because it's easy to understand. The main complication is that terms can be repeated, which means that there may be fewer than 4! = 4*3*2*1 permutations. For example, if the terms are 1 1 1 2, there are only four permutations.
To avoid duplicating permutations, we start by sorting the terms. The recursive function finds places for all duplicate terms from left to right without backtracking. For example, once the first 1 has been placed in the array, all the remaining 1 terms are placed to the right of it. But when we get to a 2 term, we can go back to the beginning of the array.
To build arithmetic expressions, we use another recursive function. This function looks at each position between two terms of the permutation, splitting the array into a segment to the left of the position and a segment to the right. It makes a pair of recursive calls to build expressions for the left and right segments. Finally, it joins the resulting child expressions with each of the four arithmetic operators. The base case is when the array is of size one, so it can't be split. This results in a node with no operator and no children, only a value.
Evaluating the expressions by performing arithmetic on double values would be problematic due to the imprecision of floating-point division. For example, 1.0 / 3 = 0.33333..., but 3 * 0.33333... = 0.99999.... This makes it difficult to know for sure that 1 / 3 * 3 = 1 when you're using double values. To avoid these difficulties, I defined a Fraction class. It performs arithmetic operations on fractions and always simplifies the result by means of the greatest common divisor. Division by zero does not result in an error message. Instead, we store the fraction 0/0.
The final piece of the puzzle is converting expressions into strings. We want to make canonical or normalized strings so that we don't repeat ourselves needlessly. For example, we don't want to display 1 + (1 + (1 + 2)) and ((1 + 1) + 1) + 2, since these are essentially the same expression. Instead of showing all possible parenthesizations, we just want to display 1 + 1 + 1 + 2.
We can achieve this by adding parentheses only when necessary. To wit, parentheses are necessary if a node with a higher-priority operator (multiplication or division) is the parent of a node with a lower-priority operator (addition or subtraction). By priority I mean operator precedence, also known as the order of operations. The higher-priority operators bind more tightly than the lower ones. So if a parent node has higher priority than the operator of a child node, it is necessary to parenthesize the child. To ensure that we end up with unique strings, we check them against a hash set before adding them to the result list.
The following program, Equation.java, accepts user input on the command line. The parameters of the game are on the first line of the Equation class. You can modify these to build expressions with more terms, bigger terms, and different target values.
import java.lang.*;
import java.util.*;
import java.io.*;
class Fraction { // Avoids floating-point trouble.
int num, denom;
static int gcd(int a, int b) { // Greatest common divisor.
while (b != 0) {
int t = b;
b = a % b;
a = t;
}
return a;
}
Fraction(int num, int denom) { // Makes a simplified fraction.
if (denom == 0) { // Division by zero results in
this.num = this.denom = 0; // the fraction 0/0. We do not
} else { // throw an error.
int x = Fraction.gcd(num, denom);
this.num = num / x;
this.denom = denom / x;
}
}
Fraction plus(Fraction other) {
return new Fraction(this.num * other.denom + other.num * this.denom,
this.denom * other.denom);
}
Fraction minus(Fraction other) {
return this.plus(new Fraction(-other.num, other.denom));
}
Fraction times(Fraction other) {
return new Fraction(this.num * other.num, this.denom * other.denom);
}
Fraction divide(Fraction other) {
return new Fraction(this.num * other.denom, this.denom * other.num);
}
public String toString() { // Omits the denominator if possible.
if (denom == 1) {
return ""+num;
}
return num+"/"+denom;
}
}
class Expression { // A tree node containing a value and
Fraction value; // optionally an operator and its
String operator; // operands.
Expression left, right;
static int level(String operator) {
if (operator.compareTo("+") == 0 || operator.compareTo("-") == 0) {
return 0; // Returns the priority of evaluation,
} // also known as operator precedence
return 1; // or the order of operations.
}
Expression(int x) { // Simplest case: a whole number.
value = new Fraction(x, 1);
}
Expression(Expression left, String operator, Expression right) {
if (operator == "+") {
value = left.value.plus(right.value);
} else if (operator == "-") {
value = left.value.minus(right.value);
} else if (operator == "*") {
value = left.value.times(right.value);
} else if (operator == "/") {
value = left.value.divide(right.value);
}
this.operator = operator;
this.left = left;
this.right = right;
}
public String toString() { // Returns a normalized expression,
if (operator == null) { // inserting parentheses only where
return value.toString(); // necessary to avoid ambiguity.
}
int level = Expression.level(operator);
String a = left.toString(), aOp = left.operator,
b = right.toString(), bOp = right.operator;
if (aOp != null && Expression.level(aOp) < level) {
a = "("+a+")"; // Parenthesize the child only if its
} // priority is lower than the parent's.
if (bOp != null && Expression.level(bOp) < level) {
b = "("+b+")";
}
return a + " " + operator + " " + b;
}
}
public class Equation {
// These are the parameters of the game.
static int need = 4, min = 1, max = 9, target = 24;
int[] terms, permutation;
boolean[] used;
ArrayList<String> wins = new ArrayList<String>();
Set<String> winSet = new HashSet<String>();
String[] operators = {"+", "-", "*", "/"};
// Recursively break up the terms into left and right
// portions, joining them with one of the four operators.
ArrayList<Expression> make(int left, int right) {
ArrayList<Expression> result = new ArrayList<Expression>();
if (left+1 == right) {
result.add(new Expression(permutation[left]));
} else {
for (int i = left+1; i < right; ++i) {
ArrayList<Expression> leftSide = make(left, i);
ArrayList<Expression> rightSide = make(i, right);
for (int j = 0; j < leftSide.size(); ++j) {
for (int k = 0; k < rightSide.size(); ++k) {
for (int p = 0; p < operators.length; ++p) {
result.add(new Expression(leftSide.get(j),
operators[p],
rightSide.get(k)));
}
}
}
}
}
return result;
}
// Given a permutation of terms, form all possible arithmetic
// expressions. Inspect the results and save those that
// have the target value.
void formulate() {
ArrayList<Expression> expressions = make(0, terms.length);
for (int i = 0; i < expressions.size(); ++i) {
Expression expression = expressions.get(i);
Fraction value = expression.value;
if (value.num == target && value.denom == 1) {
String s = expressions.get(i).toString();
if (!winSet.contains(s)) {// Check to see if an expression
wins.add(s); // with the same normalized string
winSet.add(s); // representation was saved earlier.
}
}
}
}
// Permutes terms without duplication. Requires the terms to
// be sorted. Notice how we check the next term to see if
// it's the same. If it is, we don't return to the beginning
// of the array.
void permute(int termIx, int pos) {
if (pos == terms.length) {
return;
}
if (!used[pos]) {
permutation[pos] = terms[termIx];
if (termIx+1 == terms.length) {
formulate();
} else {
used[pos] = true;
if (terms[termIx+1] == terms[termIx]) {
permute(termIx+1, pos+1);
} else {
permute(termIx+1, 0);
}
used[pos] = false;
}
}
permute(termIx, pos+1);
}
// Start the permutation process, count the end results, display them.
void solve(int[] terms) {
this.terms = terms; // We must sort the terms in order for
Arrays.sort(terms); // the permute() function to work.
permutation = new int[terms.length];
used = new boolean[terms.length];
permute(0, 0);
if (wins.size() == 0) {
System.out.println("There are no feasible expressions.");
} else if (wins.size() == 1) {
System.out.println("There is one feasible expression:");
} else {
System.out.println("There are "+wins.size()+" feasible expressions:");
}
for (int i = 0; i < wins.size(); ++i) {
System.out.println(wins.get(i) + " = " + target);
}
}
// Get user input from the command line and check its validity.
public static void main(String[] args) {
if (args.length != need) {
System.out.println("must specify "+need+" digits");
return;
}
int digits[] = new int[need];
for (int i = 0; i < need; ++i) {
try {
digits[i] = Integer.parseInt(args[i]);
} catch (NumberFormatException e) {
System.out.println("\""+args[i]+"\" is not an integer");
return;
}
if (digits[i] < min || digits[i] > max) {
System.out.println(digits[i]+" is outside the range ["+
min+", "+max+"]");
return;
}
}
(new Equation()).solve(digits);
}
}
I would recommend you to use a tree structure to store the equation, i.e. a syntactic tree in which the root represents and operator having two children representing the operands and so on recursively. You would probably get a cleaner code doing it like that, because then you won't need to generate the combinations of operands "by hand", but you can make a code which picks every operand from a 1-dimensional char[] operands = new char[] {'+','-','*','/'} array.
If you don't want to use a syntactic tree or think it's not necessary for your use case you can always try to find a different way to make the code to pick operands from the 1-dimensional array and store them into a different data structure. But I would especially avoid writing all the combinations as you are doing. It does not look very easy to maintain.
I have fixed the similar puzzle with below code.
public static boolean game24Points(int[] operands) {
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("javascript");
char[] operations = new char[] { '+', '-', '*', '/' };
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
for (int k = 0; k < 4; k++) {
try {
String exp = "" + operands[0] + operations[i] + operands[1] + operations[j]
+ operands[2] + operations[k] + operands[3];
String res = engine.eval(exp).toString();
if (Double.valueOf(res).intValue() == 24) {
System.out.println(exp);
return true;
}
} catch (ScriptException e) {
return false;
}
}
}
}
return false;
}
Here are testcases
public void testCase01() {
int[] operands = { 7, 2, 1, 10 };
assertEquals(true, Demo.game24Points(operands));
}
public void testCase02() {
int[] operands = { 1, 2, 3, 4 };
assertEquals(true, Demo.game24Points(operands));
}
public void testCase03() {
int[] operands1 = { 5, 7, 12, 12 };
assertEquals(true, Demo.game24Points(operands1));
int[] operands = { 10, 3, 3, 23 };
assertEquals(true, Demo.game24Points(operands));
}

Categories