Allergies in 8th: Complete Solution & Deep Dive Guide

white printer paper with blue text

From Zero to Hero: Decoding Allergy Scores with 8th and Bitwise Magic

A single, cryptic number arrives from a lab: 25. This isn't a test score; it's a patient's complete allergy profile. This number holds the key to whether they're allergic to tomatoes, eggs, or strawberries, but how do you unlock that information? This scenario isn't fiction; it's a common and highly efficient method for storing multiple true/false values in a single integer.

You've probably faced similar data-packing puzzles, where information feels compressed and inaccessible. The challenge lies in cleanly and efficiently extracting individual flags from a composite value. Many developers might reach for complex conditional chains or string parsing, but there's a far more elegant and powerful solution rooted in the very bits and bytes of the number itself.

This comprehensive guide will demystify the process of decoding an allergy score using the stack-based language 8th. We will explore the fundamental concept of bitmasking and wield the power of bitwise operations to build a robust and readable solution. By the end, you'll not only solve this specific problem from the kodikra learning path but also gain a deep understanding of a technique used everywhere from operating systems to game development.


What is the Allergies Score Problem?

The core of this challenge, a classic in the kodikra.com curriculum, is to interpret a single integer known as an "allergy score." This score is a composite value that represents all the allergies a person has from a predefined list. Each potential allergen is assigned a unique numerical value that is a power of two.

Here is the standard list of allergens and their associated values:

  • eggs: 1
  • peanuts: 2
  • shellfish: 4
  • strawberries: 8
  • tomatoes: 16
  • chocolate: 32
  • pollen: 64
  • cats: 128

The total allergy score is calculated by summing the values of all the items a person is allergic to. For example, if someone is allergic to peanuts (value 2) and strawberries (value 8), their total score would be 10 (2 + 8). Our task is twofold: first, to create a function that can check for a specific allergy given a score, and second, to generate a complete list of all allergies for that score.

Why Powers of Two are Crucial

The choice of 1, 2, 4, 8, and so on, is not arbitrary. These numbers are successive powers of two (20, 21, 22, 23, ...). When represented in binary, each of these numbers has exactly one bit set to '1' in a unique position.

  • 1 = 00000001
  • 2 = 00000010
  • 4 = 00000100
  • 8 = 00001000
  • ...and so on.

Because each allergen occupies its own "slot" or bit in the binary representation, their sum creates a unique binary pattern. A score of 10, for instance, is 00001010 in binary. You can clearly see the bits for 8 (...1000) and 2 (...0010) are turned on. This structure is called a bitmask, and it's the key to solving the problem efficiently.


Why Use Bitwise Operations for This Task?

When you have data packed into a bitmask, the most direct and performant way to query it is through bitwise operations. These are low-level operations that act directly on the binary representations of numbers. For our allergy problem, the most important one is the Bitwise AND.

The Bitwise AND operation compares two numbers bit by bit. If both corresponding bits are 1, the resulting bit is 1; otherwise, it's 0. This makes it a perfect tool for checking if a specific "flag" (our allergen) is set within the total score.

Let's take our score of 10 (allergic to peanuts and strawberries) and check for a shellfish allergy (value 4).


  10  (Score)      -->  00001010
&  4  (Shellfish)  -->  00000100
---------------------------------
   0  (Result)     -->  00000000

Since the result of the Bitwise AND is 0, we know the bit for shellfish was not set in the score. The person is not allergic to shellfish.

Now, let's check for a strawberries allergy (value 8).


  10  (Score)        -->  00001010
&  8  (Strawberries) -->  00001000
---------------------------------
   8  (Result)       -->  00001000

The result is 8 (which is non-zero). This confirms that the bit for strawberries was set in the original score. The person is allergic to strawberries. This simple, powerful logic forms the basis of our 8th solution.


How to Implement the Allergies Solution in 8th

8th, being a Forth-like language, is exceptionally well-suited for this kind of low-level manipulation. Its stack-based nature and rich set of built-in words (functions) allow for a concise and elegant solution. We'll build our solution by first defining our data and then creating two main words: allergic-to? and list.

Step 1: Defining the Allergen Data

