Looping If And Until Result Found and Then Exiting - clojure

I do not think the key-pres? function below is working the way I expect it to. First, here is the input data.
Data from which cmp-val derived:
["2" "000-00-0000" "TOAST" "FRENCH" "" "M" "26-Aug-99" "" "ALL CARE PLAN" "MEDICAL"]
Data that is missing the key (ssn).
["000-00-0000" "TOAST " "FRENCH " "RE-PART B - INSURED "]
The problem is, if I make one of the input's 000-00-0000 something else, I should
see that conj'd onto a log vector. I don't, and I don't see it printed with the if-not empty?.
(defn is-a-in-b
"This is a helper function that takes a value, a column index, and a
returned clojure-csv row (vector), and checks to see if that value
is present. Returns value or nil if not present."
[cmp-val col-idx csv-row]
(let [csv-row-val (nth csv-row col-idx nil)]
(if (= cmp-val csv-row-val)
cmp-val
nil)))
(defn key-pres?
"Accepts a value, like an index, and output from clojure-csv, and looks
to see if the value is in the sequence at the index. Given clojure-csv
returns a vector of vectors, will loop around until and if the value
is found."
[cmp-val cmp-idx csv-data]
(let [ret-rc
(for [csv-row csv-data
:let [rc (is-a-in-b cmp-val cmp-idx csv-row)]
:when (true? (is-a-in-b cmp-val cmp-idx csv-row))]
rc)]
(vec ret-rc)))
(defn test-key-inclusion
"Accepts csv-data file and an index, a second csv-data param and an index,
and searches the second csv-data instances' rows (at index) to see if
the first file's data is located in the second csv-data instance."
[csv-data1 pkey-idx1 csv-data2 pkey-idx2]
(reduce
(fn [out-log csv-row1]
(let [cmp-val (nth csv-row1 pkey-idx1 nil)]
(doseq [csv-row2 csv-data2]
(let [temp-rc (key-pres? cmp-val pkey-idx2 csv-row2)]
(if-not (empty? temp-rc)
(println cmp-val, " ", (nth csv-row2 pkey-idx2 nil), " ", temp-rc))
(if (nil? temp-rc)
(conj out-log cmp-val))))))
[]
csv-data1))
What I want the function to do is traverse data returned by clojure-csv (a vector of vectors). If cmp-val can be found at the cmp-idx location in csv-row, I'd like that
assigned to rc, and the loop to terminate.
How can I fix the for loop, and if not, what looping mechanism can I use to accomplish this?
Thank you.

