Resistor Color in Common-lisp: Complete Solution & Deep Dive Guide

a white board with a bunch of wires attached to it

The Complete Guide to Resistor Color Coding in Common Lisp

Learn to solve the classic resistor color coding problem using Common Lisp. This guide provides a complete solution, explaining how to map color names to their numerical values using idiomatic Lisp data structures like association lists, ensuring a robust and efficient implementation for any developer.

Ever stared at a small electronic component with colorful stripes, wondering how those tiny bands could possibly represent a precise electrical value? You're not alone. This is a common hurdle for electronics hobbyists and a fantastic introductory problem for programmers. The challenge isn't just about coding; it's about translating a real-world system into logical, efficient code.

Many developers, especially those new to a language as unique as Common Lisp, might feel stuck on where to even begin. How do you store the color data? How do you perform the lookup? This guide promises to clear that fog. We will walk you through building a solution from scratch, exploring the "Lisp way" of thinking about data and functions, and transforming a seemingly simple task into a deep learning experience.


What Is the Resistor Color Problem?

At its core, the Resistor Color problem is a mapping challenge derived from electronics. Resistors, which are fundamental components that limit the flow of electrical current, are too small to have their resistance value printed on them directly. Instead, they use a standardized color-coding system.

Each color corresponds to a specific digit, from 0 to 9. The first few bands on a resistor represent the significant digits of its resistance value. For this particular module from the kodikra.com curriculum, we focus on the foundational step: creating a program that can take a color name as input and return its corresponding numerical value.

The standard color mapping is as follows:

Color Value
Black 0
Brown 1
Red 2
Orange 3
Yellow 4
Green 5
Blue 6
Violet 7
Grey 8
White 9

Our goal is to implement two main functionalities:

  • A function that accepts a color string (e.g., "blue") and returns its integer value (6).
  • A function that returns a list of all available color names.

Why Use Common Lisp for This Task?

Common Lisp might seem like an esoteric choice compared to mainstream languages, but it possesses unique strengths that make it exceptionally well-suited for problems involving symbolic data manipulation like this one.

Symbolic Processing Power

Lisp stands for "List Processing." The language was fundamentally designed to work with symbols and lists. Mapping a symbolic name like 'blue or a string "blue" to a numerical value is the most natural kind of operation in Lisp. Its data structures are built for this very purpose.

Interactive Development (REPL)

Common Lisp's REPL (Read-Eval-Print Loop) provides a powerful, interactive development environment. You can define your color data, write a lookup function, and test it instantly, piece by piece. This rapid feedback loop makes debugging and experimentation incredibly efficient, allowing you to build and verify your logic incrementally.

Flexible Data Structures

The language offers several ways to store key-value data, from simple association lists (alists) to more complex hash tables. This flexibility allows the programmer to choose the right tool for the job. For a small, fixed dataset like the ten resistor colors, an alist is not only sufficient but also highly idiomatic and easy to read.


How to Implement the Resistor Color Solution

Let's build the solution step-by-step. Our strategy will be to define the data structure holding the color mappings first, and then create the functions that operate on this data. We will use an association list, which is one of the most fundamental data structures in Lisp.

Step 1: Structuring the Data with an Association List

An association list, or alist, is a list of pairs. Each pair is a cons cell where the car is the key and the cdr is the value. It's a simple and effective way to store key-value data.

We'll define our color map using defparameter. This creates a global, dynamic variable that can be redefined, which is useful during development in the REPL. The asterisks (*) around the name are a convention in Lisp for naming global "special" variables.


;; We define a global parameter to hold our color-to-value mappings.
;; An association list is a list of (key . value) pairs.
(defparameter *color-map*
  '(("black"  . 0)
    ("brown"  . 1)
    ("red"    . 2)
    ("orange" . 3)
    ("yellow" . 4)
    ("green"  . 5)
    ("blue"   . 6)
    ("violet" . 7)
    ("grey"   . 8)
    ("white"  . 9)))

Step 2: The Complete Common Lisp Code

Here is the complete, self-contained solution. We define a package to encapsulate our functions, define our data, and then implement the required functions: color-code and colors.


;;; This solution is part of the kodikra.com exclusive learning curriculum.
;;; It defines functions to work with resistor color codes.

