I am trying to create a function that does the following:
Assuming that the code input is "(a 1 2 (b 3 4 5 (c 6) |7) 8 9)"
where the pipe | symbol is the position of the cursor,
the function returns:
a String "b 3 4 5 (c 6) 7" representing the code that is in the scope of the cursor
an int 8 representing the start index of the string relative to the input
an int 30 representing the end index of the string relative to the input
I already have working code that returns exactly that. However, the problem lies in ignoring comments, while keeping track of context (e.g. String literals, my own literal delimiters, etc).
Here is the code which keeps track of context:
public static void applyContext(Context context, String s, String snext, String sprev) {
if (s.equals("\"")) {
if (context.context == Context.Contexts.MAIN) {
context.context = Context.Contexts.STRING;
context.stringDelimiterIsADoubleQuote = true;
} else if (context.context == Context.Contexts.STRING && context.stringDelimiterIsADoubleQuote && !sprev.equals("\\"))
context.context = Context.Contexts.MAIN;
} else if (s.equals("\'")) {
if (context.context == Context.Contexts.MAIN) {
context.context = Context.Contexts.STRING;
context.stringDelimiterIsADoubleQuote = false;
} else if (context.context == Context.Contexts.STRING && !context.stringDelimiterIsADoubleQuote && !sprev.equals("\""))
context.context = Context.Contexts.MAIN;
} else if (s.equals("/") && snext.equals("/")) {
if (context.context == Context.Contexts.MAIN)
context.context = Context.Contexts.COMMENT;
} else if (s.equals("\n")) {
if(context.context == Context.Contexts.COMMENT)
context.context = Context.Contexts.MAIN;
}
else if (s.equals("\\")) {
if(context.context == Context.Contexts.MAIN)
context.context = Context.Contexts.PATTERN;
else if(context.context == Context.Contexts.PATTERN)
context.context = Context.Contexts.MAIN;
}
}
Firstly, I'll be using the function above like so:
String sampleCode = "(a b "cdef" g \c4 bb2 eb4 g4v0.75\)";
Context c = new Context(Context.Contexts.MAIN);
for(int i = 0; i < sampleCode.length(); i++) {
String s = String.valueOf(sampleCode.charAt(i));
String snext = *nullcheck* ? String.valueOf(sampleCode.charAt(i + 1)) : "";
String sprev = *nullcheck* ? String.valueOf(sampleCode.charAt(i - 1)) : "";
applyContext(c, s, snext, sprev);
if(c.context == blahlbah) doBlah();
}
Second, I'll be using this both forwards an backwards, as the current method of doing the function stated at the top of the description is (in pseudocode) this:
function returnCodeInScopeOfCursor(theWholeCode::String, cursorIndex::int) {
var depthOfCodeAtCursorPosition::int = getDepth(theWholeCode, cursorIndex);
Context c = new Context(getContextAt(theWholeCode, cursorIndex));
var currDepth::int = depthOfCodeAtCursorPosition;
var startIndex::int, endIndex::int;
for(i = cursorIndex; i >= 0; i--) {//going backwards
s = .....
snext = ......
sprev = ......
applyContext(c, s, snext, sprev);
if(c.context == Context.MAIN) {
if s = "(" then currDepth --;
if s = ")" then currDepth ++;
}
when currDepth < depthOfCodeAtCursorPosition
startIndex = i + 1;
break;
}
currDepth = depthOfCodeAtCursorPosition;//reset
for(i = cursorIndex; i < theWholeCode.length; i++) {//going forwards
s = ...
snex......
sprev.....
applyContext(c, s, snext, sprev);
if(c.context == Context.MAIN) {
if s = "(" then currDepth ++;
if s = ")" then currDepth --;
}
when currDepth < depthOfCodeAtCursorPosition
endIndex = i - 1;
break;
}
var returnedStr = theWholeCode->from startIndex->to endIndex
return new IndexedCode(returnedStr, startIndex, endIndex);
As you can see, this function would work both forwards and in reverse. Or at least most of it. The only problem is that if I were to use this function backwards, the proper scanning of comments (denoted by the standard ECMA double slash "//") goes haywire.
If I were to create a separate function for reverse context application and check every line recursively for a double slash, then making everything after that '//' a COMMENT (or in the direction of the function's usage, everything before that //), it will take way too much processing time as I want to use this as a livecoding environment for music.
Also, removing the comments before trying to do that returnCodeInScopeOfCursor method may not be feasible... as I need to keep track of the indexes of the code and what not. If I were to remove the comments, there will be a big mess with all the code positions and keeping track of where did I remove what exactly and how many characters etc....
The text area input GUI I'm working with (RichTextFX) does not support Line-Char tracking, so everything is tracked using char index only, hence the problems...
So... I'm utterly perplexed as with what to do with my current code. Any help, suggestions, advice etc... will be greatly appreciated.
Could you pre-transform comments from // This is a comment<CR> to { This is a comment}<CR> you then have a language you can walk backwards and forwards.
Apply this transform on the way in and reverse it on the way out and all should be well. Notice we are replacing //... with {...} so all charaqcter offsets are retained.
Anyways, after a little experimenting with OldCurmudgeon's idea, I came up with a separate function to get context of the code in a reverse direction.
public static void applyContextBackwards(Context context, String entireCode, int caretPos) {
String s = String.valueOf(entireCode.charAt(caretPos));
//So far this is not used
//String snext = caretPos + 1 < entireCode.length() ? String.valueOf(entireCode.charAt(caretPos + 1)) : "";
String sprev = caretPos - 1 >= 0 ? String.valueOf(entireCode.charAt(caretPos - 1)) : "";
//Check for all the flags and what not...
if(context.commentedCharsLeft > 0) {
context.commentedCharsLeft--;
if(context.commentedCharsLeft == 0)
context.context = Context.Contexts.MAIN;//The comment is over
}
if(context.expectingEndOfString){
context.context = Context.Contexts.MAIN;
context.expectingEndOfString = false;
}
if(context.expectingEndOfPattern) {
context.context = Context.Contexts.MAIN;
context.expectingEndOfPattern = false;
}
//After all the flags are cleared, do this
if(context.commentedCharsLeft == 0) {
if (s.equals("\"")) {
if (context.context == Context.Contexts.MAIN) {
context.context = Context.Contexts.STRING;
context.stringDelimiterIsADoubleQuote = true;
} else if (context.context == Context.Contexts.STRING && context.stringDelimiterIsADoubleQuote && !sprev.equals("\\"))
context.expectingEndOfString = true;//Change the next char to a MAIN, cuz this one's still part of the string
} else if (s.equals("\'")) {
if (context.context == Context.Contexts.MAIN) {
context.context = Context.Contexts.STRING;
context.stringDelimiterIsADoubleQuote = false;
} else if (context.context == Context.Contexts.STRING && !context.stringDelimiterIsADoubleQuote && !sprev.equals("\""))
context.expectingEndOfString = true;//Change the next char to a MAIN, cuz this one's still part of the string
} else if (s.equals("\n")) {
int earliestOccuranceOfSingleLineCommentDelimiterAsDistanceFromThisNewLine = -1;//-1 for no comments
//Loop until the next \n is found. In the process, determine location of comment if any
for(int i = caretPos; i >= 0; i--) {
String curr = String.valueOf(entireCode.charAt(i));
String prev = i - 1 >= 0 ? String.valueOf(entireCode.charAt(i - 1)) : "";
if(curr.equals("\n"))
break;//Line has been scanned through
if(curr.equals("/") && prev.equals("/"))
earliestOccuranceOfSingleLineCommentDelimiterAsDistanceFromThisNewLine = caretPos - i;
}
//Set the comment context flag
//If no comments, -1 + 1 will be 0 and will be treated as no comments.
context.commentedCharsLeft = earliestOccuranceOfSingleLineCommentDelimiterAsDistanceFromThisNewLine + 1;
if(earliestOccuranceOfSingleLineCommentDelimiterAsDistanceFromThisNewLine > 0) {
context.context = Context.Contexts.COMMENT;
}
} else if (s.equals("\\")) {
if (context.context == Context.Contexts.MAIN)
context.context = Context.Contexts.PATTERN;
else if (context.context == Context.Contexts.PATTERN)
context.expectingEndOfPattern = true;//Change the next char to a MAIN cuz this one's still part of the Pattern
}
}
}
Related
I am trying to solve this CSES problem: Grid Paths. You are given a string of length 48, and you have to find the amount of paths such that you traverse all of the grid and end up at the lower left corner.
I believe I have pruned the search to the best of my ability, as according to this book: CP Handbook (Look in the pruning the search category), the best optimization for this type of problem is to prevent your path from closing yourself off, and I have already implemented this. The time limits for this specific problem are tight, and although I have basically solved this problem, I am still failing 1-2 test cases because my solution takes around 1.01 seconds instead of being below the 1 second time limit.
Finally, I just wanted to know if there were any cool micro-optimizations I could use to marginally enhance the speed of my java code, so I could actually pass all of the test cases for this problem.
import java.io.*;
public class GridPaths {
public static class FastIO {
InputStream dis;
byte[] buffer = new byte[1 << 17];
int pointer = 0;
public FastIO(String fileName) throws Exception {
dis = new FileInputStream(fileName);
}
public FastIO(InputStream is) {
dis = is;
}
int nextInt() throws Exception {
int ret = 0;
byte b;
do {
b = nextByte();
} while (b <= ' ');
boolean negative = false;
if (b == '-') {
negative = true;
b = nextByte();
}
while (b >= '0' && b <= '9') {
ret = 10 * ret + b - '0';
b = nextByte();
}
return (negative) ? -ret : ret;
}
long nextLong() throws Exception {
long ret = 0;
byte b;
do {
b = nextByte();
} while (b <= ' ');
boolean negative = false;
if (b == '-') {
negative = true;
b = nextByte();
}
while (b >= '0' && b <= '9') {
ret = 10 * ret + b - '0';
b = nextByte();
}
return (negative) ? -ret : ret;
}
Integer[] readArray(int n) throws Exception {
Integer[] a = new Integer[n];
for (int i = 0; i < n; i++) a[i] = nextInt();
return a;
}
byte nextByte() throws Exception {
if (pointer == buffer.length) {
dis.read(buffer, 0, buffer.length);
pointer = 0;
}
return buffer[pointer++];
}
String next() throws Exception {
StringBuilder ret = new StringBuilder();
byte b;
do {
b = nextByte();
} while (b <= ' ');
while (b > ' ') {
ret.appendCodePoint(b);
b = nextByte();
}
return ret.toString();
}
}
static char[] board;
static boolean[][] visited = new boolean[7][7];
static int ans = 0;
public static boolean works(int i, int j) {
//makes sure that current spot is on the 7x7 grid and is not visited
return (i >= 0 && i<=6 && j>=0 && j<=6 && !visited[i][j]);
}
public static void solve(int i, int j, int steps) {
if (i == 6 && j == 0) {
if (steps == 48) ans++; //all spots of the grid have to be visited in order to be counted as part of the answer
return;
}
visited[i][j] = true;
//you are given ? characters in the input string, and those mean that you have to try out all 4 combinations (U,D,L,R)
if (board[steps] == '?' || board[steps] == 'L') {
//second condition of the second if statement checks if the spot directly ahead of the current spot is blocked, and if it is, the left and right spots cannot both be unvisited or else you will not continue searching
if (works(i,j-1) && !(!works(i,j-2) && works(i+1,j-1) && works(i-1,j-1))) {
solve(i, j - 1, steps + 1);
}
}
if (board[steps] == '?' || board[steps] == 'R') {
if (works(i,j+1) && !(!works(i,j+2) && works(i+1,j+1) && works(i-1,j+1))) {
solve(i, j + 1, steps + 1);
}
}
if (board[steps] == '?' || board[steps] == 'U') {
if (works(i-1,j) && !(!works(i-2,j) && works(i-1,j+1) && works(i-1,j-1))) {
solve(i - 1, j, steps + 1);
}
}
if (board[steps] == '?' || board[steps] == 'D') {
if (works(i+1,j) && !(!works(i+2,j) && works(i+1,j+1) && works(i+1,j-1))) {
solve(i + 1, j, steps + 1);
}
}
visited[i][j] = false;
}
public static void main(String[] args) throws Exception {
FastIO in = new FastIO(System.in);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
board = in.next().toCharArray();
solve(0,0,0);
out.println(ans);
out.close();
}
}
Note: I am already using one of the fastest, if not the fastest, ways to receive input in Java, so I do not believe I can actually improve upon that.
I've been playing around with this. In addition to using a standard mechanism for reading the input file (which I suggested in a comment), you can gain a little time in the search alg itself by doing two things:
Break the case board[steps] == '?' off from the other cases. So check for board[steps] == '?' first, and just try all four directions in that case. Otherwise (the else case for if (board[steps] == '?'), just check for U/D/L/R. Since for most steps, the character will be '?', you save having to make the U/D/L/R tests most of the time.
Look up the character to be tested once, with c = board[steps],and then use c in each test instead of board[steps].
Doing these two things saved about 5% it seems. I was doing 100 reps of the solve and timing with System.currentTimeMillis(). I know there are more accurate ways of timing, but this was good enough to see a definite improvement even though the times jumped around quite a bit trial to trial. The best I ever saw in each case was 3600 millis for 100 iterations as originally written vs 3400 millis with the improvements.
My guess is that it's mostly the first change that matters. I'd expect the compiler to be doing the second already, but I didn't try the two optimizations independently.
I also solved this problem in java (AC), and here is how I did it
public static char[] defaultPath;
public static boolean[][] isUsed;
public static int counter = 0;
public static void solve(int indexChar, int indexRow, int indexColumn) {
if (indexRow == 8 && indexColumn == 2) {
if (indexChar == 48) {
counter++;
}
}else {
// Find correct way: 'D', 'U', 'L', 'R'
char correctWay = '?';
// (1) (1)
// 0 1 or 1 0
// 1 1
if ((isUsed[indexRow+1][indexColumn+1] || isUsed[indexRow+1][indexColumn-1])
&& isUsed[indexRow+2][indexColumn] && !isUsed[indexRow+1][indexColumn]) {
correctWay = 'D';
}
// 1 1
// 0 1 or 1 0
// (1) (1)
else if ((isUsed[indexRow-1][indexColumn+1] || isUsed[indexRow-1][indexColumn-1])
&& !isUsed[indexRow-1][indexColumn] && isUsed[indexRow-2][indexColumn]) {
correctWay = 'U';
}
// 1 0 (1) or 1
// 1 1 0 (1)
else if ((isUsed[indexRow+1][indexColumn-1] || isUsed[indexRow-1][indexColumn-1])
&& !isUsed[indexRow][indexColumn-1] && isUsed[indexRow][indexColumn-2]) {
correctWay = 'L';
}
//(1) 0 1 or 1
// 1 (1) 0 1
else if ((isUsed[indexRow+1][indexColumn+1] || isUsed[indexRow-1][indexColumn+1])
&& !isUsed[indexRow][indexColumn+1] && isUsed[indexRow][indexColumn+2]) {
correctWay = 'R';
}
// Check input path (default path)
char c = defaultPath[indexChar];
if (c == '?') {
if (correctWay == '?') {
// 'D'
if (!isUsed[indexRow+1][indexColumn]) {
isUsed[indexRow+1][indexColumn] = true;
solve(indexChar+1, indexRow+1, indexColumn);
isUsed[indexRow+1][indexColumn] = false;
}
// 'U'
if (!isUsed[indexRow-1][indexColumn]) {
isUsed[indexRow-1][indexColumn] = true;
solve(indexChar+1, indexRow-1, indexColumn);
isUsed[indexRow-1][indexColumn] = false;
}
// 'L'
if (!isUsed[indexRow][indexColumn-1]) {
isUsed[indexRow][indexColumn-1] = true;
solve(indexChar+1, indexRow, indexColumn-1);
isUsed[indexRow][indexColumn-1] = false;
}
// 'R'
if (!isUsed[indexRow][indexColumn+1]) {
isUsed[indexRow][indexColumn+1] = true;
solve(indexChar+1, indexRow, indexColumn+1);
isUsed[indexRow][indexColumn+1] = false;
}
}else {
if (correctWay == 'D') {
isUsed[indexRow+1][indexColumn] = true;
solve(indexChar+1, indexRow+1, indexColumn);
isUsed[indexRow+1][indexColumn] = false;
}else if (correctWay == 'U') {
isUsed[indexRow-1][indexColumn] = true;
solve(indexChar+1, indexRow-1, indexColumn);
isUsed[indexRow-1][indexColumn] = false;
}else if (correctWay == 'L') {
isUsed[indexRow][indexColumn-1] = true;
solve(indexChar+1, indexRow, indexColumn-1);
isUsed[indexRow][indexColumn-1] = false;
}else if (correctWay == 'R') {
isUsed[indexRow][indexColumn+1] = true;
solve(indexChar+1, indexRow, indexColumn+1);
isUsed[indexRow][indexColumn+1] = false;
}
}
}else {
if (c == correctWay || correctWay == '?') {
if (c == 'D' && !isUsed[indexRow+1][indexColumn]) {
isUsed[indexRow+1][indexColumn] = true;
solve(indexChar+1, indexRow+1, indexColumn);
isUsed[indexRow+1][indexColumn] = false;
}else if (c == 'U' && !isUsed[indexRow-1][indexColumn]) {
isUsed[indexRow-1][indexColumn] = true;
solve(indexChar+1, indexRow-1, indexColumn);
isUsed[indexRow-1][indexColumn] = false;
}else if (c == 'L' && !isUsed[indexRow][indexColumn-1]) {
isUsed[indexRow][indexColumn-1] = true;
solve(indexChar+1, indexRow, indexColumn-1);
isUsed[indexRow][indexColumn-1] = false;
}else if (c == 'R' && !isUsed[indexRow][indexColumn+1]) {
isUsed[indexRow][indexColumn+1] = true;
solve(indexChar+1, indexRow, indexColumn+1);
isUsed[indexRow][indexColumn+1] = false;
}
}
}
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
defaultPath = scanner.next().toCharArray();
isUsed = new boolean[11][11];
for (int i = 0; i < 11; i++) {
isUsed[0][i] = true;
isUsed[1][i] = true;
isUsed[9][i] = true;
isUsed[10][i] = true;
isUsed[i][0] = true;
isUsed[i][1] = true;
isUsed[i][9] = true;
isUsed[i][10] = true;
}
isUsed[2][2] = true;
isUsed[8][1] = false;
isUsed[9][2] = false;
solve(0, 2, 2);
System.out.println(counter);
scanner.close();
}
At a job interview, you are challenged to write an algorithm to check if a given string, s, can be formed from two other strings, part1 and part2.
The restriction is that the characters in part1 and part2 are in the same order as in s.
The interviewer gives you the following example and tells you to figure out the rest from the given test cases.
For example:
'codewars' is a merge from 'cdw' and 'oears':
See: https://www.codewars.com/kata/merged-string-checker/train/java
This is my java code,but couldn't pass all test.Excuse me, where is wrong?Thank you!
public static boolean isMerge(String s, String part1, String part2) {
s = s.replace(" ","");
part1 = part1.replace(" ","");
part2 = part2.replace(" ","");
int index1 = 0;
int index2 = 0;
char[] cp1 = part1.toCharArray();
char[] cp2 = part2.toCharArray();
for (int i = 0; i < s.length(); ) {
char is = s.charAt(i);
if (index1 < cp1.length && cp1[index1] == is) {
index1++;
i++;
continue;
}
if (index2 < cp2.length && cp2[index2] == is) {
index2++;
i++;
continue;
}
return false;
}
return s.length() == index1 + index2;
}
The problem is that if for example you have:
S = cdwzzzcdw
part1 = cdw
part2 = cdwzzz
What happens is that first you try to match the whole part1 with the beginning of S, and part won't be able to match the rest of the string. But if you have considered part2 first, the it would be properly done.
So, the bottom line is that you should consider all the possibilites. I assume a dyanmic programming approach would work. Here is a pseudocode in C++:
bool rec(idx1, idx2, idxS ){
if(idx1 == part1.length && idx2 == part2.length){
if(idxS == S.length) return true; //everything matched
return false; //S has remaining
}
if(idxS == S.length){ //length part1 + part2 is more than S
return false;
}
if(mark[idx1][idx2][idxS] == true) //computed before
return dp[idx1][idx2][idxS];
mark[idx1][idx2][idxS] = true;
dp[idx1][idx2][idxS] = false;
if(idx1 < part1.length && part1[idx1] == S[idxS]){
if(rec(idx1 + 1, idx2, idxS + 1) == true) {
dp[idx1][idx2][idxS] = true;
}
}
if(idx2 < part2.length && part2[idx2] == S[idxS]){
if(rec(idx1, idx2 + 1, idxS + 1) == true){
dp[idx1][idx2][idxS] = true;
}
}
return dp[idx1][idx2][idxS];
}
All i need this method to do is take a string lets say "aaxbb" and it will return true because there is an 'aa' and a 'bb',
If the string given is length = 0 or length = 1 it should fail.
The issue i'm having is that which i do not know.. i know that in my terminal after hasAdjacentPair's first test case Pass's, i get a blinking curser meaning that somehwere in this method i'm not kicking out of one of my loops for it to continue to check the string for any more adjacent pairs
the first test case passes while its an empty string "" = because it returned false
the second test case passes while its "a" = because it returned false
We are also not allowed to use Arrays :(
public boolean hasAdjacentPair(String str)
{
boolean result = false;
if (str.length() == 0)
{
result = false;
}
if (str.length() == 1)
{
result = false;
}
while (str.length() != 0)
{
for (int i = 0; i < str.length() - 1; ++i)
{
char adjChar = str.charAt(i);
char nextAdjChar = str.charAt(i + 1);
if (adjChar == nextAdjChar)
{
result = true;
}
}
}
return result;
}
Changed my while loop while (str.length() != 0) to while (str.length() != 0 && str.length() != 1) this enabled test 2 to work
EDIT 2 : After i completely took out the while (str.length() != 0) All 5 of my test cases pass :) so i guess it was just that?
while (str.length() != 0)
is always true and loop never end. Instead of if..if..while structure use either switch on length of string or if-else.
You could try something like this
if (str.length() == 0)
{
result = false;
}
else if (str.length() == 1)
{
result = false;
}
else
{
for (int i = 0; i < str.length() - 1; ++i)
{
char adjChar = str.charAt(i);
char nextAdjChar = str.charAt(i + 1);
if (adjChar == nextAdjChar)
{
result = true;
break;
}
}
If you checking for atleast one pair make use of break statement as you already know that you got the result
try this alternative:
boolean c = false;
//Your other code
while (str.length() != 0 && c == false)
{
for (int i = 0; i < str.length() - 1; ++i)
{
char adjChar = str.charAt(i);
char nextAdjChar = str.charAt(i + 1);
if (adjChar == nextAdjChar)
{
result = true;
}
}
c = true;
}
//Your other code
Is there a reason you have a while loop checking for the str length? The str is not modified in the loop.
What you most likely want is an if, else if, else structure.
You can most likely return true within the adjChar == nextAdjChar if statement and return false at the end of your function.
So is there a way to simplify this to make is smaller in anyway?
else if(selectedCards.size() == 3
&& cardAt(selectedCards.get(0)).pointValue() + cardAt(selectedCards.get(1)).pointValue() + cardAt(selectedCards.get(2)).pointValue() == 0
&& !cardAt(selectedCards.get(0)).rank().equals(cardAt(selectedCards.get(1)).rank())
&& !cardAt(selectedCards.get(0)).rank().equals(cardAt(selectedCards.get(2)).rank())
&& !cardAt(selectedCards.get(1)).rank().equals(cardAt(selectedCards.get(2)).rank()))
From what I can see you're trying to test if the 3 cards have different ranks. An easier way to test this is to put them into a Set and see if the set size is same as selected set. This scales to any number of selected cards...
public boolean differentRanks(List<Integer> selectedCards) {
Set<Integer> ranks = new HashSet<Integer>();
for (int card : selectedCards) {
ranks.add(cardAt(card).rank());
}
return ranks.size() == selectedCards.size();
}
I'd also create a method to total the points for the selected cards...
public int sum(List<Integer> selectedCards) {
int total;
for (int card : selectedCards) {
total += cardAt(card).pointValue();
}
return total;
}
So the condition would end up
} else if (selectedCards.size() == 3 && sum(selectedCards) == 0 &&
differentRanks(selectedCards) {
This would be one option:
else if(selectedCards.size() == 3
&& cardAt(selectedCards.get(0)).pointValue() + cardAt(selectedCards.get(1)).pointValue() + cardAt(selectedCards.get(2)).pointValue() == 0
&& !(cardAt(selectedCards.get(0)).rank().equals(cardAt(selectedCards.get(1)).rank())).equals(cardAt(selectedCards.get(2)).rank()) )
To make more readable this condition you could do something like this:
//here you extract the values you need only once and use them in your condition block below
int cardsSize = selectedCards.size();
int pointValue0 = cardsSize == 3 ? cardAt(selectedCards.get(0)).pointValue() : 0;
int pointValue1 = cardsSize == 3 ? cardAt(selectedCards.get(1)).pointValue() : 0;
int pointValue2 = cardsSize == 3 ? cardAt(selectedCards.get(2)).pointValue() : 0;
bool rankEquals = CompareRanks(cardAt(selectedCards.get(0)),cardAt(selectedCards.get(1)),cardAt(selectedCards.get(2));
if (<condition>) {
//block of sentences
} else if (cardsSize == 3 && (pointValue0 + pointValue1 + pointValue2) == 0 && !rankEquals )
I'm suggesting the creation of a function called "CompareRanks" where you receive 3 different objects (result of "cardAt") and you get the rank in there and compare if the values are the same or not.
This option leads you to more lines of code but is cleaner and more readable for any person besides you.
In my opinion most readable:
else if(selectedCards.size() == 3 && checkRanks(selectedCards))
{
//...
}
//...
private boolean checkRanks(List<Card> cards)
{
Card zeroCard = cardAt(selectedCards.get(0));
Card firstCard = cardAt(selectedCards.get(1));
Card secondCard = cardAt(selectedCards.get(2));
boolean isZero = zeroCard.pointValue() + firstCard.pointValue() + secondCard.pointValue() == 0;
boolean zeroCardRankNotEqualFirst = !zeroCard.rank().equals(firstCard.rank())
boolean zeroCardRankNotEqualSecond = !zeroCard.rank().equals(secondCard.rank())
boolean firstCardRankNotEqualsSecond = !firstCard.rank().equals(secondCard.rank());
return isZero && zeroCardRankNotEqualFirst && zeroCardRankNotEqualSecond && firstCardRankNotEqualsSecond;
}
Here is the problem: Return true if the string "cat" and "dog" appear the same number of times in the given string. Examples: catDog("catdog") → true; catDog("catcat") → false; catDog("1cat1cadodog") → true
public boolean catDog(String str) {
int countCat=0;
int countDog=0;
for (int i=0; i<str.length();i++)
{
if (str.charAt(i)== 'c'&& str.length()>=3)
{
if (str.substring(i,i+3).equals("cat"))
countCat++;
}
}
for (int i=0; i<str.length();i++)
{
if (str.charAt(i)== 'd' && str.length()>=3)
{
if (str.substring(i,i+3).equals("dog"))
countDog++;
}
}
if (countCat == countDog)
return true;
else
return false;
}
In your for loops conditions you are checking if entire String has length greater or equal 3 instead of checking only part from i till end. Try maybe with
str.length() - i >= 3
instead of
str.length() >= 3
str.substring(i,i+3).equals("cat")
i might be the last and i+3 will give an error
Why don't you simply use StringUtils#countMatches?
StringUtils.countMatches(myStr, "cat") == StringUtils.countMatches(myStr, "dog");
Don't get lost with the indexes. However, if you don't want to use this method, debugging your code is the best thing you can do.
Okay, this is what I might do:
The problem was with your check str.length() >= 3. It should have been i + str.length().
I also suggest some changes to your code to get rid of duplication. Here I extracted the part that counts the number of appearances of a substring and moved it to its own method. The part that checks if count of cat equals count of dog now calls said method twice.
public static void main(String[] args) {
System.out.println(catDog("catdog"));
System.out.println(catDog("catcat"));
System.out.println(catDog("1cat1cadodog"));
System.out.println(catDog("catdogcatc"));//Would previously throw error.
}
public static boolean catDog(String str) {
int countCat = countAppearances(str, "cat");
int countDog = countAppearances(str, "dog");
return countCat == countDog;
}
private static int countAppearances(String str, String key) {
int count = 0;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == key.charAt(0) && i + key.length() <= str.length()) {
if (str.substring(i, i + key.length()).equals(key)) {
count++;
}
}
}
return count;
}
You need to update your first condition before spiting you string like:
if (str.charAt(i)== 'c' && (str.length() - i) >= 3)
{
if (str.substring(i,i+3).equals("cat"))
countCat++;
}
public boolean catDog(String str) {
int catCount = 0, dogCount = 0;
//run a for loop to check cat count
//run loop till 2nd last character
for (int i = 0; i < str.length() - 2; i++) {
//now check if the charaters at positions matches "cat"
//if matches then increment cat count
if (str.charAt(i) == 'c' && str.charAt(i + 1) == 'a' && str.charAt(i + 2) == 't') {
catCount++;
} else if (str.charAt(i) == 'd' && str.charAt(i + 1) == 'o' && str.charAt(i + 2) == 'g') {
//else check if the charaters at positions matches "dog"
//if matches then increment dog count
dogCount++;
}
}
//check cat count and dog count
if (catCount == dogCount) {
return true;
} else {
return false;
}
}