you don't need true?, it specifically checks for the boolean true value;
don't repeat the call to is-a-in-b;
it would be more idiomatic (and readable) to use a-in-b? as the fn name;
I suggest simplifying the code, you don't really need that let.
Code:
(vec (for [csv-row csv-data
:let [rc (a-in-b? cmp-val cmp-idx csv-row)]
:when rc)]
rc))
But, this are just some general comments on code style... what you're implementing here is just a simple filter:
(vec (filter #(a-in-b? cmp-val cmp-idx %) csv-data))
Furthermore, this will return not only the first, but all matches. If I read your question right, you just need to find the first match? Then use some:
(some #(a-in-b? cmp-val cmp-idx %) csv-data)
UPDATE
Rereading your question I get the feeling that you consider for to be a loop construct. It's not -- it's a list comprehension, producing a lazy seq. To write a loop where you control when to iterate, you must use loop-recur. But in Clojure you'll almost never need to write you own loops, except for performance. In all other cases you compose higher-order functions from clojure.core.

Related

Clojure doesn't work the way I want it to

I made a function to take questions like this.
(defn ask-ques [ques pred]
(print ques)
(let [user-input (read-line)]
(if #(pred user-input) user-input (recur ques pred))))
And I wrote main like this.
(defn -main []
(loop []
(let [user-input (ask-ques "CHOOSE ONE. (C)ontinue OR (E)xit : " #(contains? #{"C" "E"} %))]
(when (= user-input "C") (apply body (rand-nth (seq voc-map))) (recur)))))
But, Clojure received the input first and printed "CHOOSE ONE. (C)ontinue OR (E)xit : " out, and pred does not work well.
What's the problem? Why does it work like this? And what should I do?
#(pred user-input) is a function of zero arguments and, since it has a non-nil value, the if will treat it as truth, so you will always get user-input and it will never recur. I suspect you want (pred user-input) instead.

Loop through vector of vectors and remove element from vector in Clojure

I am new to clojure programming and would like some help with some code. I have this vector of vectors like below,
(def start-pos [[[:fox :goose :corn :you] [:boat] []]])
I would like to loop through the vector and remove an element from one of the internal vectors, e.g. remove ':goose' from start-pos.
I tried the code below but for some reason it doesnt work as intended,
(map #(disj (set %) :goose) start-pos)
Instead the result is,
(#{[:boat] [] [:fox :goose :corn :you]})
As you can see from the result, the internal vectors are now a set and yes, the original order is distorted, is there a way of removing the element and not disarrange the original order of the vectors, maybe without converting it to a set first? I choose this conversion to a set first because according to the docs disj only works for sets.
Add: This post is not similar to this suggested post as my vector is nested three vectors deep.
the internal vectors are now a set
That's because the result of #(disj (set %) :goose) returns a set.
original order is distorted
Sets don't preserve insertion order by default, similar to maps with over 8 keys.
I would like to loop through the vector and remove an element from one of the internal vectors, e.g. remove ':goose' from start-pos.
The function you need for removing an element from a collection by predicate is called remove, but...
The value you want to remove is actually nested three vectors deep in start-pos, so you'd need an additional iteration for each inner vector, and so on if you wanted to remove the keyword :goose from every vector recursively. That's an excuse to use clojure.walk:
(clojure.walk/postwalk
(fn [v]
(if (coll? v)
(into (empty v) (remove #{:goose}) v)
v))
start-pos)
=> [[[:fox :corn :you] [:boat] []]]
This walks every value in start-pos, removing :goose from any collections it finds.
Here is a less flexible approach, that I made more so for my own benefit (learning Clojure)
(update-in
start-pos
[0 0]
#(vec (concat
(subvec % 0 1)
(subvec % (inc 1)))))
It manually navigates in and reconstructs the :goose level of keywords to not have :goose inside
I think some alternative approaches to this problem include Specter and Zippers
you could also employ clojure zipper for that:
user> (require '[clojure.zip :as z])
user> (loop [curr (z/vector-zip start-pos)]
(cond (z/end? curr) (z/root curr)
(= :goose (z/node curr)) (recur (z/remove curr))
:else (recur (z/next curr))))
;; => [[[:fox :corn :you] [:boat] []]]
also, that is quite easy to do with clojure's core functions only:
user> (defn remv [pred data]
(if (vector? data)
(mapv (partial remv pred) (remove pred data))
data))
#'user/remv
user> (remv #{:goose} start-pos)
;; => [[[:fox :corn :you] [:boat] []]]

clojure: filtering a vector of maps by keys existence and values

I have a vector of maps like this one
(def map1
[{:name "name1"
:field "xxx"}
{:name "name2"
:requires {"element1" 1}}
{:name "name3"
:consumes {"element2" 1 "element3" 4}}])
I'm trying to define a functions that takes in a map like {"element1" 1 "element3" 6} (ie: with n fields, or {}) and fiters the maps in map1, returning only the ones that either have no requires and consumes, or have a lower number associated to them than the one associated with that key in the provided map (if the provided map doesn't have any key like that, it's not returned)
but I'm failing to grasp how to approach the maps recursive loop and filtering
(defn getV [node nodes]
(defn filterType [type nodes]
(filter (fn [x] (if (contains? x type)
false ; filter for key values here
true)) nodes))
(filterType :requires (filterType :consumes nodes)))
There's two ways to look at problems like this: from the outside in or from the inside out. Naming things carefully can really help when working with nested structures. For example, calling a vector of maps map1 may be adding to the confusion.
Starting from the outside, you need a predicate function for filtering the list. This function will take a map as a parameter and will be used by a filter function.
(defn comparisons [m]
...)
(filter comparisons map1)
I'm not sure I understand the comparisons precisely, but there seems to be at least two flavors. The first is looking for maps that do not have :requires or :consumes keys.
(defn no-requires-or-consumes [m]
...)
(defn all-keys-higher-than-values [m]
...)
(defn comparisons [m]
(some #(% m) [no-requires-or-consumes all-keys-higher-than-values]))
Then it's a matter of defining the individual comparison functions
(defn no-requires-or-consumes [m]
(and (not (:requires m)) (not (:consumes m))))
The second is more complicated. It operates on one or two inner maps but the behaviour is the same in both cases so the real implementation can be pushed down another level.
(defn all-keys-higher-than-values [m]
(every? keys-higher-than-values [(:requires m) (:consumes m)]))
The crux of the comparison is looking at the number in the key part of the map vs the value. Pushing the details down a level gives:
(defn keys-higher-than-values [m]
(every? #(>= (number-from-key %) (get m %)) (keys m)))
Note: I chose >= here so that the second entry in the sample data will pass.
That leaves only pulling the number of of key string. how to do that can be found at In Clojure how can I convert a String to a number?
(defn number-from-key [s]
(read-string (re-find #"\d+" s)))
Stringing all these together and running against the example data returns the first and second entries.
Putting everything together:
(defn no-requires-or-consumes [m]
(and (not (:requires m)) (not (:consumes m))))
(defn number-from-key [s]
(read-string (re-find #"\d+" s)))
(defn all-keys-higher-than-values [m]
(every? keys-higher-than-values [(:requires m) (:consumes m)]))
(defn keys-higher-than-values [m]
(every? #(>= (number-from-key %) (get m %)) (keys m)))
(defn comparisons [m]
(some #(% m) [no-requires-or-consumes all-keys-higher-than-values]))
(filter comparisons map1)

clojure: Removing maps from lazy-seq of maps

I have a lazy-seq of maps and I'm attempting to remove maps from that lazy-seq based on the return value from another function. The other function will return true or false depending on whether or not a call of get returns a value equal to the parameter. The problem is the function isn't working correctly and I'm not too sure why.
(defn filter-by-name "Filter by names" [name m]
(if (= name (get m :name_of_person)) true false))
;To be called on each map
(defn remove-nonmatching-values "Remove anything not matching" [filter-val all-maps]
(map #(remove (filter-by-name filter-val %)) all-maps))
;trying to call on the lazy seq
You only need to call remove on the sequence of maps.
(defn remove-nonmatching-values
"Remove anything not matching"
[filter-val all-maps]
(remove #(filter-by-name filter-val %) all-maps))
Check Clojure's remove doc
(remove pred coll)
Returns a lazy sequence of the items in coll for which
(pred item) returns false. pred must be free of side-effects.
Returns a transducer when no collection is provided.
A function that produces the test-function you need for a given name is
(defn name-matcher [name]
(fn [m] (= name (:name_of_person m))))
All you have to do is filter the maps accordingly:
(defn retain-matching-maps [name maps]
(filter (name-matcher name) maps))
For example,
(retain-matching-maps "hello" (list {:name_of_person "hello"} {:name_of_person "bye"}))
;({:name_of_person "hello"})
I have got rid of
the comments (which are implied by the function names)
the if (as noted by Guillermo)
the get (Keywords - or maps - are implicit get functions)
the double negative in the function name remove-nonmatching-values.
You could also use :name instead of :name-of-person. The more succinctly you express your program, the less likely you are to make mistakes.

How To Turn a Reduce-Realized Sequence Back Into Lazy Vector Sequence

When I run a 221 line .csv file -- parsed with clojure-csv -- into this function
(defn test-key-inclusion
"Accepts csv-data param and an index, a second csv-data param and an index,
and searches the second csv-data instances' rows (at index) to see if
the first file's data is located in the second csv-data instance."
[csv-data1 pkey-idx1 csv-data2 pkey-idx2 lnam-idx fnam-idx]
(reduce
(fn [out-log csv-row1]
(let [cmp-val (nth csv-row1 pkey-idx1 nil)
lnam (nth csv-row1 lnam-idx nil)
fnam (nth csv-row1 fnam-idx)
temp-rc (first (key-pres? cmp-val pkey-idx2 csv-data2))]
(concat out-log (sorted-map cmp-val (vector lnam fnam)))))
{}
csv-data1))
and then print the result, everything's fine.
If I run a 2672 line .csv file -- also parsed with clojure-csv -- through the function above and then try to print it, I get a stack overflow error -- Exception in thread "main" java.lang.StackOverflowError
So my questions are:
1) Should wrapping the call to this function inside lazy-seq cure my problem?
2) I don't want a list, so will wrapping the lazy-seq call inside a vec turn
my sequence back into a vector without realizing the whole sequence in memory, that is make the lazy-seq un-lazy again?
Thank you.
1) i expect that making the sequence lazy not to help because print will evaluate realize it before printing it. instead try doseq or (map print my-seq) to print it in smaller chunks.
2) yes wrapping it in vec will give you what you want :) though wrapping your reduce with an into would keep it a vector the whole time. ie: (reduce into [] [[1] [2] [3]] ) --> [1 2 3]
(into out-log (sorted-map cmp-val (vector lnam fnam)))))