Explore Clojure's powerful sequence operations like map, filter, reduce, and more. Learn how to transform data efficiently with Clojure's functional programming capabilities.
In this section, we delve into the world of sequence operations in Clojure, a cornerstone of functional programming. For Java developers, understanding these operations is crucial as they offer a new paradigm for handling collections, emphasizing immutability and functional transformations. We’ll explore commonly used sequence functions such as map
, filter
, reduce
, take
, drop
, concat
, distinct
, sort
, group-by
, and partition
. Each function will be accompanied by clear explanations and examples to illustrate their use.
Clojure sequences are a powerful abstraction for working with collections. They provide a uniform interface for accessing elements, regardless of the underlying data structure. This abstraction allows you to write generic code that can operate on lists, vectors, sets, and maps seamlessly.
Let’s explore some of the most commonly used sequence functions in Clojure, providing examples and comparisons to Java where applicable.
map
§The map
function applies a given function to each element of a sequence, returning a new sequence of the results.
Clojure Example:
(def numbers [1 2 3 4 5])
(defn square [x] (* x x))
(map square numbers) ; => (1 4 9 16 25)
Java Equivalent:
In Java, you might use streams to achieve a similar result:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.map(x -> x * x)
.collect(Collectors.toList());
Diagram:
Diagram 1: The flow of data through the map
function.
filter
§The filter
function returns a new sequence containing only the elements that satisfy a given predicate.
Clojure Example:
(defn even? [x] (zero? (mod x 2)))
(filter even? numbers) ; => (2 4)
Java Equivalent:
List<Integer> evens = numbers.stream()
.filter(x -> x % 2 == 0)
.collect(Collectors.toList());
Diagram:
flowchart LR A[Input Sequence: 1, 2, 3, 4, 5] --> B[filter even?] B --> C[Output Sequence: 2, 4]
Diagram 2: Filtering even numbers from a sequence.
reduce
§The reduce
function processes a sequence to produce a single accumulated value. It takes a function and an initial value, applying the function cumulatively to the elements of the sequence.
Clojure Example:
(reduce + 0 numbers) ; => 15
Java Equivalent:
int sum = numbers.stream()
.reduce(0, Integer::sum);
Diagram:
flowchart LR A[Initial Value: 0] --> B[+ 1] --> C[+ 2] --> D[+ 3] --> E[+ 4] --> F[+ 5] F --> G[Output: 15]
Diagram 3: Accumulating a sum using reduce
.
take
and drop
§The take
function returns the first n elements of a sequence, while drop
returns the sequence without the first n elements.
Clojure Example:
(take 3 numbers) ; => (1 2 3)
(drop 3 numbers) ; => (4 5)
Java Equivalent:
Java doesn’t have direct equivalents, but similar functionality can be achieved with sublists:
List<Integer> firstThree = numbers.subList(0, 3);
List<Integer> afterThree = numbers.subList(3, numbers.size());
Diagram:
flowchart LR A[Input Sequence: 1, 2, 3, 4, 5] --> B[take 3] --> C[Output: 1, 2, 3] A --> D[drop 3] --> E[Output: 4, 5]
Diagram 4: Using take
and drop
to partition a sequence.
concat
§The concat
function combines multiple sequences into one.
Clojure Example:
(concat [1 2] [3 4] [5]) ; => (1 2 3 4 5)
Java Equivalent:
List<Integer> combined = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5))
.flatMap(Collection::stream)
.collect(Collectors.toList());
Diagram:
flowchart LR A[Sequence 1: 1, 2] --> B[concat] C[Sequence 2: 3, 4] --> B D[Sequence 3: 5] --> B B --> E[Output Sequence: 1, 2, 3, 4, 5]
Diagram 5: Concatenating multiple sequences.
distinct
§The distinct
function removes duplicate elements from a sequence.
Clojure Example:
(distinct [1 2 2 3 3 3 4 5]) ; => (1 2 3 4 5)
Java Equivalent:
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.collect(Collectors.toList());
Diagram:
flowchart LR A[Input Sequence: 1, 2, 2, 3, 3, 3, 4, 5] --> B[distinct] B --> C[Output Sequence: 1, 2, 3, 4, 5]
Diagram 6: Removing duplicates with distinct
.
sort
§The sort
function returns a new sequence with elements sorted in ascending order. You can also provide a custom comparator.
Clojure Example:
(sort [3 1 4 1 5 9 2 6 5 3 5]) ; => (1 1 2 3 3 4 5 5 5 6 9)
Java Equivalent:
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
Diagram:
flowchart LR A[Unsorted Sequence: 3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5] --> B[sort] B --> C[Sorted Sequence: 1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
Diagram 7: Sorting a sequence.
group-by
§The group-by
function partitions a sequence into a map of sequences, keyed by the result of a function applied to each element.
Clojure Example:
(group-by even? [1 2 3 4 5 6]) ; => {false [1 3 5], true [2 4 6]}
Java Equivalent:
Map<Boolean, List<Integer>> grouped = numbers.stream()
.collect(Collectors.groupingBy(x -> x % 2 == 0));
Diagram:
flowchart LR A[Input Sequence: 1, 2, 3, 4, 5, 6] --> B[group-by even?] B --> C[Output Map: {false: [1, 3, 5], true: [2, 4, 6]}]
Diagram 8: Grouping elements by a predicate.
partition
§The partition
function splits a sequence into sub-sequences of a specified size.
Clojure Example:
(partition 2 [1 2 3 4 5 6]) ; => ((1 2) (3 4) (5 6))
Java Equivalent:
Java doesn’t have a direct equivalent, but you can achieve similar functionality with loops or custom methods.
Diagram:
flowchart LR A[Input Sequence: 1, 2, 3, 4, 5, 6] --> B[partition 2] B --> C["Output: (1, 2), (3, 4), (5, 6)"]
Diagram 9: Partitioning a sequence into sub-sequences.
To deepen your understanding, try modifying the examples above:
map
to see how it affects the output.filter
and group-by
.partition
.map
and filter
to transform a sequence of numbers by squaring them and then filtering out the odd results.reduce
to find the product of a sequence of numbers.sort
with a custom comparator.group-by
to group a sequence of strings by their length.