First, we need a way to map the allergen names (strings) to their bitwise values (integers). An 8th map is the perfect data structure for this. We'll define a global variable to hold this map for easy access.


\ Define a map of allergens to their corresponding bitmask values
m{
  "eggs"         1,
  "peanuts"      2,
  "shellfish"    4,
  "strawberries" 8,
  "tomatoes"     16,
  "chocolate"    32,
  "pollen"       64,
  "cats"         128
} constant allergens

Here, m{ ... } creates a map, and constant gives it a name, allergens, making it immutable and available throughout our program.

Step 2: Checking for a Specific Allergy (`allergic-to?`)

Our first functional requirement is to check if a person is allergic to a specific item. We'll create a word called allergic-to? that takes the allergy score and the allergen name (a string) from the stack and leaves a boolean value (true or false) on the stack.

Here is the logic flow for this word:

    ● Start: score, "allergen"
    │
    ▼
  ┌───────────────────────────┐
  │ Get allergen value from   │
  │ 'allergens' map           │
  └────────────┬──────────────┘
               │ (stack: score, value)
               ▼
  ┌───────────────────────────┐
  │ Perform Bitwise AND       │
  │ (score b.and value)       │
  └────────────┬──────────────┘
               │ (stack: result)
               ▼
    ◆ Is result non-zero?
   ╱                       ╲
 Yes (Allergic)          No (Not Allergic)
  │                         │
  ▼                         ▼
 [Push true]             [Push false]
  │                         │
  └──────────┬──────────────┘
             ▼
    ● End: boolean result

And here is the implementation in 8th:


\ Checks if a score includes a specific allergen
\ Stack: ( score item -- ? )
: allergic-to?
  allergens m:@     \ Look up the item's value in the map
  b.and             \ Perform bitwise AND between the score and the item's value
  0 !=              \ Check if the result is not zero. If so, the bit was set.
;

This word is incredibly dense and powerful. Let's walk through it:

  1. allergens m:@: This takes the item string from the top of the stack (e.g., "peanuts") and uses it as a key to look up its value in our allergens map. The string is consumed, and the value (e.g., 2) is pushed onto the stack. The stack now holds `score` and `value`.
  2. b.and: This is the built-in 8th word for Bitwise AND. It pops the top two numbers (the score and the value), performs the AND operation, and pushes the result back onto the stack.
  3. 0 !=: This pops the result and compares it to zero. It pushes true if the result is non-zero, and false otherwise. This is our final boolean answer.

Step 3: Generating the Full Allergy List (`list`)

The second requirement is to generate a complete list of all allergies for a given score. We'll create a word named list that takes the score and returns an array of strings.

The logic for generating the list involves iterating through all known allergens and using our `allergic-to?` word to decide which ones to include.

    ● Start: score
    │
    ▼
  ┌───────────────────────────┐
  │ Create an empty array     │
  │ for results               │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ For each allergen in      │
  │ our 'allergens' map...    │
  └────────────┬──────────────┘
      ╭────────╯
      │
      ▼
    ◆ allergic-to?(score, allergen)?
   ╱                             ╲
 Yes (Push to array)           No (Do nothing)
  │                               │
  ▼                               │
┌─────────────────┐               │
│ a:push allergen │               │
└─────────────────┘               │
      │                           │
      ╰───────────┬───────────────╯
                  │
                  ▼
  ┌───────────────────────────┐
  │ Loop to next allergen     │
  │ or finish.                │
  └────────────┬──────────────┘
               │
               ▼
    ● End: [ "allergy1", "allergy2", ... ]

Here is the 8th code that implements this logic:


\ Returns a list of all allergens for a given score
\ Stack: ( score -- a )
: list
  a{} swap            \ Create an empty array, then swap to get score on top
  allergens m:keys    \ Get all the keys ("eggs", "peanuts", etc.) from the map
  ' swap >a           \ Convert the list of keys into an array
  (
    over over         \ Duplicate the score and the results array
    rot               \ Bring the current allergen name to the top
    allergic-to?      \ Check if allergic to this item
    if
      a:push          \ If true, push the item name onto our results array
    else
      drop drop       \ If false, drop the score and item name we don't need
    then
  ) a:for-each        \ Iterate over each allergen name
  nip                 \ Drop the original score, leaving the results array
