New to Julia / lost in translation / how to index a simple 2d-array of strings

Hi,

I’ve (very recently) started with julia and decided I would migrate my (java-) chess-engine to julia.

Installation of the environment and ide (vs code) has been pretty smooth and I like working in the repl, which neatly complements my need to get aquainted with lots of new constructs and concepts of the language. I have plenty of experience, in general, hacking basic & 6510-assembler as a preteen (back in the 80s), then turbo-pascal & C(++), java, sql, R and whatever else I had to deal with, in various challenges as a freelancer (even pl/1 on a mainframe for a while). Nowadays, I’m not coding that much, anymore, but am trying to stay up-to-date with the developments on a practical level.

My first goal is to migrate the utility-, debugging- and logging-functions of the engine to julia. It’s the most boring part of it, but essential for a clean migration and it’s also my opportunity to learn the constructs, patterns & tools, julia provides, before I need my brain for more critical (and performance-relevant!) pieces of code, which include a lot of bit-fiddling on integers, processor intrinsics, branch-avoidance, etc…

…however, I’ve already gotten stuck, trying to understand the way, julia indexes into arrays. I know about the column-major indexing, etc. - so one might think “how hard can that even be?”
…well, turns out: Harder than I thought: :sweat_smile:

const DISPLAY_MARKED            = "|"
const DISPLAY_NOTMARKED         = " "
const DISPLAY_PIECE             = "O"
const DISPLAY_NOPIECE           = " "
const DISPLAY_SQUARE            = [ (DISPLAY_NOTMARKED  * DISPLAY_NOPIECE   * DISPLAY_NOTMARKED)
                                    (DISPLAY_MARKED     * DISPLAY_NOPIECE   * DISPLAY_MARKED)
                                ;   (DISPLAY_NOTMARKED  * DISPLAY_PIECE     * DISPLAY_NOTMARKED)
                                    (DISPLAY_MARKED     * DISPLAY_PIECE     * DISPLAY_MARKED)
                                ]

…these are some basic definitions of global constants to be used for pure utility- / debugging-purposes of displaying human-readable bitboards (= 64bit unsigned integers), representing certain aspects of a chess position.

The idea of DISPLAY_SQUARE is to generate a very simple constant 2x2 lookup-table (with Strings of length 3), to help with generating strings, being printed on the console (only, when testing / debugging), showing the state of 3 bitboards (3x UInt64), projected on one 8x8 chessboard. For example all white pawns (first bitboard) vs. all black pieces (2nd bitboard) and all the squares, the white pawns can move to, including captures (3rd bitboard) of black pieces, like here…

image

In the example, I’m expecting to index into DISPLAY_SQUARE[i1][i2] with i1/i2==2, when there is a piece on a square / a white pawn can move to that square (capturing in case there is a piece) and ...==1, otherwise. (There are also other use-cases for this debugging-pattern, i.e. 4 possible states of every square depending on 2…3 bitboards = UInt64s).

However, it’s not working, like I’m expecting (I fell back to some if-then-else logic, for the example, above). This piece of code is not critical (for debugging, only) but the engine-core will have to use a lot of other / different 2d or 3d lookup-tables for performance-critical parts, also (luckily no strings, though, there).

For some reason, I’m ending up with a 1d-array of cardinality 4. What am I missing?!? :thinking:
image

The line breaks between elements in your code mark new rows and thus create a 1d Array, i.e. a Vector. To get a matrix, list the elements in the same row without line breaks, like this

const MA = "|"
const NM = " "
const PI = "O"
const NP = " "
const SQUARE   = [ NM*NP*NM MA*NP*MA ;
                   NM*PI*NM MA*PI*MA]

Then

julia> SQUARE 
2×2 Matrix{String}:
 "   "  "| |"
 " O "  "|O|"

julia> SQUARE[1,2]
"| |"

The variations of array concatenation and array literals are explained here: Multi-dimensional Arrays · The Julia Language

I believe the problem here is that a line-break is the same as a ;, not a space. So while [1 2; 3 4] makes a matrix, the code above is like [1;2;3;4].

These array literals are pretty confusing, and it might be clearer to avoid them. Some ways are:

