Clojure say-hi with varargs - clojure

Input: "Michael" "Julia" "Joe" "Sam"
Output: Hi, Michael, Julia, Joe, and Sam. (pay attention to the commas and the word "and")
Input: nil
Output: Hi, world.
Here is my first attempt:
(defn say-hi [& name]
(print "Hi," name))
user> (say-hi "Michael")
Hi, (Michael)
nil
user> (say-hi "Michael" "Julia")
Hi, (Michael Julia)
nil
Question:
How to implement default: (no input, say "Hi World!")
How to get rid of the parents around names in output?
How to implement the commas separation and add the conjunction word "and"?

First off, Clojure supports multi-arity functions, so you could do something like this to achieve default behaviour:
(defn say-hi
([] (say-hi "World"))
([& names] ...))
Then, what you want is to take a seq and join all the strings it contains together, using ", " in between. The clojure.string namespaces contains lots of string manipulation functions, one of them being clojure.string/join:
(require '[clojure.string :as string])
(string/join ", " ["Michael", "Julia"])
;; => "Michael, Julia"
But the last element of the seq should be concatenated using " and " as a separator, so you'll end up with something like this:
(require '[clojure.string :as string])
(defn say-hi
([] (say-hi "World"))
([& names]
(if (next names)
(format "Hi, %s, and %s!"
(string/join ", " (butlast names))
(last names))
(format "Hi, %s!" (first names)))))
Note that you have to differentiate between the single- and multi-name cases and (next names) basically checks whether the seq contains more than one element. (You could achieve the same by adding another arity to the function.)
(say-hi)
;; => "Hi, World!"
(say-hi "Michael")
;; => "Hi, Michael!"
(say-hi "Michael" "Julia" "Joe" "Sam")
;; => "Hi, Michael, Julia, Joe, and Sam!"

You can use clojure.string/join:
(use '[clojure.string :only [join]])
(defn sentencify [& elems]
(->>
[(join ", " (butlast elems)) (last elems)]
(remove empty?)
(join " and ")))
(defn say-hi [& name]
(print "Hi," (if name
(sentencify name)
"World!")))

A concise solution:
(defn say-hi [& names]
(let [names (case (count names)
0 ["world"]
1 names
(concat (butlast names) (list (str "and " (last names)))))]
(->> names, (cons "Hi"), (interpose ", "), (apply str))))
(say-hi)
;"Hi, world"
(say-hi "Michael")
;"Hi, Michael"
(say-hi "Michael" "Julia" "Joe" "Sam")
;"Hi, Michael, Julia, Joe, and Sam"
For long lists of names, you would want to eschew count, last, and butlast, maybe by pouring names into a vector first.
To print (as the question does) rather than return the formatted string, append print to the final form:
(->> names, (cons "Hi"), (interpose ", "), (apply str), print)

Related

clojure string null check failed with string/blank

(defn get-coll-id [^String coll_id]
(log/info "coll_id: " coll_id)
(if (string/blank? coll_id)
(let [collVal (get-coll-val)]
(log/info "collVal: " collSeqVal)
(format "C%011.0f" collVal))
coll_id))
The log shows "coll_id: null". However, string/blank did not detect the null, and thus log of collVal is skipped. What is the method to check null string?
Something (perhaps a DB?) is giving you the string "null" for coll_id, or perhaps (log/info ...) converts a Clojure nil into the string "null".
Consider this code:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[clojure.string :as str]
))
(defn get-coll-id [^String coll_id]
(println "coll_id: " coll_id)
(if (str/blank? coll_id)
(println :blank)
coll_id))
(dotest
(newline)
(println :v1)
(spyx (get-coll-id nil))
(newline)
(println :v2)
(spyx (get-coll-id (pr-str nil)))
)
with output:
:v1
coll_id: nil
:blank
(get-coll-id nil) => nil
:v2
coll_id: nil
(get-coll-id (pr-str nil)) => "nil"
No matter what you do, you get either the value nil or the string "nil" printed.
Since I haven't used Java for a while, I tried to force it to generate the string "null", but calling o.toString() for a null value creates a
NullPointerException, so that is not the answer.
Update
As amalloy points out, String.valueOf() will convert a Java null into the string "null":
package demo;
public class Demo {
public static String go() {
Object o = null;
return String.valueOf( o );
}
}
when run:
(newline)
(spyx :v3 (demo.Demo/go))
with result
:v3 (demo.Demo/go) => "null"
As to your original question, the nil? function could be used:
(defn blank-or-nil?
[s]
(or (nil? s)
(str/blank? s)))
(defn get-coll-id [^String coll_id]
(println "coll_id: " coll_id)
(if (blank-or-nil? coll_id)
(println "found blank-or-nil coll_id")
coll_id))
which then prints found blank-or-nil coll_id when passed a nil value. However, this could confuse things if your are passed the string "null" or the string "nil".
You need to clarify which value is the input, then track down the source.
The above code is based on my favorite template project.

Trying to split string in Clojure running into lazy seq problem

I am working on a problem to read in a file with lines like:
A abcdefg
B bcdefgh
But I keep getting errors about Lazy Sequence not compatible with Java Charseq ..
I tried:
(def notlazy (doall lyne2))
Then thought I verified:
(realized? notlazy)
true
But still:
(str/split notlazy #" ")
ClassCastException class clojure.lang.LazySeq cannot be cast to class
java.lang.CharSequence (clojure.lang.LazySeq is in unnamed module of
loader 'app'; java.lang.CharSequence is in module java.base of loader
'bootstrap') clojure.string/split (string.clj:219)
Help please!
The first argument to str/split must be a CharSequence to be split. Presumably you want to split each input line in the sequence for which you can use map without needing to eagerly evaluate the input sequence:
(map (fn [line] (str/split line #" ")) lyne2)
Expanding on the previous result a bit, we have this example. You can reproduce the following using this template project.
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.java.io :as io]
[tupelo.string :as str]
))
(def data-file
"A abcdefg
B bcdefgh
")
(dotest
; Version #1
(let [lines (line-seq (io/reader (str/string->stream data-file)))
lines2 (remove str/blank? lines)
lines3 (map str/trim lines2)
line-words (mapv #(str/split % #"\s+") lines3) ; "\s+" => "one or more whitespace chars"
]
(spyxx lines)
(spyxx lines2)
(spyxx lines3)
(spyxx line-words))
with result:
--------------------------------------
Clojure 1.10.2-alpha1 Java 15
--------------------------------------
Testing tst.demo.core
lines => <#clojure.lang.Cons ("A abcdefg" " B bcdefgh" " ")>
lines2 => <#clojure.lang.LazySeq ("A abcdefg" " B bcdefgh")>
lines3 => <#clojure.lang.LazySeq ("A abcdefg" "B bcdefgh")>
line-words => <#clojure.lang.PersistentVector [["A" "abcdefg"] ["B" "bcdefgh"]]>
This shows the type of each result along with its value. We use string->stream so we don't need to set up a dummy file to read from.
The following shows how it would typically be written in real code (not as a demo exercise like Version #1). We use the "thread-last" operator, and write a unit test to verify the result:
; Version #2
(let [result (->> data-file
(str/string->stream)
(io/reader)
(line-seq)
(remove str/blank?)
(map str/trim)
(mapv #(str/split % #"\s+"))) ; "\s+" => "one or more whitespace chars"
]
(is= result [["A" "abcdefg"] ["B" "bcdefgh"]])))

Compact way in Clojure to print a variable number of command line arguments?

I'm a newbie to Clojure. I think I'm trying to solve this procedurally and there must be a better (more functional) way to do this in Clojure...
The -main function can receive a variable number of 'args'. I would like to print them out but avoid an IndexOutOfBoundsException. I thought I could mimic Java and use a case fall-through to minimize the code involved, but that didn't work:
(defn -main [& args]
(println "There are" (str (count args)) "input arguments.")
(println "Here are args:" (str args))
(let [x (count args)]
(case x
(> x 0) (do
(print "Here is the first arg: ")
(println (nth args 0)))
(> x 1) (do
(print "Here is the 2nd arg: ")
(println (nth args 1)))
(> x 2) (do
(print "Here is the 3rd arg: ")
(println (nth args 2))))))
(doseq [[n arg] (map-indexed vector arguments)]
(println (str "Here is the argument #" (inc n) ": " (pr-str arg))))
map-indexes is like map but adds index number in the beginning.
So it goes item by item through arguments, packs index and item into a vector and by destructruing index number and item are mapped to [n arg].
Since clojure begins counting from 0, you use (inc n) to begin counting from 1. pr-str is pretty print string. The str joins all string components together.
there is also a handy formatting facility in clojure's core library: cl-format, which is the port of common lisp's format syntax. It includes a nice way to print out collections:
(require '[clojure.pprint :refer [cl-format]])
(let [args [:a :b :c :d]]
(cl-format true "~{here is the ~:r arg: ~a~%~}"
(interleave (rest (range)) args)))
;; here is the first arg: :a
;; here is the second arg: :b
;; here is the third arg: :c
;; here is the fourth arg: :d
some more information about what format can do is here

Function for applying to a user with a various number of data fields

The question was born when I was practicing an Observer topic in a tutorial
I am trying to apply the function to the user but cannot use user's data fields like name, surname.
Let's say that the user may have various number of data fields so we must use & args argument. My code that does not work:
(ns observer.core)
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(def user2 {:name "Jane" :surname "Smith"})
(apply
(fn [& args] (println (str "I am " (:name args) " " (:surname args) ".")))
user)
(apply
(fn [& args] (println (str "My sister is " (:name args) " " (:surname args) ".")))
user2)
The output:
I am .
My sister is .
observer.core>
How to fix it regarding that the apply function must be used?
apply converts a map to a seq, i.e.
{:name "Alan" :surname "Smith" :alias "Mike"} becomes ([:name "Alan"] [:surname "Smith"] [:alias "Mike"])
You could put it back into a map, if that is what you need:
(let [user {:name "Alan" :surname "Smith" :alias "Mike"}]
(apply
(fn [& args]
(let [args (into {} args)]
(println (str "I am " (:name args) " " (:surname args) "."))))
user))
but this looks a bit of a stretch to me. I believe the solution could have been better if I knew how this function is supposed to be used.
Usually there are two types of functions: (fn :text "some" :row 25) and (fn {:text "some" :row 25}).
In the spirit of learning:
Check out Clojure - Cheatsheet.
10 years with Clojure, and I still use it daily.
(apply some-func (list x y z)) becomes (some-func x y z), because apply assumes that the second argument is a list (which it then unpacks).
And what you are currently doing is collecting all the arguments back into a list called args
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [& args]
(prn 'ARGS args) ;; lets see what is actually in args
(println (str "I am " (:name args) " " (:surname args) ".")))
user)
;; -> ARGS ([:name "Alan"] [:surname "Smith"] [:alias "Mike"])
;; -> I am .
And the outut is as #akond says.
You could, of course, put 'user' in a vector (or list), but then don't use '&' to collect everything back into a list, (which you would then have to pick stuff out of again):
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [args]
(prn 'ARGS args)
(println (str "I am " (:name args) " " (:surname args) ".")))
[user])
That would give you the output you expected. But this is a bit strange, perhaps, but certainly viable if you must use apply and you can control the "list" part of the argument.
So, #akond's solution is simple and clean.
And augmenting it with Clojure "destructing":
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [& args]
(let [{:keys [name surname alias]} (into {} args)]
(println (str "I am " name " " surname "." (when alias (str " But call me " alias "!"))))))
user)
I believe you intended to do something like this:
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(def user2 {:name "Jane" :surname "Smith"})
(defn fn-1
[item]
(println (str "I am " (:name item) " " (:surname item) ".")) )
(defn fn-2
[item]
(println (str "My sister is " (:name item) " " (:surname item) ".")))
(fn-1 user)
(fn-2 user2)
with result:
I am Alan Smith.
My sister is Jane Smith.
One has to wrap a user object or the map by a list.
(ns observer.core)
(defrecord Person [name balance])
(def user (Person. "Alan" 150.34))
(def user2 {:name "Adam" :balance 629.74})
(def observers (atom #{}))
(swap! observers conj (fn [l] (println (str "2. " (:name l)))))
(swap! observers conj (fn [l] (println (str "1. " (:balance l)))))
(println "user")
(vec (map #(apply % (list user)) #observers))
(println "\nuser2")
(vec (map #(apply % (list user2)) #observers))
Output
user
1. 150.34
2. Alan
user2
1. 629.74
2. Adam
observer.core>

How to convert a clojure string of numbers into separate integers?

I can read some data in like this in the repl. For a real program I plan to assign in a let special form.
(def x1 (line-seq (BufferedReader. (StringReader. x1))))
If I enter 5 5, x1 is bound to ("5 5")
I would like to convert this list of one element into a list of two integers. How can I do that? I have been playing around with parsing the string on whitespace, but am having trouble performing the conversion to integer.
Does this help? In Clojure 1.3.0:
(use ['clojure.string :only '(split)])
(defn str-to-ints
[string]
(map #(Integer/parseInt %)
(split string #" ")))
(str-to-ints "5 4")
; => (5 4)
(apply str-to-ints '("5 4"))
; => (5 4)
In case the Clojure version you're using doesn't have clojure.string namespace you can skip the use command and define the function in a following way.
(defn str-to-ints
[string]
(map #(Integer/parseInt %)
(.split #" " string)))
You can get rid of regular expressions by using (.split string " ") in the last line.
Works for all numbers and returns nil in the case it isn't a number (so you can filter out nils in the resulting seq)
(require '[clojure.string :as string])
(defn parse-number
"Reads a number from a string. Returns nil if not a number."
[s]
(if (re-find #"^-?\d+\.?\d*$" s)
(read-string s)))
E.g.
(map parse-number (string/split "1 2 3 78 90 -12 0.078" #"\s+"))
; => (1 2 3 78 90 -12 0.078)
The string can be wrapped with brackets and after that evaluated as clojure list with read-string function:
(def f #(read-string (str "(" % ")")))
(f "5 4")
; => (5 4)