All Maps Are Not Created Equally

For this, the final blog post of the Capstone project, I’ve decided to utilize this space and time to gain a deeper understanding of maps and, more specifically, the boost container called flat_map. 

Recently in the Mahjong codebase I’ve been working with nested maps and accessing data stored in a nested map structure. Although maps were obviously a topic covered throughout my CS coursework, I honestly didn’t reach for them too often in my own projects and it took a fair amount of brainpower to unpack both what was in the nested map structure I was looking at and how to get the data from it that I needed.

Fast-forward through conversations with engineers, cppreference reading, and a supremely helpful video about maps in C++ from the Cherno and I succeeded in strengthening my understanding of maps, how to use them, and when to reach for them. But then, as often happens, my mentor/sensei asked me, “why do you think we’re using a boost container flat_map instead of just std::map?” to which, in the moment, I could only make assumptions and guesses around optimization, but nothing concrete. Back to the books for me!

First off, what the heck is a boost container flat_map? A flat_map is “an associative container that supports unique keys and provides fast retrieval of values of another type T based on the keys” (source). That sure sounds like a map… 🧐 Like std::map, flat_map also supports random access iterators.

Where I started to uncover some differences is in the following:

  • flat_map <Key, T> has a value_type of std::pair<Key, T> whereas std::map<Key,T> has a value_type of std::pair<const Key, T>
  • flat_map is implemented like an ordered vector, which means that insertion of new elements will invalidate previous iterators and references
  • Random insertion into a std::map is faster than random insertion into flat_map (source)
  • Random search between the two containers is comparable, though there is some debate that flat_map can be made faster and std::map cannot (source)

None of this necessarily answered my sensei’s question of why use a flat_map vs. a map. Thankfully though, a thorough and multiply cited above Stack Overflow post uncovered the answer to my query!

One very important, significant advantage of a boost container flat_map over std::map is the speed with which a flat_map can be iterated over. Although a std::map would work for the specific use case in our Mahjong game where we use a flat_map, the fact that our nested map structure needs to be iterated over in a couple of different places in the game to get the data we need, a flat_map is a much better choice because that iteration can be optimized.

Of course this is part of the job of a software engineer, however it’s still amazing to me that someone knew to or at least took the time to look into making that design decision. I am such a novice and constantly humbled by the intelligence and experience of my colleagues. It’s an incredible honor to work and learn from them, and I am eternally eager to continue growing in this field.

Critical Conversations

With graduation and the Capstone deadline fast approaching, I’ve been having a fair number of critical conversations reflecting on my Postbacc educational journey, career history, and what goals I have for my career path moving forward. 

Imposter syndrome has weighed heavy on me the past few weeks along with a sense of frustration and insecurity about my own C++ deficiencies, and too much of my energy has been aimed at calming anxiety about what I deliver by the end of term and if that’s “enough”. These feelings, quite frankly, are a nonsense-no-good-waste-of-time, and I know that, but they still well up and require some attention to quash.

On the flipside, this project has been an exceptional experience filled with tremendous personal growth as I tap back into my C++ knowledge and dig deeper into practical applications of topics explored in prior terms. This has allowed me to develop closer connections with other members of the engineering team and I feel a sense of pride for and ownership of the work I’ve contributed to the Mahjong codebase.

The rollercoaster ride of feelings and experiences I’ve been reflecting on place before me some important questions:

  • Do I really, truly want to be a software engineer?
  • Am I prepared to be a Brainium Software Engineer? Can I contribute meaningfully and maximally to our team in this role? 
  • Is there a role that suits my skills, professional experience, and passions more appropriately?
  • How will I be evaluated at Brainiun—both prior to any promotion to SWE and during my tenure working as a SWE?
  • Will the work of a Software Engineer bring me joy?

Most of my answers to these questions are in a state of flux currently. For the longest time, I’ve been stubbornly working towards this goal with the idea that after a few years of getting some software engineering experience I could work towards engineering management. This is still a sensible route, but does it have to look like this? Should my path look like this? Is there a better or different way for me to forge my own trail?

After recently conversing with Brainium’s People Operations Manager about my advancement goals, my greatest takeaway was to not put myself into a box. Just because I’ve had this idea of “Chelsea Marie Hicks – Software Engineer” in my mind, doesn’t mean reality has to exactly match that vision and perhaps I should take a wider look at the view to see what other possibilities are presented. I’ve some soul searching to do and more questions than answers (honestly, that never seems to change).

Buttons, You Really Push My Buttons

I kid you not, I have more to say about buttons.

Previously I experienced success with creating a grid of buttons that would display on the screen, but they didn’t do anything yet. Ultimately, I wanted to have users be able to press a ghost tile button and for the tile to register being touched by turning on like a light switch going from quarter opacity to full opacity. 

I began by utilizing a member function that allows the opacity of buttons to be set, which seemed like a surefire way to get the opacity of the buttons to change on press, but when running the Board Editor on a device I saw no inkling of an opacity change. When running the debugger I was able to confirm that the opacity function was indeed being entered and called on the button pressed. Yet again, a mystery was afoot!

Turns out, instead of setting the opacity of the ghost tiles when they were created, I was setting the lower opacity in the function that renders the view, which on the device at first looks just fine, but is very problematic since the rendering function gets called any time there is a change to the board, or any time it is “made dirty”. So, when a tile button was pressed, the opacity changed to full opacity, a change to the board occurred, and the render function got called, changing the button back to ghost state. The remedy was simple, once figured out, and voila, buttons were able to be pressed and turned on.

A few other things would also need to happen when a ghost tile would be touched, such as the counter incrementing and that tile actually being added to the board. Although I really should’ve focused on adding the tile to the board and working on the model side of things, I kept tinkering with the view and added the increment counter (and decrement counter) functions to the handler for the buttons so that the counter would increase/decrease accordingly.

This led me to discover a bug, which truly was more of a result of poor planning on my part. The accuracy of the counter in its current state requires that the designer use the board editor in a very specific way. They start in add mode by default and can immediately tap on ghost tile buttons to get them to appear at full opacity and the counter to increment. To remove a tile and have the counter remain accurate, a designer must press the remove button and then click the tile they want removed. However, currently, a tile that is at full opacity will go back to quarter opacity when tapped, regardless of the current mode. 

Various discussions with engineers illuminated for me how and why the counter should be tied to the current/latest board in the boards vector instead of being tied to the number of touches on the screen. This makes a whole lot of sense when you think about it–who knows how many tiles there are at any given time? The current board! In practice though, it took me a while to absorb this and rather than letting the counter be something implemented later, I impatiently wanted it “to work” now. My way of doing it works in theory, but not well in practice.

For now, the buggy counter will live on and I’ll ignore it while I get to work on what should’ve come first, the model.