Search

Dark theme | Light theme
Showing posts with label Clojure Goodness. Show all posts
Showing posts with label Clojure Goodness. Show all posts

April 21, 2024

Clojure Goodness: Pretty Printing Collection Of Maps

The namespace clojure.pprint has some useful function to pretty print different data structures. The function print-table is particularly useful for printing a collection of maps, where each map represents a row in the table, and the keys of the maps represent the column headers. The print-table function accepts the collection as argument and prints the table to the console (or any writer that is bound to the *out* var). We can also pass a vector with the keys we want to include in the table. Only the keys we specify are in the output. The order of the keys in the vector we pass as argument is also preserved in the generated output.

In the following example code we use the print-table function to print some conference data that is stored in a vector of maps. We use the function with-out-str to capture the output of print-table so we can compare it with our expected result. In the example we first call print-table with the full conferences vector, and then we call it again with a vector of specific keys we want to include in the output.

(ns mrhaki.sample.print-table
  (:require [clojure.pprint :refer [print-table]]
            [clojure.test :refer [is]])
  (:import [java.io StringWriter]))

;; Vector with maps representing conference information.
(def conferences [{:name "Javaland" :location "Nürburgring" :country "Germany"}
                  {:name "JFall" :location "Ede" :country "The Netherlands"}
                  {:name "Full Stack Conference" :location "Nieuwegein" :country "The Netherlands"}])