# product comprehension:
julia> [d*p*d for p in (DISPLAY_NOPIECE, DISPLAY_PIECE), d in (DISPLAY_NOTMARKED, DISPLAY_MARKED)]
2×2 Matrix{String}:
 "   "  "| |"
 " O "  "|O|"

# broadcasting with two rows & one column:
julia> [DISPLAY_NOTMARKED DISPLAY_MARKED] .* [DISPLAY_NOPIECE, DISPLAY_PIECE] .* [DISPLAY_NOTMARKED DISPLAY_MARKED]
2×2 Matrix{String}:
 "   "  "| |"
 " O "  "|O|"

# broadcasting with one row & one column
julia> broadcast([DISPLAY_NOTMARKED DISPLAY_MARKED], [DISPLAY_NOPIECE, DISPLAY_PIECE]) do m, p
         m * p * m
       end
2×2 Matrix{String}:
 "   "  "| |"
 " O "  "|O|"

Note that this won’t work. It will need to be DISPLAY_SQUARE[i1, i2] if you have a matrix, as this is distinct from a vector of vectors.

1 Like

Thx so much, for the quick response!

However, though it looks good on the surface, I still can’t index into the array in a 2d-way (at least not the way, I was anticipating)…

image

…it’s almost, as if the array remained 1d, below the surface (see SQUARE[1:4] and the 2nd index is indexing a char-array, underlying each of the 4 strings. :man_shrugging:

image

I have been fiddling with this issue for roughly 3h…4h, now, and I had actually been at this point, once before, but didn’t put that in my original post, as I didn’t understand, how I got there (and lost the 2x2 array on the surface, again).

Your explanation about the line-break made that transparent, luckily - I had looked at the very page you linked, before, but somehow the line-break - detail went under my radar.

How can indexing a 2x2 array be so hard? :rofl:

1 Like

As mentioned by @mcabbott:

To index into a matrix you need to do Matrix[i1,i2], to index into a 3d array you need to do array[i1,i2,i3] etcetera.

You still can index using only one index, as you point out. This is called linear indexing. But doing Matrix[i1][i2] is not what you want.

You can take a look at what the docs say about how to index here.

So if you do SQUARE[4][2] you are going to the 4th element of the array (or equivalently the element [2,2]). This element is composed of three symbols |,O, and |. And you are indexing into the second element of this string, which is the character “O”.

Note that this won’t work. It will need to be DISPLAY_SQUARE[i1, i2] if you have a matrix, as this is distinct from a vector of vectors.

Ah, that was another unrelated mistake, I made - too much java, in the past, I guess - help much appreciated!

image

…quite interesting, though, how easily julia exposes the underlying memory-alignment and leaves it open for any indexing, I find appropriate - this might come in handy for dealing with large tables and is quite different, from what I’m used to, in java. :+1:

1 Like
julia> [d*p*d for p in (DISPLAY_NOPIECE, DISPLAY_PIECE), d in (DISPLAY_NOTMARKED, DISPLAY_MARKED)]
2×2 Matrix{String}:
 "   "  "| |"
 " O "  "|O|"

…this is my favorite, of your alternatives for defining the array - also much appreciated. Love julia for this kind of syntax, already!

2 Likes

Final words…

…didn’t get that variant to work, without an additional function for initializing (or a macro) and went for the other one-liner, as that is possible, en passant, with the declaration of the SQUARE_FG-const.

Using arrays (and tuples!) is fun, in julia - quite pleased with the results!

# =========================================================================================
# lookup-tables for branchless bitboard-display
# SQUARE_FG[i,j] : i = mask, j = piece : each in {1,2} with 1: no, 2: yes
# -----------------------------------------------------------------------------------------
const ML                = '['   #    mask
const MR                = ']'   #    mask
const NM                = ' '   # no mask
const P                 = 'O'   #    piece
const NP                = ' '   # no piece
const SQUARE_FG         = [NM ML] .* [NP;P] .* [NM MR]          # 2x2 : forground, no color
const SQUARE_FG_COL     = [CRAYON_FG_WHITE CRAYON_FG_BLACK]     # 2   : fg-color
const SQUARE_BG_COL     = [CRAYON_BG_LIGHT CRAYON_BG_DARK ]     # 2   : bg-color
# -----------------------------------------------------------------------------------------`

7 Likes