;

Detailed Code Walkthrough of the `list` Word

This word is more complex due to the stack manipulation required for iteration. Let's break it down line by line.

  • a{} swap: First, a{} creates a new, empty array and pushes it to the stack. The stack is now ( score [] ). swap reorders the top two items, so the stack becomes ( [] score ). We do this to prepare for the loop, where we'll need the score repeatedly.
  • allergens m:keys ' swap >a: allergens m:keys gets all the keys from our map as a list. ' swap >a is an idiomatic way to convert that list into an array. The stack is now ( [] score ["eggs", "peanuts", ...] ).
  • ( ... ) a:for-each: This is the main loop. a:for-each takes an array and a quotation (an anonymous function, enclosed in parentheses) from the stack. It executes the quotation for each element in the array.
  • Inside the loop (`( ... )`): For each iteration, an allergen name is pushed to the stack. The stack looks like this at the start of an iteration: ( [] score "allergen" ).
    • over over: Duplicates the top two stack items. Stack becomes: ( [] score "allergen" [] score ).
    • rot: Rotates the top three items. Stack becomes: ( [] "allergen" [] score score ). Whoops, that's not right. Let's re-think the stack-juggling. A cleaner approach is needed.

A Cleaner `list` Implementation

The previous loop logic was overly complex. Let's refine it for clarity and correctness. A better approach is to filter the keys directly.


\ Returns a list of all allergens for a given score (Refined Version)
\ Stack: ( score -- a )
: list
  dup                     \ Duplicate the score. Stack: ( score score )
  allergens m:keys ' swap >a \ Get an array of all allergen names. Stack: ( score score ["keys"] )
  ( rot allergic-to? )    \ Define a quotation for filtering
  a:filter                \ Filter the array, keeping only items where the quotation returns true
  nip                     \ Drop the extra score copy
;