;; Using print-table we get a nicely formatted table with all
;; rows from our conferences vector.
;; Each key name is a column header.
;; We use with-out-str function to capture the output
;; of the print-table function as String value.
(is (= "
|                 :name |   :location |        :country |
|-----------------------+-------------+-----------------|
|              Javaland | Nürburgring |         Germany |
|                 JFall |         Ede | The Netherlands |
| Full Stack Conference |  Nieuwegein | The Netherlands |
" (with-out-str (print-table conferences))))

;; Using print-table with a vector of keys we get a nicely formatted table
;; with all rows from our conferences vector.
;; But now the columns are only the keys we specified.
;; The order of the keys is also the order of the columns.
;; We use with-out-str function to capture the output
;; of the print-table function as String value.
(is (= "
|        :country |                 :name |
|-----------------+-----------------------|
|         Germany |              Javaland |
| The Netherlands |                 JFall |
| The Netherlands | Full Stack Conference |
" (with-out-str (print-table [:country :name] conferences))))

Written with Clojure 1.11.2.

October 1, 2022

Clojure Goodness: Writing Text File Content With spit

In a previous post we learned how to read text file contents with the slurp function. To write text file content we use the spit function. We content is defined in the second argument of the function. The first argument allows several types that will turn into a Writer object used for writing the content to. For example a string argument is used as URI and if that is not valid as a file name of the file to read. A File instance can be used directly as argument as well. But also Writer, BufferedWriter, OutputStream, URI, URL and Socket. As an option we can specify the encoding used to write the file content using the :encoding keyword. The default encoding is UTF-8 if we don't specify the encoding option. With the option :append we can define if content needs to be appended to an existing file or the content should overwrite existing content in the file.

In the following example we use the spit function with several types for the first argument:

(ns mrhaki.sample.spit
  (:import (java.io FileWriter FilterWriter StringWriter File)))

;; With spit we can write string content to a file.
;; spit treats the first argument as file name if it is a string.
(spit "files/data/output.txt" (println-str "Clojure rocks!"))

;; We can add the option :append if we want to add text
;; to a file, instead of overwriting the content of a file.
(spit "files/data/output.txt" (println-str "And makes the JVM functional.") :append true)

;; Another option is the :encoding option which is UTF-8 by default
(spit "files/data/output.txt" (str "Clojure rocks!") :encoding "UTF-8")

;; The first argument can also be a File.
(spit (File. "files/data/file.xt") "Sample")

;; We can pass a writer we create ourselves as well.
(spit (FileWriter. "files/data/output.txt" true) "Clojure rocks")

;; Or use a URL or URI instance.
(spit (new java.net.URL "file:files/data/url.txt") "So many options...")
(spit (java.net.URI/create "file:files/data/url.txt") "And they all work!" :append true)

Written with Clojure 1.11.1.

April 28, 2020

Clojure Goodness: Partition Collection Into Sequences

Clojure has the partition, partition-all and partition-by functions to transform a collection into a list of sequences with a (fixed) number of items. We can set the number of items in each sequence by providing a number as the first argument of the partition and partition-all functions. Any remainder elements are not in the resulting list of sequences when we use partition, but are when we use partition-all. We can also specify another collection to use values from to fill up the remainder as the third argument of the partition function.
Optionally we can specify an offset step value as a second argument using both functions. This mean a new partition sequence will start based on stepping through the original collection with the given step value.
Finally we can use a function to define when a new partition must start with the partition-by function. Every time the function returns a new value a new partition will begin.

In the following example Clojure code we use all three functions with all possible arguments:

(ns mrhaki.core.partition
  (:require [clojure.test :refer [is]]))

;; Sample string (a sequence of characters).
(def letters "aBCdeFg")

;; First argument defines how many items are in each partition.
;; Any remainder is ignored. 
(is (= [[\a \B] [\C \d] [\e \F]] (partition 2 letters)))

;; With partition-all the remainder is part of the result.
(is (= [[\a \B] [\C \d] [\e \F] [\g]] (partition-all 2 letters)))


;; The second argument is a step offset.
(is (= [[\a \B] [\d \e]] (partition 2 3 letters)))

(is (= [[\a \B] [\d \e] [\g]] (partition-all 2 3 letters)))

(is (= [[\a \B \C] [\C \d \e] [\e \F \g]] (partition 3 2 letters)))

(is (= [[\a \B \C] [\C \d \e] [\e \F \g] [\g]] (partition-all 3 2 letters)))


;; The third argument is used to fill the last remainder partition if needed.
(is (= [[\a \B \C] [\d \e \F] [\g \! \?]] (partition 3 3 [\! \? \@] letters)))

(is (= [[\a \B \C] [\d \e \F] [\g \! \!]] (partition 3 3 (repeat \!) letters)))

;; When padding collection has not enough items, only what is available
;; is used to fill the remainder part.
(is (= [[\a \B \C] [\d \e \F] [\g \!]] (partition 3 3 [\!] letters)))


;; Using partition-by we can use a function that perfoms the split
;; when the function returns a new value.
(is (= [[\a] [\B \C] [\d \e] [\F] [\g]]
       (partition-by #(Character/isUpperCase %) letters)))

(is (= [[ 1 2 3 4] [5] [6 7 8 9] [10] [11 12 13 14]]
       (partition-by #(= 0 (mod % 5)) (range 1 15))))

Written with Clojure 1.10.1.

April 24, 2020

Clojure Goodness: Counting Frequency Of Items In A Collection

If we want to know how often an item is part of a collection we can use the frequencies function. This function returns a map where each entry has the item as key and the number of times it appears in the list as value.

In the following example Clojure code we use the frequencies function:

(ns mrhaki.core.frequencies
  (:require [clojure.test :refer [are is]]))


(def sample "Clojure is cool!")

(is (= {\space 2 \! 1 \C 1 \c 1 \e 1 \i 1 \j 1 \l 2 \o 3 \r 1 \s 1 \u 1}
       (frequencies sample))
    "Frequency of each character in sample")


(def list ["Clojure" "Groovy" "Cool" "Goodness"])

(is (= {\C 2 \G 2}
       (frequencies (map first list)))
    "Two words start with C and two with G")


(def numbers '(29 31 42 12 8 73 46))

(defn even-or-odd
  "Return string even when number is even, 
   otherwise return string odd."
  [n]
  (if (even? n)
    "even"
    "odd"))

(is (= {"odd" 3 "even" 4}
       (frequencies (map even-or-odd numbers)))
    "list numbers has 3 odd and 4 even numbers")


(def user {:user "mrhaki" :city "Tilburg" :age 46})

(is (= {java.lang.String 2 java.lang.Long 1}
       (frequencies (map type (vals user))))
    "user map has two values of type String and 1 of type Long")

Written with Clojure 1.10.1.

January 10, 2020

Clojure Goodness: Flatten Collections

We can use the flatten function when we have a collection with nested sequential collections as elements and create a new sequence with the elements from all nested collections.

In the following example we use the flatten function:

(ns mrhaki.sample
  (:require [clojure.test :refer [is]]))

;; Elements from nested sequential collections are flattend into new sequence.
(is (= [1 2 3 4 5] (flatten [1 [2 3] [[4]] 5])))
(is (sequential? (flatten [1 [2 3] [[4]] 5])))
(is (= [1 2 3 4 5] (flatten [[1] [2 3] [[4 5]]])))

;; We can use different sequential collection types.
;; We might have to force a type to a sequential collection with seq.
(is (= '(1 2 3 4 5) (flatten [1 (seq (java.util.List/of 2 3)) ['(4 5)]])))
(is (= (quote (1 2 3 4 5)) (flatten [[1] [(range 2 6)]])))

;; flatten on nil returns empty sequence.
(is (= () (flatten nil)))

Written with Clojure 1.10.1.

January 8, 2020

Clojure Goodness: Getting Intersections Between Sets

In the clojure.set namespace we can find the intersection function. This functions accepts one or more sets as arguments and return a new set with all elements that are present in the sets that are passed as arguments to the intersection function. The argument must be a set, so we need to convert other collection or seq values to a set first before we use it as an argument for the function.

In the following example we use one, two or three arguments for the intersection function and also convert other types to a set to be used as argument:

(ns mrhaki.sample
  (:require [clojure.set :refer [intersection]]
            [clojure.test :refer [is]]))

;; Use intersection with sets to find common elements.
(is (= #{"Clojure"} (intersection #{"Java" "Scala" "Clojure"} #{"Clojure" "Groovy"})))

;; An empty set is returned if there is no common element.
(is (= #{} (intersection #{"Java" "Groovy" "Clojure"} #{"C++" "C#"})))

;; We can use more than two sets to find intersections.
(is (= #{"Clojure"} (intersection #{"Java" "Scala" "Clojure"} 
                                  #{"Clojure" "Groovy"} 
                                  #{"Groovy" "JRuby" "Clojure"})))

;; With one set intersections returns the set.
(is (= #{"Clojure" "Groovy"} (intersection #{"Clojure" "Groovy"})))

;; Only sets are allowed as arguments for the intersection function.
;; If one of the arguments is not a set the return value is unexpected. 
(is (= #{} (intersection #{"Clojure" "Groovy"} ["Java" "Scala" "Clojure"])))
;; But we can convert a non-set to a set with the set function.
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"} (set ["Java" "Scala" "Clojure"]))))
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"} (set '("Java" "Scala" "Clojure")))))
;; Or using into #{}.
(is (= #{"Clojure"} (intersection #{"Clojure" "Groovy"} 
                                  (into #{} (vals {:platform "Java" :language "Clojure"})))))

Written with Clojure 1.10.1