0

I'm currently programming an MCTS AI for a game and I want to optimize the CPU bottlenecks. One method takes about 20% of all processing and returns to what team a piece/base on a position on a 2D String belongs. Bases are formatted as "b:000" where 000 is the teamId. Pieces are formatted as "p:000_1" where 000 is the teamId. All I want to do is get the 000 from the Id String as efficient as possible.

Currently my Code is the following:

public static int getOccupantTeam(String[][] grid, int[] pos, StringBuilder sb) {
    sb = sb.delete(0, sb.length()).append(grid[pos[0]][pos[1]]);
    int indexUnderscore = sb.indexOf("_");
    return Integer.parseInt(sb.substring(sb.indexOf(":")+1, indexUnderscore == -1 ? sb.length() : indexUnderscore));
  } 

The StringBuilder is to reduce the amount of created Objects as I can create it once and use it as often as I want. Is there any way to make my code more efficient?

2
  • 1
    do these need to be strings? Maybe make a class that encapsulates this info. If the Ids could be numbers, comparison operations will be much faster. Commented Mar 28, 2024 at 15:09
  • For me it is a smell that you first concatenate everything into one big StringBuilder and then cut that again with substring to parse it. What is the structure of grid, can you maybe work directly with that instead of concatenation? Maybe we could help you better if you provide some examples of the input data and the expected output. Commented Mar 28, 2024 at 15:42

2 Answers 2

1

As a comment already says, you’re better off fixing the overall design. Use dedicated objects instead of formatted strings.

But if you want to keep the logic:

What’s striking is the entirely pointless use of a StringBuilder here. You’re emptying the builder at the beginning (via sb.delete(0, sb.length())), followed by copying a single string into it, just to perform operations on the StringBuilder which you could do on the original String in the first place.

Besides that, since the location of the underscore is expected to be after the colon, you can search for the colon first and only search for the underscore after that position.

public static int getOccupantTeam(String[][] grid, int[] pos, StringBuilder sb) {
    String s = grid[pos[0]][pos[1]];
    int start = s.indexOf(":") + 1, indexUnderscore = s.indexOf("_", start);
    return Integer.parseInt(
        s.substring(start, indexUnderscore == -1? s.length(): indexUnderscore));
}

You can, of course, remove the obsolete StringBuilder parameter now.

If you are using Java 9 or newer, you can omit the substring operation:

public static int getOccupantTeam(String[][] grid, int[] pos) {
    String s = grid[pos[0]][pos[1]];
    int start = s.indexOf(":") + 1, indexUnderscore = s.indexOf("_", start);
    return Integer.parseInt(
        s, start, indexUnderscore == -1? s.length(): indexUnderscore, 10);
}

See Integer.parseInt(CharSequence s, int beginIndex, int endIndex, int radix)

Sign up to request clarification or add additional context in comments.

2 Comments

I didn't want to create endless many String Objects when I call the method hundrets of thousands of times. I think Ill try the suggestion and implement Objects that hold information about a certain position in the grid, it's worth a try. Thank you
In the first variant, substring is the only operation creating a new String object and, of course, calling substring on a StringBuilder also creates a new String object. This is unavoidable when you need a String object for parseInt. You can’t create a String object without creating a String object. The Java 9+ solution avoids that. The StringBuilder did not add anything useful. The way you used it, it did not prevent any object creation but added several copying operations.
0

Strings

Drop the b: & p: prefixes as the length of the string distinguishes the base from the piece. Three characters means a base, more than 3 means a piece.

boolean isBase = ( string.length() == 3 ) ;
boolean isPiece = ( string.length() > 3 ) ;

To get the team number, you know the digits are always in the first three characters.

int teamId = Integer.parseInt( string.substring( 0 , 2 ) ) ;

Objects

But as others suggested, you should be using smart objects rather than dumb strings.

Something like this quick-and-dirty demo.

Make an interface to cover both base and piece.

package work.basil.example.game;

public interface GamePart
{
    int teamId ( );
}

Implement both base and piece. I would define them as a record if their main purpose is to transparently communicate shallowly-immutable data.

package work.basil.example.game;

public record Base ( int teamId ) implements GamePart
{
}
package work.basil.example.game;

public record Piece ( int teamId , int id ) implements GamePart
{
}

Represent the board. The board holds elements of the type of the interface GamePart. The actual objects can be an instance of either of our two record classes, Base & Piece.

package work.basil.example.game;

public class Board
{
    final GamePart[][] grid = new GamePart[ 8 ][ 8 ];

    GamePart gamePartAtIndices ( final int xIndex , final int yIndex )
    {
        return grid[ xIndex ][ yIndex ];
    }

    void placeGamePartAtIndices ( final int xIndex , final int yIndex , final GamePart gamePart )
    {
        this.grid[ xIndex ][ yIndex ] = gamePart;
    }

    String report ( )
    {
        StringBuilder stringBuilder = new StringBuilder( );
        for ( int row = 0 ; row < grid.length ; row++ )
        {
            for ( int column = 0 ; column < grid[ row ].length ; column++ )
            {
                stringBuilder.append( "Row: " ).append( row ).append( " | Column: " ).append( column ).append( " = " ).append( grid[ row ][ column ] ).append( System.lineSeparator( ) );
            }
        }
        return stringBuilder.toString( );
    }
}

Drive the whole thing through a Game class.

package work.basil.example.game;

public class Game
{
    final Board board = new Board( );

    public static void main ( String[] args )
    {
        Game game = new Game( );
        game.board.placeGamePartAtIndices( 1 , 0 , new Piece( 7 , 42 ) );
        game.demo( );
    }

    private void demo ( )
    {
        System.out.println( this.board.report( ) );
    }
}

When run:

Row: 0 | Column: 0 = null
Row: 0 | Column: 1 = null
Row: 0 | Column: 2 = null
Row: 0 | Column: 3 = null
Row: 0 | Column: 4 = null
Row: 0 | Column: 5 = null
Row: 0 | Column: 6 = null
Row: 0 | Column: 7 = null
Row: 1 | Column: 0 = Piece[teamId=7, id=42]
Row: 1 | Column: 1 = null
Row: 1 | Column: 2 = null
Row: 1 | Column: 3 = null
…

1 Comment

I think I might've given too little information to reverse engineer the game. I am unable to change anything about the Piece class as my professor doesn't allow it, so making it implement an interface does't work. Also Base is not a class but rather a value stored in another "Team" class and as a String in my grid. The teamId can be any int. After reading the comments, I'm gonna implement a container class storing a teamId and in case the Object is a Piece, its reference. Also it stores what object is contained, for this I created an Enum storing all different possibilities. Thank you though!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.