Let's walk through this much-improved version:

  1. dup: We start with the score on the stack. dup duplicates it. The stack is now ( score score ). We need one copy to pass into the filter and one to keep for the final result (which a:filter doesn't consume).
  2. allergens m:keys ' swap >a: Same as before, this gets an array of all allergen names. The stack is now ( score score ["eggs", "peanuts", ...] ).
  3. ( rot allergic-to? ): This is the quotation that a:filter will use. For each element in the array, a:filter pushes the element onto the stack. So, inside the filter's loop, the stack will look like ( score score "allergen" ).
    • rot: Rotates the top three items. The stack becomes ( score "allergen" score ).
    • allergic-to?: This word expects ( score item -- ? ). The stack is perfectly set up for it! It consumes the score and item, and pushes a boolean. The stack is now ( score true/false ). This boolean is what a:filter uses to decide whether to keep the item.
  4. a:filter: This powerful word iterates through the array. It applies our quotation to each element. It builds a *new* array containing only the elements for which the quotation returned true. After it's done, the stack is ( score [filtered_allergens] ).
  5. nip: This drops the second-to-top item on the stack (the original score we duplicated). The stack is left with just the final array of allergens, which is our desired output.

This refined solution is far more idiomatic to 8th, leveraging high-level iterating words like a:filter to produce clean, declarative code.


Where Else Can Bitmasking Be Used?

The technique you've just mastered is fundamental in computer science and software engineering. It's not just for allergy tests. Understanding bitmasking opens doors to solving a wide range of problems efficiently.

  • User Permissions: A classic example. An administrator might assign permissions like `READ` (1), `WRITE` (2), and `EXECUTE` (4). A user with a permission value of 5 (1 + 4) can read and execute, but not write. Checking permissions is a simple bitwise AND.
  • Feature Flags: In large applications, developers use feature flags to turn new features on or off for certain users or environments. Storing these flags in a single integer is a memory-efficient way to manage application state.
  • Game Development: Character states (e.g., poisoned, stunned, invisible, flying) or object properties (e.g., collidable, flammable, breakable) are often managed using bitmasks for high-performance state checks within the game loop.
  • Embedded Systems & Hardware: When communicating with hardware, developers often manipulate configuration registers. Each bit in a register might control a different hardware setting (e.g., enable a sensor, set a clock speed). Bitwise operations are the primary way to interact with such hardware.

By learning this concept through the kodikra module, you're gaining a skill that is directly transferable to many domains of programming, especially those where performance and memory efficiency are paramount.

Pros & Cons of Bitmasking

Like any technique, bitmasking has its trade-offs. It's important to know when it's the right tool for the job.

Pros (Advantages) Cons (Disadvantages)
  • Memory Efficient: Stores multiple boolean flags in a single integer. Can store 32 or 64 flags in just 4 or 8 bytes.
  • Extremely Fast: Bitwise operations are single CPU instructions, making them one of the fastest operations possible.
  • Easy to Combine: Combining sets of flags is a simple bitwise OR (b.or) operation.
  • Database Friendly: Storing a single integer in a database is much more efficient than having many boolean columns.
  • Limited Number of Flags: You are limited by the number of bits in your integer type (usually 32 or 64).
  • Readability Can Suffer: Without clear constants and helper functions, code like if (flags & 16) can be cryptic.
  • Not Easily Extensible: Adding a new flag can be difficult if you've exhausted all available bits.
  • Prone to Error: Accidentally using a non-power-of-two value for a flag can lead to overlapping bits and buggy behavior.

Frequently Asked Questions (FAQ)

What exactly is a bitmask?
A bitmask is an integer value used to store multiple on/off states, or "flags." Each bit in the integer's binary representation corresponds to a single flag. If the bit is 1, the flag is "on"; if it's 0, the flag is "off."

Why must the allergy scores be powers of two?
Using powers of two (1, 2, 4, 8...) ensures that each flag corresponds to a single, unique bit in the binary representation. This prevents overlap. If you used 3 (`0011`) for an allergen, it would conflict with allergens 1 (`0001`) and 2 (`0010`), making it impossible to distinguish them reliably.

How does the bitwise AND operator work for checking allergies?
The bitwise AND (b.and in 8th) acts as a "filter." When you AND the total score with a specific allergen's value (which has only one bit set), the result will be non-zero *if and only if* that same bit was also set in the total score. It isolates the one bit you care about.

Can I add a new allergen to this system?
Yes, easily. As long as you haven't exhausted the available bits, you can add a new allergen. You would simply pick the next available power of two (in our case, 256), and add it to the allergens map: "new-allergen" 256,. The existing code will work without any other changes.

Is this method efficient for a large number of allergies?
It is extremely efficient for up to 32 or 64 allergies, depending on the standard integer size of the system. If you need to manage hundreds or thousands of flags, other data structures like a `HashSet` or a bit array (`BitSet`) would be more appropriate.

What does a stack comment like ( score item -- ? ) mean in 8th?
This is a standard convention in Forth-like languages to document a word's effect on the stack. The part before the -- shows what the word expects to find on the stack (in this case, a score and an item). The part after the -- shows what the word leaves on the stack after it's done (in this case, a boolean value represented by ?).

Are there other useful bitwise operators in 8th?
Absolutely. Besides b.and, 8th provides b.or (to add a flag to a score), b.xor (to toggle a flag), and b.not (to invert all bits). These form a complete toolkit for any bitmask manipulation task.

Conclusion: The Power of Thinking in Bits

We began with a simple number and transformed it into a meaningful list of information. By leveraging the power of bitwise operations in 8th, we developed a solution that is not only correct but also incredibly efficient and elegant. The allergy score problem, a key challenge in the 8th learning path on kodikra.com, serves as a perfect introduction to the world of bitmasking.

The core takeaways are clear: understanding how data is represented at the binary level allows you to write faster, more memory-conscious code. The techniques you've learned here—using powers of two for flags and the bitwise AND for checking them—are universal principles applicable across countless programming languages and domains. You are now better equipped to tackle problems involving permissions, state management, and hardware control with confidence.

Disclaimer: The 8th code in this article is written for modern, standard 8th implementations. Syntax and available words may vary slightly in different environments.

Ready to apply these skills to new challenges? Explore the complete 8th 4 roadmap module to continue your journey. For a deeper dive into the language itself, be sure to visit our comprehensive 8th language guide.


Published by Kodikra — Your trusted 8th learning resource.