Collections: List
Overview
In Clojure, a list is a fundamental data structure used to store an ordered collection of elements. Lists are essential in functional programming and are extensively used in Clojure. Lists are immutable, meaning that once they are created, their contents cannot be modified. This is because modifying a list would require changing the reference to the next element, and since this is not allowed, the list is immutable. Clojure lists are implemented as singly linked lists, where each element references the next element. This makes lists efficient for adding elements to the beginning of the list but inefficient for adding elements to the end of the list. Despite this, lists are still widely used in Clojure because of their simplicity and ease of use.
From https://clojure.org/reference/data_structures#Lists:
Lists are collections. They implement the ISeq interface directly. (Note that the empty list implements ISeq as well, however the
seqfunction will always returnnilfor an empty sequence.) count is O(1). conj puts the item at the front of the list.
List Creation
Code
(ns collections-list.core)
;; ---
;; List creation
;; ---
'(1 2 3 4 5)
;; => (1 2 3 4 5)
(list 1 2 3 4 5)
;; => (1 2 3 4 5)
(class '(1 2 3 4 5))
;; => clojure.lang.PersistentList
(= '(1 2 3 4 5) (list 1 2 3 4 5))
;; => true
'(1 "b" 3 "d")
;; => (1 "b" 3 "d")
(cons 1 '(2 3 4 5))
;; => (1 2 3 4 5)
(conj '(2 3 4 5) 1)
;; => (1 2 3 4 5)Explanation
This Clojure code is about creating and manipulating lists. Here’s a breakdown of each line:
(ns collections-list.core): This line declares the namespace for the following code. Namespaces in Clojure are a way to prevent naming conflicts. In this case, the namespace iscollections-list.core.'(1 2 3 4 5): The quote (’) before the list is a shorthand way to create a list without evaluating its elements. This means'(1 2 3 4 5)will be treated as a list of elements rather than a function call.(list 1 2 3 4 5): This creates a list with the elements 1, 2, 3, 4, and 5. Thelistfunction in Clojure creates a new list with the given arguments.(class '(1 2 3 4 5)): This retrieves the class (type) of the list'(1 2 3 4 5), which isclojure.lang.PersistentList. A PersistentList in Clojure is a list data structure with efficient add and remove operations that don’t mutate the original list.(= '(1 2 3 4 5) (list 1 2 3 4 5)): The=function in Clojure is used for equality testing. This line tests if the quoted list'(1 2 3 4 5)is equal to the list created by(list 1 2 3 4 5). It returns true, which means these two lists are the same.’(1 "b" 3 "d"): This creates a list of various types of elements. In Clojure, lists can contain different types of data.(cons 1 '(2 3 4 5)): Theconsfunction in Clojure adds an element to the beginning of a list or sequence. This line creates a new list with 1 as the first element and the list elements(2 3 4 5)following.(conj '(2 3 4 5) 1): Theconjfunction in Clojure adds an element to a collection. For lists, it adds the new element to the front of the list, which is why the result is(1 2 3 4 5). If you usedconjon a different type of collection, such as a vector, it would add the new element(s) at the end.
The overall theme of this code is demonstrating different ways to create and manipulate lists in Clojure.
Accessing Elements
Code
(ns collections-list.core)
;; ---
;; Accessing elements
;; ---
(first '(1 2 3 4 5))
;; => 1
(first '())
;; => nil
(second '(1 2 3 4 5))
;; => 2
(second '())
;; => nil
(rest '(1 2 3 4 5))
;; => (2 3 4 5)
(rest '(1))
;; => ()
(rest '())
;; => ()
(nth '(1 2 3 4 5) 0)
;; => 1
(nth '() 0)
;; => Execution error (IndexOutOfBoundsException) at collections-list.core/eval8980 (REPL:48).
;; null
(last '(1 2 3 4 5))
;; => 5
(last '(1))
;; => 1
(last '())
;; => nilExplanation
This Clojure code demonstrates various ways of accessing elements from a list (in this case, a quoted list akin to an immutable array). Here’s a breakdown of each function used:
first: This function returns the first element of the collection passed to it. In this example,(first '(1 2 3 4 5))would return1, which is the first element of the list. If the collection is empty as in(first '()), it will returnnil.second: Similar to thefirstfunction,secondreturns the second element of the collection. If the collection has no second element or is empty, it will returnnil.rest: This function returns a collection of all the elements after the first one.(rest '(1 2 3 4 5))will give you(2 3 4 5). If the list only contains one element, like(rest '(1)), it will return an empty list(). If the list is empty, it will still return an empty one().nth: This function is used to access an element at a specific position in the collection.(nth '(1 2 3 4 5) 0)would return1, the element at position0(Clojure uses zero-based indexing). However, if the index is out of bounds (as with(nth '() 0)), it will throw anIndexOutOfBoundsException.last: This function returns the last element of the collection.(last '(1 2 3 4 5))would return5. If the list contains only one element, like(last '(1)), it will return that element. If the list is empty, it will returnnil.
Remember that accessing a list’s first or last element in Clojure is not as efficient as in array-like data structures. That’s because lists in Clojure are implemented as singly linked lists, meaning you have to traverse the entire list to access the last element. For large data sets, vectors would be more efficient if you need to access elements often at the end of the collection.
Manipulating Lists
Code
(ns collections-list.core)
;; ---
;; Manipulating lists
;; ---
(def list-a '(1 2 3 4 5))
(def list-b '(6 7 8 9 10))
(concat list-a list-b)
;; => (1 2 3 4 5 6 7 8 9 10)
(concat list-a)
;; => (1 2 3 4 5)
(concat list-a ())
;; => (1 2 3 4 5)
(flatten '((1 2 3) (4 5 6 (7 8 9)) (10 11 12) 13))
;; => (1 2 3 4 5 6 7 8 9 10 11 12 13)
(map (fn [item] (+ item 1)) list-a)
;; => (2 3 4 5 6)
(map inc list-a)
;; => (2 3 4 5 6)
list-a
;; => (1 2 3 4 5)
(filter (fn [item] ((fn [item] (even? item)) item)) list-a)
;; => (2 4)
(filter (fn [item] (even? item)) list-a)
;; => (2 4)
(reduce (fn [acc item] (+ acc item)) 0 list-a)
;; => 15
(reduce + 0 list-a)
;; => 15Explanation
The given Clojure code includes different functions for manipulating lists. Here is the explanation for each section:
(ns collections-list.core): This is the namespace declaration, a container for different functions, macros, and other data types in Clojure.(def list-a '(1 2 3 4 5))and(def list-b '(6 7 8 9 10)): These lines define two lists,list-a, andlist-b, with five elements each.concatfunction: Theconcatfunction is used to concatenate or join together two or more lists.(concat list-a list-b)will combine the elements oflist-aandlist-b, resulting in a new list with all elements from both lists.(concat list-a)and(concat list-a ())will returnlist-aelements asconcatexpects at least two lists. Since onlylist-aorlist-aand an empty list are provided, there are not enough lists to perform concatenation, so it returns the input list (list-a) as is.
flattenfunction: Theflattenfunction will take a list of lists and return a list containing all the elements of the input lists but flattened into a single list.mapfunction: Themapfunction applies a given function to each element of a list.(map (fn [item] (+ item 1)) list-a)adds 1 to each element inlist-a.(map inc list-a)does the same but uses the built-inincfunction (which increments its argument by 1) instead of a custom function.
filterfunction: Thefilterfunction applies a predicate function (a function that returns true or false) to each list element. It returns a new list that includes only the elements for which the predicate function returns true.(filter (fn [item] ((fn [item] (even? item)) item)) list-a)and(filter (fn [item] (even? item)) list-a)both return a new list that includes only the even numbers fromlist-a.
reducefunction: Thereducefunction applies a binary function (a function that takes two arguments) to a start value and the elements of a list from left to right to reduce the list to a single output value.(reduce (fn [acc item] (+ acc item)) 0 list-a)and(reduce + 0 list-a)compute the sum of list-a elements. The binary function is+, the start value is0, andlist-ais the list of numbers to sum.
In the end, you call the variable list-a to print it again; it will be (1 2 3 4 5) as defined because none of these operations modified the original list. They instead returned new lists since Clojure data structures are immutable.
Predicates
Code
(ns collections-list.core)
;; ---
;; Predicates
;; ---
(empty? '())
;; => true
(empty? '(1 2))
;; => false
(.contains '(1 2 3 4 5) 3)
;; => true
(.contains '(1 2 3 4 5) 6)
;; => false
(every? (fn [item] (> item 3)) '(3 4 5 6))
;; => false
(every? (fn [item] (> item 1)) '(3 4 5 6))
;; => true
(every? #(even? %) '(2 4 6 8))
;; => true
(every? #(odd? %) '(2 4 6 8))
;; => false
(some (fn [item] (> item 3)) '(3 4 5 6))
;; => true
(some (fn [item] (> item 10)) '(3 4 5 6))
;; => nilExplanation
Here is an explanation of each of the code segments:
ns collections-list.core: This code declares a namespace namedcollections-list.core. Namespaces in Clojure are similar to packages in Java; they allow you to organize your code and prevent naming conflicts.empty? '(): Theempty?function checks if a given collection is empty. In this case, it’s checking an empty list. The result istrue, meaning the list is indeed empty.empty? '(1 2): This checks if the list containing1and2is empty. The result isfalsebecause the list contains elements.(.contains '(1 2 3 4 5) 3): The.containsmethod checks if a certain value is in a collection. In this case, it checks if3is in the list of(1 2 3 4 5). The result istrueas3is in the list.(.contains '(1 2 3 4 5) 6): Similar to the previous point, this checks if6is in the list of(1 2 3 4 5). The result isfalsebecause6is not in the list.(every? (fn [item] (> item 3)) '(3 4 5 6)): Theevery?function checks if every element in a collection satisfies a predicate. In this case, it checks if every element in the list(3 4 5 6)is greater than3. The result isfalsebecause3is not greater than3.(every? (fn [item] (> item 1)) '(3 4 5 6)): This checks if every element in the list(3 4 5 6)is greater than1. The result istruebecause all of the elements are greater than1.(every? #(even? %) '(2 4 6 8)): This uses a shorthand notation for a function#(even? %)to check if every element in the list(2 4 6 8)is even. The result istruebecause all numbers in the list are even.(every? #(odd? %) '(2 4 6 8)): Similar to the previous point, this checks if every element in the list(2 4 6 8)is odd. The result isfalsebecause none of the numbers in the list are odd.(some (fn [item] (> item 3)) '(3 4 5 6)): Thesomefunction checks if some (at least one) element in a collection satisfies a predicate. In this case, it checks if any element in the list(3 4 5 6)is greater than3. The result istruebecause4,5, and6are greater than3.(some (fn [item] (> item 10)) '(3 4 5 6)): Similar to the previous point, this checks if any element in the list(3 4 5 6)is greater than10. The result isnil(equivalent tofalse), indicating that no elements in the list are greater than10.
Utility Functions
Code
(ns collections-list.core)
;; ---
;; Utily functions
;; ---
(count '(1 2 3 4 5))
;; => 5
(count '())
;; => 0
(reverse '(1 2 3 4 5))
;; => (5 4 3 2 1)
(reverse '())
;; => ()
(sort '(5 3 1 2 4))
;; => (1 2 3 4 5)
(sort '())
;; => ()
(distinct '(1 2 3 4 5 1 2 3 4 5))
;; => (1 2 3 4 5)
(distinct '(1 2 3 4 5))
;; => (1 2 3 4 5)
(distinct '())
;; => ()
(partition 2 '(1 2 3 4 5 6))
;; => ((1 2) (3 4) (5 6))
(partition 2 '(1 2 3 4 5))
;; => ((1 2) (3 4))
(partition 2 '())
;; => ()
(take 2 '(1 2 3 4 5))
;; => (1 2)
(take 3 '(1 2 3 4 5))
;; => (1 2 3)
(take 6 '(1 2 3 4 5))
;; => (1 2 3 4 5)
(take 6 '())
;; => ()Explanation
This code demonstrates some common functions used on collections in Clojure, a functional programming language. Each function is used on different types of lists. Here’s what each function does:
count: This function returns the number of elements in a collection. In this case, it’s being used on a list. So,(count '(1 2 3 4 5))returns5because there are five elements in the list.reverse: This function returns a sequence of the items in the collection in the reverse order.(reverse '(1 2 3 4 5))returns(5 4 3 2 1).sort: This function returns a sorted sequence of the items in the collection.(sort '(5 3 1 2 4))returns(1 2 3 4 5), the original list is sorted in ascending order.distinct: This function returns a sequence of the items in the collection with duplicates removed.(distinct '(1 2 3 4 5 1 2 3 4 5))returns(1 2 3 4 5).partition: This function partitions the collection into chunks of a specified size.(partition 2 '(1 2 3 4 5 6))returns((1 2) (3 4) (5 6)), which is the original list partitioned into pairs. If there aren’t enough elements in the list to form a complete chunk, those elements are not included in the output (as seen in the case of(partition 2 '(1 2 3 4 5)).take: This function returns the first n items of the collection.(take 2 '(1 2 3 4 5))returns(1 2), which are the first two elements of the list. If the collection has fewer than n items, it returns them. This is illustrated by(take 6 '(1 2 3 4 5)), which returns the entire list because the list contains fewer than 6 items.
The empty lists ('()) examples show how these functions behave when applied to an empty collection. The result is an empty collection for all these functions except for count, which returns 0.
Conversion to Other Collections
Code
(ns collections-list.core)
;; ---
;; Conversion to other collections
;; ---
(vec '(1 2 3 4 5))
;; => [1 2 3 4 5]
(set '(1 2 3 4 5))
;; => #{1 4 3 2 5}
;; convert clojure list to hash-map
(into {} '({:a 2} {:b 4} {:c 6}))
;; => {:a 2, :b 4, :c 6}
(into {} '([:a 2] [:b 4] [:c 6]))
;; => {:a 2, :b 4, :c 6}
(into {} '((:a 2) (:b 4) (:c 6)))
;; => Execution error (ClassCastException) at collections-list.core/eval14028 (REPL:180).
;; class clojure.lang.Keyword cannot be cast to class java.util.Map$Entry (clojure.lang.Keyword is in unnamed module of loader 'app'; java.util.Map$Entry is in module java.base of loader 'bootstrap')
(seq '(1 2 3 4 5))
;; => (1 2 3 4 5)
(str '(1 2 3 4 5))
;; => "(1 2 3 4 5)"Explanation
This Clojure code demonstrates how to convert between various collections. Here’s what each line of code does:
(ns collections-list.core)- This line is defining a new namespace,collections-list.core, which is a container for code and data.(vec '(1 2 3 4 5))- Thevecfunction converts a list to a vector. A vector in Clojure is an indexed, sequential data structure. So here, it’s converting the list(1 2 3 4 5)to the vector[1 2 3 4 5].(set '(1 2 3 4 5))- Thesetfunction converts a list to a set. A set in Clojure is a collection of unique elements. The order of elements in a set is not guaranteed. So here, it’s converting the list(1 2 3 4 5)to the set#{1 4 3 2 5}. Notice that the order of the elements in the output set may vary.(into {} '({:a 2} {:b 4} {:c 6}))- Theintofunction is used to add items to a collection. Here, it’s being used to convert a list of maps into a single map. The result is{:a 2, :b 4, :c 6}.(into {} '([:a 2] [:b 4] [:c 6]))- Here, it’s being used to convert a list of vectors into a single map. The result is{:a 2, :b 4, :c 6}.(into {} '((:a 2) (:b 4) (:c 6)))- In this case, theintofunction tries to convert a list of lists into a map. However, because this is another list instead of a map or vector, this leads to an execution error.(seq '(1 2 3 4 5))- Theseqfunction in Clojure returns a sequence of the collection. Here, it returns a sequence from the list(1 2 3 4 5). The input is already a sequence (a list), but the output is the same.(str '(1 2 3 4 5))- Thestrfunction converts a data type into a string. Here, it’s converting the list(1 2 3 4 5)into the string"(1 2 3 4 5)".