Browse Clojure Foundations for Java Developers

Clojure Sequence Operations: A Comprehensive Guide for Java Developers

Explore Clojure's powerful sequence operations like map, filter, reduce, and more. Learn how to transform data efficiently with Clojure's functional programming capabilities.

A.2.1 Sequence Operations

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.

Introduction to Clojure Sequences

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.

Key Characteristics of Clojure Sequences

  • Lazy Evaluation: Sequences are often lazy, meaning they compute their elements only as needed. This can lead to performance improvements and allows for the creation of infinite sequences.
  • Immutability: Like all Clojure data structures, sequences are immutable. Operations on sequences return new sequences without modifying the original.
  • Uniform Interface: Functions that operate on sequences can be applied to any collection type, thanks to the sequence abstraction.

Common Sequence Functions

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:

1(def numbers [1 2 3 4 5])
2(defn square [x] (* x x))
3(map square numbers) ; => (1 4 9 16 25)

Java Equivalent:

In Java, you might use streams to achieve a similar result:

1List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
2List<Integer> squares = numbers.stream()
3                               .map(x -> x * x)
4                               .collect(Collectors.toList());

Diagram:

    flowchart LR
	    A[Input Sequence: 1, 2, 3, 4, 5] --> B[map square]
	    B --> C[Output Sequence: 1, 4, 9, 16, 25]

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:

1(defn even? [x] (zero? (mod x 2)))
2(filter even? numbers) ; => (2 4)

Java Equivalent:

1List<Integer> evens = numbers.stream()
2                             .filter(x -> x % 2 == 0)
3                             .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:

1(reduce + 0 numbers) ; => 15

Java Equivalent:

1int sum = numbers.stream()
2                 .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:

1(take 3 numbers) ; => (1 2 3)
2(drop 3 numbers) ; => (4 5)

Java Equivalent:

Java doesn’t have direct equivalents, but similar functionality can be achieved with sublists:

1List<Integer> firstThree = numbers.subList(0, 3);
2List<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:

1(concat [1 2] [3 4] [5]) ; => (1 2 3 4 5)

Java Equivalent:

1List<Integer> combined = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4), Arrays.asList(5))
2                               .flatMap(Collection::stream)
3                               .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:

1(distinct [1 2 2 3 3 3 4 5]) ; => (1 2 3 4 5)

Java Equivalent:

1List<Integer> distinctNumbers = numbers.stream()
2                                       .distinct()
3                                       .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:

1(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:

1List<Integer> sortedNumbers = numbers.stream()
2                                     .sorted()
3                                     .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:

1(group-by even? [1 2 3 4 5 6]) ; => {false [1 3 5], true [2 4 6]}

Java Equivalent:

1Map<Boolean, List<Integer>> grouped = numbers.stream()
2                                             .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:

1(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.

Try It Yourself

To deepen your understanding, try modifying the examples above:

  • Change the function used in map to see how it affects the output.
  • Use different predicates with filter and group-by.
  • Experiment with different partition sizes in partition.
  • Combine multiple sequence operations to perform complex data transformations.

Exercises

  1. Transform and Filter: Use map and filter to transform a sequence of numbers by squaring them and then filtering out the odd results.
  2. Accumulate Values: Use reduce to find the product of a sequence of numbers.
  3. Custom Sorting: Sort a sequence of strings by their length using sort with a custom comparator.
  4. Group by Length: Use group-by to group a sequence of strings by their length.
  5. Partition and Concat: Partition a sequence into pairs and then concatenate the pairs into a single sequence.

Key Takeaways

  • Clojure’s sequence operations provide a powerful and flexible way to manipulate collections.
  • These operations emphasize immutability and functional programming principles.
  • Understanding these functions will enhance your ability to write concise and expressive Clojure code.
  • By leveraging these operations, you can perform complex data transformations with ease.

Further Reading

Quiz: Mastering Clojure Sequence Operations

### Which function is used to apply a function to each element of a sequence in Clojure? - [x] map - [ ] filter - [ ] reduce - [ ] concat > **Explanation:** The `map` function applies a given function to each element of a sequence, returning a new sequence of the results. ### What does the `filter` function do in Clojure? - [ ] Applies a function to each element - [x] Returns elements that satisfy a predicate - [ ] Combines sequences - [ ] Sorts elements > **Explanation:** The `filter` function returns a new sequence containing only the elements that satisfy a given predicate. ### How does the `reduce` function operate on a sequence? - [ ] It filters elements - [ ] It sorts elements - [x] It accumulates a single value - [ ] It partitions elements > **Explanation:** The `reduce` function processes a sequence to produce a single accumulated value. ### Which function would you use to remove duplicate elements from a sequence? - [ ] map - [ ] filter - [ ] reduce - [x] distinct > **Explanation:** The `distinct` function removes duplicate elements from a sequence. ### What is the result of `(take 3 [1 2 3 4 5])`? - [x] (1 2 3) - [ ] (3 4 5) - [ ] (4 5) - [ ] (2 3 4) > **Explanation:** The `take` function returns the first n elements of a sequence. ### How can you combine multiple sequences into one in Clojure? - [ ] filter - [ ] reduce - [x] concat - [ ] sort > **Explanation:** The `concat` function combines multiple sequences into one. ### What does the `group-by` function return? - [ ] A single value - [ ] A sorted sequence - [x] A map of sequences - [ ] A concatenated sequence > **Explanation:** The `group-by` function partitions a sequence into a map of sequences, keyed by the result of a function applied to each element. ### Which function splits a sequence into sub-sequences of a specified size? - [ ] map - [ ] filter - [ ] reduce - [x] partition > **Explanation:** The `partition` function splits a sequence into sub-sequences of a specified size. ### What is the primary advantage of lazy evaluation in Clojure sequences? - [ ] It modifies the original sequence - [x] It computes elements only as needed - [ ] It sorts elements automatically - [ ] It combines sequences > **Explanation:** Lazy evaluation computes elements only as needed, which can lead to performance improvements and allows for the creation of infinite sequences. ### True or False: Clojure sequences are mutable. - [ ] True - [x] False > **Explanation:** Clojure sequences are immutable, meaning operations on sequences return new sequences without modifying the original.
Monday, December 15, 2025 Monday, November 25, 2024