Clojure Spec to parse Reducible - clojure

The doc of clojure.spec.alpha/+ says:
Returns a regex op that matches one or more values matching
pred. Produces a vector of matches
And I can use it like this:
erdos=> (s/conform (s/+ (s/cat :e #{\a \b \c})) (seq "abc"))
[{:e \a} {:e \b} {:e \c}]
In the next step, I want to generalize it to run on Reducible values instead of sequences. But it will not work:
erdos=> (s/conform (s/+ (s/cat :e #{\a \b \c})) "abc")
:clojure.spec.alpha/invalid
How could I use clojure.spec regular expression operators on Reducibles instead of sequences? (but without creating temporary sequences.) Thank you!

You can define a conformer that converts the input (e.g. a string) to a sequence. Use and to compose it with the spec that operates on the sequence:
(s/def ::seq-from-string (s/conformer #(if (string? %) (seq %) ::s/invalid)
#(apply str %)))
(s/conform (s/and ::seq-from-string
(s/+ (s/cat :e #{\a \b \c})))
"abc")
;; => [{:e \a} {:e \b} {:e \c}]
(s/unform (s/and ::seq-from-string
(s/+ (s/cat :e #{\a \b \c})))
[{:e \a} {:e \b} {:e \c}])
;; => "abc"
Here is a more complex example with coll-of on top of that spec:
(s/conform (s/coll-of (s/and ::seq-from-string
(s/+ (s/cat :e #{\a \b \c}))))
["a" "bb"])
;; => [[{:e \a}] [{:e \b} {:e \b}]]
By the way, I am not sure why you want to avoid creating a temporary sequence around the string. When creating a sequence from a string, e.g. (seq "abc"), a lightweight sequence object StringSeq gets created that wraps the underlying string. I don't see the problem with that.

Related

What is :<> in reagent hiccup?

I don't understand the tag ":<>" in the following code
clojure re-frame todomvc
(defn todo-app
[]
[:<>
[:section#todoapp
[task-entry]
(when (seq #(subscribe [:todos]))
[task-list])
[footer-controls]]
[:footer#info
[:p "Double-click to edit a todo"]]])
Can anyone help me on this?
That is creating a React Fragment:
https://reactjs.org/docs/fragments.html
Adding a bit more detail to the previous answer, the fragment gets spliced into a surrounding list instead of creating a child element. In this way, it is similar to the unquoted-splicing operator in Clojure ~# compared to the regular unquote operator ~. An example:
(defn middle-seq [] [ :d :e :f])
(defn middle-seq-frag [] [:<> :d :e :f])
When used to create a Reagent component, we see the difference:
[:a :b :c (middle-seq) :g :h :i] ;=> [:a :b :c [:d :e :f] :g :h :i]
[:a :b :c (middle-seq-frag) :g :h :i] ;=> [:a :b :c :d :e :f :g :h :i]
Otherwise, you would have to restructure the input and use concat:
(vec
(concat
[:a :b :c]
(middle-seq)
[:g :h :i] )) ;=> [:a :b :c :d :e :f :g :h :i]

What is the difference between `&` and `and` in Clojure Spec?

I seem to have a hard time keeping apart the meaning of the & and and operators of Clojure Spec. They both seem to do sort of the same thing, only one is noted as a regex operator, a difference I'm not sure I understand the importance of.
We can see the difference between the two if we sample some data from them:
(ns playground
(:require [clojure.spec :as spec]
[clojure.spec.gen :as gen]))
(gen/generate (spec/gen (spec/and #{:a :c} #{:b :a :c})))
=> :a
(gen/sample (spec/gen (spec/and #{:a :c} #{:b :a :c})))
=> (:c :a :c :a :a :a :a :a :a :c)
As we can see spec/and matches single occurrences of what matches the two predicates #{:a :c} and #{:b :a :c}.
(gen/generate (spec/gen (spec/& #{:a :c} #{:b :a :c})))
=> [:c]
(gen/sample (spec/gen (spec/& #{:a :c} #{:b :a :c})))
=> ([:c] [:a] [:a] [:c] [:c] [:c] [:c] [:a] [:c] [:c])
spec/& on the other hand matches what's accepted by the predicates as part of a sequence.

Convert set to regex pattern in clojure

If I have this set
(def my-set #{"foo.clj" "bar.clj" "baz.clj"})
How can I turn it to this pattern string:
"foo\.clj|bar\.clj|baz\.clj"
My attempt : 
(defn set->pattern-str [coll]
(-> (clojure.string/join "|" coll)
(clojure.string/replace #"\." "\\\\.")))
(set->pattern-str my-set)
=> "foo\\.clj|baz\\.clj|bar\\.clj" ;I get the double backslash
Better ideas?
In case your set of strings might have other metacharacters than just . in them, a more general approach is to ask the underlying java.util.regex.Pattern implementation to escape everything for us:
(import 'java.util.regex.Pattern)
(defn set->pattern-str [coll]
(->> coll
(map #(Pattern/quote %))
(clojure.string/join \|)
re-pattern))
IDEone link here. Remember, IDEone is not a REPL, and you have to tell it to put values on stdout with e.g. println before you can see them.
You were close to the final solution. Double backslash is displayed because it is shown escaped. When you turn it into a seq you will see individual characters:
(seq "foo\\.clj")
;;=> (\f \o \o \\ \. \c \l \j)
And working solution:
(def my-set #{"foo.clj" "bar.clj" "baz.clj"})
(def my-set-pattern
(-> (clojure.string/join "|" my-set)
(clojure.string/replace "." "\\.")
(re-pattern)))
(re-matches my-set-pattern "foo.clj")
;;=> "foo.clj"
(re-matches my-set-pattern "bar.clj")
;;=> "bar.clj"
(re-matches my-set-pattern "baz.clj")
;;=> "baz.clj"
(re-matches my-set-pattern "foo-clj")
;;=> nil
Edit: OK, this one does in fact work. Probably want to break it apart a bit more if it's meant to be long lived code, but this is the simplest way I could find to do it with minimal string munging.
(defn is-matching-file-name [target-string]
(re-matches
(re-pattern (clojure.string/escape (String/join "|" my-set) {\. "\\."}))
target-string))
The clojure.string/escape here takes two arguments: the string to escape, and a mapping of the characters to escape to the replacement strings. The key in this map is the literal \. and the value needs two backslashes since we want to include one backslash preceding any . in the final string to be used as the argument for the re-pattern function.

What is idiomatic clojure to validate that a string has only alphanumerics and hyphen?

I need to ensure that a certain input only contains lowercase alphas and hyphens. What's the best idiomatic clojure to accomplish that?
In JavaScript I would do something like this:
if (str.match(/^[a-z\-]+$/)) { ... }
What's a more idiomatic way in clojure, or if this is it, what's the syntax for regex matching?
user> (re-matches #"^[a-z\-]+$" "abc-def")
"abc-def"
user> (re-matches #"^[a-z\-]+$" "abc-def!!!!")
nil
user> (if (re-find #"^[a-z\-]+$" "abc-def")
:found)
:found
user> (re-find #"^[a-zA-Z]+" "abc.!#####123")
"abc"
user> (re-seq #"^[a-zA-Z]+" "abc.!#####123")
("abc")
user> (re-find #"\w+" "0123!#####ABCD")
"0123"
user> (re-seq #"\w+" "0123!#####ABCD")
("0123" "ABCD")
Using RegExp is fine here. To match a string with RegExp in clojure you may use build-in re-find function.
So, your example in clojure will look like:
(if (re-find #"^[a-z\-]+$" s)
:true
:false)
Note that your RegExp will match only small latyn letters a-z and hyphen -.
While re-find surely is an option, re-matches is what you'd want for matching a whole string without having to provide ^...$ wrappers:
(re-matches #"[-a-z]+" "hello-there")
;; => "hello-there"
(re-matches #"[-a-z]+" "hello there")
;; => nil
So, your if-construct could look like this:
(if (re-matches #"[-a-z]+" s)
(do-something-with s)
(do-something-else-with s))

Create a list from a string in Clojure

I'm looking to create a list of characters using a string as my source. I did a bit of googling and came up with nothing so then I wrote a function that did what I wanted:
(defn list-from-string [char-string]
(loop [source char-string result ()]
(def result-char (string/take 1 source))
(cond
(empty? source) result
:else (recur (string/drop 1 source) (conj result result-char)))))
But looking at this makes me feel like I must be missing a trick.
Is there a core or contrib function that does this for me? Surely I'm just being dumb right?
If not is there a way to improve this code?
Would the same thing work for numbers too?
You can just use seq function to do this:
user=> (seq "aaa")
(\a \a \a)
for numbers you can use "dumb" solution, something like:
user=> (map (fn [^Character c] (Character/digit c 10)) (str 12345))
(1 2 3 4 5)
P.S. strings in clojure are 'seq'able, so you can use them as source for any sequence processing functions - map, for, ...
if you know the input will be letters, just use
user=> (seq "abc")
(\a \b \c)
for numbers, try this
user=> (map #(Character/getNumericValue %) "123")
(1 2 3)
Edit: Oops, thought you wanted a list of different characters. For that, use the core function "frequencies".
clojure.core/frequencies
([coll])
Returns a map from distinct items in coll to the number of times they appear.
Example:
user=> (frequencies "lazybrownfox")
{\a 1, \b 1, \f 1, \l 1, \n 1, \o 2, \r 1, \w 1, \x 1, \y 1, \z 1}
Then all you have to do is get the keys and turn them into a string (or not).
user=> (apply str (keys (frequencies "lazybrownfox")))
"abflnorwxyz"
(apply str (set "lazybrownfox")) => "abflnorwxyz"