Optimize a function that analyses a String

63 Views Asked by At

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

There are 2 best solutions below

2
Holger On

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)

1
Basil Bourque On

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
…