Programming lesson
Mastering Recursion and Set Operations in Racket: A Lab 4 Guide
Learn how to implement set-equal?, union, and intersect in Racket using pure recursion. This tutorial uses timely analogies from AI and gaming to make concepts stick.
Why Recursion and Sets Matter in 2026
In the age of AI-driven apps and real-time data processing, understanding recursion and set theory is more relevant than ever. Whether you're building a recommendation engine or analyzing game leaderboards, operations like checking set equality, union, and intersection are foundational. This tutorial walks you through three key functions for your CSCI 301 Lab 4: set-equal?, union, and intersect — all using pure recursion, no loops or side effects.
Understanding the Problem
Your assignment asks you to write Racket functions that treat lists as sets. A set is a collection of unique elements, order doesn't matter. For example, '(1 (2 3)) and '((3 2) 1) are equal because they contain the same elements (including nested lists). But '(1 2 3) and '((1 2 3)) are not equal — one has three elements, the other has one element (a list).
Similarly, union combines two sets without duplicates; intersection finds common elements. You must implement these recursively, using only car, cdr, cons, null?, equal?, and your own helper functions.
Key Concepts: Recursion and Set Representation
In Racket, a set is represented as a list of elements, each of which may be an atom or a list. Since sets are unordered, you cannot rely on position. To check equality, you need to verify that every element of L1 is in L2 and vice versa. This is the subset approach: (subset? L1 L2) and (subset? L2 L1).
For union and intersection, you'll build new lists by traversing one set and checking membership in the other. Recursion replaces iteration: you process the first element, then recur on the rest.
Step-by-Step Implementation
1. Helper: member? (Element in Set)
Before writing the main functions, create a helper that checks if an element is in a set. This is a recursive search through the list.
(define (member? elem lst)
(cond
[(null? lst) #f]
[(equal? elem (car lst)) #t]
[else (member? elem (cdr lst))]))This function returns #t if elem is found, #f otherwise. It uses equal? to compare, which works for nested lists.
2. Helper: subset? (Is L1 a subset of L2?)
To test if every element of L1 is in L2, recursively check the first element, then the rest.
(define (subset? L1 L2)
(cond
[(null? L1) #t]
[(member? (car L1) L2) (subset? (cdr L1) L2)]
[else #f]))If L1 is empty, it's trivially a subset. Otherwise, check if the first element is in L2; if yes, recur; if not, return false.
3. set-equal?
Using subset?, set equality is straightforward:
(define (set-equal? L1 L2)
(and (subset? L1 L2) (subset? L2 L1)))This returns #t only if both are subsets of each other. Test with the examples: (set-equal? '(1 (2 3)) '((3 2) 1)) should give #t.
4. Helper: remove-duplicates (for union)
Union must avoid duplicates. Write a helper that removes duplicate elements from a list (preserving order but not required).
(define (remove-duplicates lst)
(cond
[(null? lst) '()]
[(member? (car lst) (cdr lst)) (remove-duplicates (cdr lst))]
[else (cons (car lst) (remove-duplicates (cdr lst)))]))If the first element appears later, skip it; otherwise, keep it and recur.
5. union
The union of S1 and S2 is all elements from S1 plus elements from S2 that are not already in S1. Recursively:
(define (union S1 S2)
(cond
[(null? S1) (remove-duplicates S2)]
[(member? (car S1) S2) (union (cdr S1) S2)]
[else (cons (car S1) (union (cdr S1) S2))]))When S1 is empty, return S2 with duplicates removed. If the first element of S1 is already in S2, skip it (to avoid duplicates later). Otherwise, include it and recur. Finally, apply remove-duplicates to the result? Actually, the above already avoids duplicates from S1, but S2 might have duplicates internally. So call remove-duplicates on the final list:
(define (union S1 S2)
(remove-duplicates (union-helper S1 S2)))
(define (union-helper S1 S2)
(cond
[(null? S1) S2]
[(member? (car S1) S2) (union-helper (cdr S1) S2)]
[else (cons (car S1) (union-helper (cdr S1) S2))]))Test: (union '(1 (2) 3) '(3 2 1)) should yield something like '(1 (2) 3 2) (order may vary).
6. intersect
Intersection contains elements that appear in both sets. Recursively:
(define (intersect S1 S2)
(cond
[(null? S1) '()]
[(member? (car S1) S2) (cons (car S1) (intersect (cdr S1) S2))]
[else (intersect (cdr S1) S2)]))If the first element of S1 is in S2, include it; otherwise skip. This naturally avoids duplicates because S1 has no duplicates (as a set). Test: (intersect '((1) (2) (3)) '((2) (3) (4))) should give '((2) (3)).
Testing Your Functions
Use the provided examples to verify your code. Remember: order doesn't matter, but elements must match exactly. For nested lists, equal? handles deep comparison.
(set-equal? '(1 (2 3)) '((3 2) 1)) ; #t
(set-equal? '(1 2 3) '((3 2)1)) ; #f
(set-equal? '(1 2 3) '((1 2 3))) ; #f
(union '((1 2 3)) '((3 4 5))) ; ((1 2 3) (3 4 5))
(intersect '((1 2 3)) '((3 2 1))) ; ((1 2 3))Real-World Analogy: AI Playlist Curation
Imagine you're building an AI that merges playlists from two music streaming services. Each playlist is a set of songs (some with nested metadata like artist and album). set-equal? checks if two playlists are identical ignoring order. union combines them without duplicates. intersect finds songs common to both — useful for collaborative filtering. This is exactly how modern recommendation systems work under the hood, using recursive set operations on user data.
Common Pitfalls and Tips
- Use
equal?for comparison, noteq?or=.equal?works for atoms and lists. - Don't use
set!or loops. The assignment forbids side effects and iteration. Stick to recursion. - Handle empty sets. Your functions should work on
'(). - Order of elements in output doesn't matter, but avoid duplicates in union.
- Nested lists are tricky. Ensure your
member?checks equality recursively.
Conclusion
By mastering these recursive set operations, you're not just completing Lab 4 — you're building skills essential for AI, data science, and systems programming. The same logic appears in SQL joins, graph algorithms, and even blockchain validation. Keep practicing recursion; it's a superpower in functional programming.
If you get stuck, trace through small examples on paper. Recursion is about trusting the process: solve one step, then let the recursion handle the rest. Good luck!