(defpackage #:resistor-color
  (:use #:cl)
  (:export #:color-code #:colors))

(in-package #:resistor-color)

;; Define the association list mapping color strings to integer values.
;; Using defparameter allows this to be easily redefined for testing.
;; The *earmuffs* are a convention for special (global) variables.
(defparameter *color-map*
  '(("black"  . 0)
    ("brown"  . 1)
    ("red"    . 2)
    ("orange" . 3)
    ("yellow" . 4)
    ("green"  . 5)
    ("blue"   . 6)
    ("violet" . 7)
    ("grey"   . 8)
    ("white"  . 9))
  "An association list mapping resistor color names to their numerical values.")

(defun color-code (color)
  "Returns the numerical value associated with a given color string."
  ;; `assoc` searches the `*color-map*` for a pair whose `car` matches the color.
  ;; We use `string-equal` for a case-insensitive comparison.
  (let ((pair (assoc color *color-map* :test #'string-equal)))
    ;; If a pair is found, `assoc` returns it. We then extract the value with `cdr`.
    ;; If no pair is found, `assoc` returns NIL. We should handle this case,
    ;; though the problem constraints may not require it. Here we assume valid input.
    (when pair
      (cdr pair))))

(defun colors ()
  "Returns a list of all defined color names."
  ;; `mapcar` applies a function to each element of a list.
  ;; Here, we apply `car` to each pair in `*color-map*` to extract the color name.
  (mapcar #'car *color-map*))

Step 3: A Detailed Code Walkthrough

Let's break down exactly what each part of the code does. Understanding the "why" is more important than just copying the code.

    ● Start: Input Color String (e.g., "Blue")
    │
    ▼
  ┌───────────────────────────┐
  │ Function: color-code("Blue") │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ Normalize: string-equal   │
  │ (Case-insensitive search) │
  └────────────┬──────────────┘
               │
               ▼
    ◆ Search in *color-map* using `assoc`
   ╱               ╲
 Found pair      Not Found (NIL)
 ("blue" . 6)
   │                  │
   ▼                  ▼
┌──────────────┐   ┌───────────┐
│ Extract CDR  │   │ Return NIL│
│ Result: 6    │   │ (or error)│
└──────────────┘   └───────────┘
   │
   ▼
 ● End: Return Integer Value 6

The Package Definition

(defpackage #:resistor-color ...) creates a new namespace for our code. This prevents our function names (like colors) from clashing with functions in other libraries or the Common Lisp standard itself. The :export clause makes the functions color-code and colors publicly accessible from outside our package.

The `color-code` Function

This is the heart of our solution. It takes one argument, color, which is expected to be a string.

(let ((pair (assoc color *color-map* :test #'string-equal))) ...) is the key line.

  • assoc is a built-in Lisp function that searches an association list. It iterates through *color-map* looking for the first sublist whose car (the first element, our color string) matches the provided color item.
  • :test #'string-equal is crucial. By default, assoc uses eql for comparison, which would not work for strings unless they are the exact same object in memory. string-equal performs a case-insensitive comparison of string characters, so "blue", "Blue", and "BLUE" will all match correctly.
  • The result of assoc is the entire pair, e.g., ("blue" . 6). We store this in a local variable called pair using let.
  • (when pair (cdr pair)) checks if a pair was actually found. If assoc fails, it returns NIL. If it succeeds, pair will be non-NIL (truthy), and we use the cdr function to extract the second part of the pair—the numerical value.

The `colors` Function

This function is much simpler and showcases the functional programming style of Lisp.

(mapcar #'car *color-map*) does all the work.

  • mapcar is a higher-order function that applies a given function to every element of a list and returns a new list containing the results.
  • We provide it with #'car, which is the function that gets the first element of a list (or cons pair).
  • So, for each pair like ("black" . 0) in *color-map*, it applies car, gets "black", and collects these results into a new list: ("black" "brown" "red" ...).

Alternative Approaches and Data Structures

While an association list is perfect for this scenario, it's important for a well-rounded developer to know the alternatives and their trade-offs. The main alternative in Common Lisp for key-value mapping is the hash-table.

When to Use a Hash Table?

A hash table provides much faster lookups, especially for large datasets. Searching an association list is a linear operation (O(n)), meaning the time it takes grows proportionally with the number of items. A hash table lookup is, on average, a constant time operation (O(1)).

For our ten colors, the difference is negligible. But if we were mapping thousands of items, a hash table would be the clear winner.

  ● Start: Need to store key-value data.
  │
  ▼
  ◆ How large is the dataset?
  ╱                           ╲
Small (< ~50 items)        Large (> ~50 items)
Static (rarely changes)     Dynamic (changes often)
  │                              │
  ▼                              ▼
┌──────────────────┐        ┌───────────────────┐
│ Use Association  │        │ Use Hash Table    │
│ List (`alist`)   │        │ (`make-hash-table`)│
└──────────────────┘        └───────────────────┘
  │                              │
  ▼                              ▼
  Pros: Simple, readable,      Pros: Fast lookups (O(1)),
  no overhead.                 efficient for large data.
  Cons: Slow lookups (O(n)).   Cons: More complex setup.

Implementation with a Hash Table

Here's how you could solve the same problem using a hash table. The main difference is in the setup.


(defun make-color-hash-table ()
  "Creates and populates a hash table for color codes."
  ;; Create a hash table that uses case-insensitive string comparison.
  (let ((table (make-hash-table :test 'equalp)))
    ;; Populate the hash table from our original alist.
    (dolist (pair *color-map*)
      (setf (gethash (car pair) table) (cdr pair)))
    table))

;; Define the hash table as a global parameter.
(defparameter *color-hash-table* (make-color-hash-table))

(defun color-code-hash (color)
  "Returns the numerical value for a color using a hash table."
  (gethash color *color-hash-table*))

In this version:

  • We use make-hash-table with :test 'equalp', which provides case-insensitive string comparison.
  • We populate the table once from our `alist`.
  • The lookup function color-code-hash is now a simple call to gethash.

Pros & Cons: Association List vs. Hash Table

Feature Association List (`alist`) Hash Table
Performance Linear search (O(n)). Slow for large lists. Amortized constant time (O(1)). Very fast.
Memory Usage Minimal overhead. It's just a list of pairs. Higher overhead due to hashing mechanism and internal array.
Readability Very high. The data is written literally in the code. Slightly lower. Requires a function to create and populate it.
Use Case Ideal for small, fixed datasets, configuration, or lexical bindings. Ideal for large, dynamic datasets, caches, and indexes.

For the kodikra module on Resistor Colors, the alist approach is superior due to its simplicity, readability, and idiomatic fit for a small, unchanging dataset.


Frequently Asked Questions (FAQ)

What exactly is an association list (alist) in Common Lisp?

An association list is a list where each element is a cons pair, representing a key-value mapping. For example, in the pair ("black" . 0), "black" is the key (the car) and 0 is the value (the cdr). Functions like assoc are designed to search these lists efficiently by key.

Why is `string-equal` important in the solution?

string-equal performs a case-insensitive comparison between two strings. Without it, the default test function eql would only find a match if the input string was the exact same object as the key in the list. Using string-equal makes the function robust, allowing it to correctly handle inputs like "Brown" or "BROWN", not just "brown".

Could I use Lisp symbols instead of strings for the colors?

Absolutely! Using symbols (e.g., 'black instead of "black") is a very common and idiomatic Lisp practice. If you used symbols, your comparison function in assoc could be eq or eql, which is faster than string comparison. The choice depends on your input source; if you are reading text from a user or file, strings are more natural.


;; Example with symbols
(defparameter *color-map-symbols*
  '((black  . 0)
    (brown  . 1)))

(assoc 'black *color-map-symbols*) ; ==> (BLACK . 0)
What's the difference between `defvar` and `defparameter`?

Both define global variables. The key difference is that defparameter always assigns the initial value to the variable, even if it already exists. defvar only assigns the value if the variable is not already defined. For this reason, defparameter is often preferred during interactive development as it allows you to easily update the variable's value by re-evaluating the expression.

How do I run this Common Lisp code?

To run this code, you need a Common Lisp implementation like SBCL (Steel Bank Common Lisp) and an editor setup like Emacs with SLIME or VS Code with the Alive extension. You would save the code in a file (e.g., resistor-color.lisp), load it into your running Lisp image using (load "resistor-color.lisp"), and then you can call the functions directly from the REPL: (resistor-color:color-code "green").

Is Common Lisp still relevant today?

Yes, though it is a niche language. Common Lisp excels in specific domains like artificial intelligence research, symbolic mathematics, scheduling systems, and building domain-specific languages (DSLs). Its powerful macro system, stability, and excellent interactive development capabilities keep it relevant for complex problem domains where its unique features provide a significant advantage.

Where does a concept like this apply in the real world?

This pattern of mapping a name to a value is ubiquitous in software engineering. It's used for configuration files (mapping setting names to values), parsers (mapping language keywords to tokens), state machines (mapping state names to functions), and internationalization (mapping language keys to translated strings).


Conclusion: From Colors to Concepts

We have successfully built a complete, robust, and idiomatic Common Lisp solution for the resistor color problem. More importantly, we've journeyed through key programming concepts. We learned how to represent real-world data using an association list, how to write clean functions to interact with that data, and how to make our code resilient with case-insensitive comparisons.

Furthermore, we analyzed the trade-offs between different data structures by comparing our alist implementation to a hash-table, a critical skill for writing performant software. This seemingly simple exercise from the kodikra curriculum serves as a powerful gateway to understanding data structures, functional programming, and the unique philosophy of Lisp.

Technology Disclaimer: The code in this article adheres to the ANSI Common Lisp standard. It has been tested and is expected to run correctly on modern implementations such as Steel Bank Common Lisp (SBCL) 2.4+, Clozure CL (CCL), and Embeddable Common Lisp (ECL).

Ready to tackle the next challenge and deepen your Lisp expertise? Explore our complete Common Lisp learning path or dive deeper into the language with our comprehensive guide to Common Lisp.


Published by Kodikra — Your trusted Common-lisp learning resource.