Filtering in Clojure - filter

I have a map like this
(def invoice
{:productId ["001" "002" "003" "004" "005" "006" "007" "008" "009" "010"],
:price ["50" "60" "70" "50" "40" "45" "55" "90" "50" "70"],
:quantity ["0" "0" "1" "2" "0" "0" "0" "0" "0" "1"]})
how can i filter so it only show product id where the quantity is 1 or more ?
i already tried to make like this
(filter (> (invoice :quantity %) 1) (map list (invoice :price) (invoice :quantity) (invoice :productid))
but it doesn't work

You need to create a function to pass as the first argument to filter. Secondly, you need to parse the quantities before you do the comparison to 1, which you can do using read-string:
(filter #(> (read-string (second %)) 1) (map list (invoice :price) (invoice :quantity) (invoice :productId)))

first step would be construct a pair of Product ID and quantity:
(map vector (invoice :quantity) (invoice :productId))
;; ["0" "001"] .... first element would be quantity and second is productiID
second step would be filter out which quantity is greater than 0, here I use (Integer. xx) to conver the quantity to a number.
(filter #(> (Integer. (first %)) 0) (map vector (invoice :quantity) (invoice :productId)))

Usually, data is easier to deal with when it's a little more 'normal' so you don't have to worry about indices like in your sample. I'd reccomend storing it as a seq of ({:id x :price y :quant z} ... ).
This function does that:
(defn invoices [invoice]
(map (fn [id price quant]
{:id id
:price price
:quant quant}) (:prouctId invoice)
(:price invoice)
(:quantity invoice))
Then from there you can simply
(->> invoice
invoices
(remove #(= "0" (:price %)) ;; remove "0" prices
(map :id))) ;; get ids
assuming that your dataset doesn't have negative quantities.

Related

Clojure re-order function

This I must admit is still where I am a clojure newbie. I often find that if I search in the Clojure Docs, I find the function I am looking for. ;)
But I am nervous about this one, but maybe I might get lucky.
I have a card game. Each player has a hand of anywhere from 1-9 cards in hand.
The cards get put into their hands 1 card at a time, from the top of their decks by drawing.
What the players are requesting is the ability to take there UNORGANIZED hand or UNSORTED hand and re-oganize their hand.
I offered a solution of "How about a command like /re-order 31487652 in the command window, that could issue the function (no worries about the command, it's just the sorting func).
The goal of this would be to take each card in their hand 12345678 and change the order to a new order that they provide, the 31487652.
The data is in this format:
(:hand player)
[{:name : "Troll", :_id : 39485723},
{:name : "Ranger", :_id : 87463293},
{:name : "Archer", :_id : 78462721},
{:name : "Orc", :_id : 12346732},
{:name : "Orc", :_id : 13445130},
{:name : "Spell", :_id : 23429900},
{:name : "Dagger", :_id : 44573321}]
My only issue is, I could THINK about this using traditional programming languages, I mean easy, you just copy the data over to another array, haha but I mean don't we love clojure?...
But I'd like to keep things in the pure clojure ideology, AND LEARN the how to do something like this. I mean if it's just "Use this function" that's great I guess, but I don't want to create an atom, unless mandatory, but I don't think that is the case.
If someone could help get me started just THINKING of a way to approach this problem using clojure that would be awesome!
Thanks for ANY help/advice/answer...
ADDENDUM #1
(defn vec-order [n]
(into [] (if (pos? n)
(conj (vec-order (quot n 10)) (mod n 10) )
[])))
(defn new-index [index new-order] (.indexOf new-order (inc index)))
(defn re-order [state side value]
(println (get-in #state [side :hand]))
(update #state [side :hand]
(fn [hand]
(->> hand
(map-indexed (fn [index card] [(new-index index (vec-order value)) card]))
(sort-by first)
(mapv second))))
(println (get-in #state [side :hand])))
So here is my current code, with extraction of data. There is a massive #state, with the side the player is on. I use:
(println (get-in #state [side :hand]))
To look at the data before and after the execution of the defn, but I am not get ANY change. The vector is, for simplicity, 21436587 into [2 1 4 3 6 5 8 7].
But I am missing something because I even run the /re-order 12345678 to make sure things aren't moved and I am just not seeing things. But nothing...
Thank You, definitely for getting me this far.
If you have your required order of the elements as a vector you can sort-by by a function returning an index of a card in that vector:
(let [cards [1 2 3 4 5 6 7 8]
my-order [3 1 4 8 7 6 5 2]]
(sort-by #(.indexOf my-order %) cards))
;; => (3 1 4 8 7 6 5 2)
So, the first function of note will be update which will allow us to return a new player with a function applied to hand if we invoke it as such.
(update player :hand (fn [hand] ... ))
Once, we have this basic structure, the next function that will help us is map-indexed which will allow us to pair the current hand with a new sort-ordered index.
From there, we will be able to sort-by the index, and finally mapv to retrieve the cards.
So, the final structure will look something like:
(defn sort-hand [player new-order]
(update
player
:hand
(fn [hand]
(->> hand
(map-indexed (fn [index card] [(new-index index new-order) card]))
(sort-by first)
(mapv second)))))
For this to work, it's expected that new-order is a vector like [3 1 4 8 7 6 5 2]
As for a solution to new-index,
we can use .indexOf like this
(defn new-index [index new-order] (.indexOf new-order (inc index)))
With your help:
(defn vec-order [n]
(into [] (if (pos? n)
(conj (vec-order (quot n 10)) (mod n 10) )
[])))
(defn new-index [new-order index] (.indexOf new-order (inc index)))
(defn re-order [state side value]
(swap! state update-in [side :hand]
(fn [hand]
(->> hand
(map-indexed (fn [index card] [(new-index (vec-order value) index) card]))
(sort-by first)
(mapv second)))))
WORKS!!! 100%

How to permanently change a vector in a clojure function with a for loop

I am trying to make make a function that will get a vector that contains letters and transform it into a vector with letter pairs
["a" "b" "c"] to ["ab" "bc"]
I found that this function does what I need, but it seems that it doesn't change the vector that I entered as a param, instead it makes a new vector for every iteration.
(defn test [param] (for [i (range (count param))] (assoc param i
(clojure.string/join [(get param i) (get param (inc i))]))))
Does anyone have an idea how to permanently change the elements of the vector?
clojure has a host of built-in fns to do this sort of manipulation. For example:
(->> ["a" "b" "c" "d"]
(partition 2 1) ; generates (("a" "b") ("b" "c") ("c" "d"))
(map clojure.string/join)); joins the pairs
;=> ("ab" "bc" "cd")
If you really want a vector, change the map to mapv

Sort list of 3 elements by particular element, then display

In my program the user will enter 3 elements for each student:
ID
Name
Grade
These are put into a list which will look like (a scheme list)
( (001 "Bob" 80) (002 "Sam" 85) (003 "Aaron" 94) etc . . .)
If the user chooses to sort by name, I'd then like the list to display the info like:
No.1: ID=003, Name=’’Aaron’’, Grade=94
No.2: ID=001, Name=’’Bob’’, Grade=80
No.3: ID=002, Name=’’Sam’’, Grade=85
I finished the function to generate the list but i'm struggling with sorting the list and displaying. any help would be appreciated, thanks
An implementation of sort in Scheme is found in Wikibooks. It is called mergesort. At its core, it assumes that you can use < to compare two elements of the list for sorting purposes.
You can modify mergesort to take an additional argument, less-proc, and use it wherever < is used.
Then you can call mergesort with:
(mergesort lst (lambda (a b) (string<? (cadr a) (cadr b))))
Check your interpreter's documentation for a sorting procedure. For example in Racket you can sort the following list:
(define lst '((001 "Bob" 80) (002 "Sam" 85) (003 "Aaron" 94)))
Ascending, using the name:
(sort lst #:key second string<?)
=> '((3 "Aaron" 94) (1 "Bob" 80) (2 "Sam" 85))
Descending, using the grade:
(sort lst #:key third >)
=> '((3 "Aaron" 94) (2 "Sam" 85) (1 "Bob" 80))
… You get the idea. For the second part of the question, once again refer to your interpreter's documentation. In Racket printf comes in handy - for instance, for printing the records after sorting them by name:
(for ([i (in-naturals 1)]
[record (sort lst #:key second string<?)])
(printf "No.~a: ID=~a, Name=’’~a’’, Grade=~a~n"
i
(~a (first record) #:min-width 3 #:align 'right #:left-pad-string "0")
(second record)
(third record)))
=> No.1: ID=003, Name=’’Aaron’’, Grade=94
No.2: ID=001, Name=’’Bob’’, Grade=80
No.3: ID=002, Name=’’Sam’’, Grade=85

developing simple functions

(define-struct animal (name species age breakfasthour dinnerhour))
(define-struct attendant (name a1 a2 a3))
(define gorilla (make-animal "Koko" "Gorilla" 4 "8" "10"))
(define bat (make-animal "Bruce" "Bat" 1 "23" "5"))
(define mandrill (make-animal "Manny" "Mandrill" 5 "8" "7"))
(define crocodile (make-animal "Swampy" "Crocodile" 1 "10" "8"))
(define ocelot (make-animal "Ozzy" "Ocelot" 7 "7" "17"))
(define capybara (make-animal "Capy" "Capybara" 4 "6" "8"))
(define potto (make-animal "Spot" "Potto" 2 "2" "6"))
(define tapir (make-animal "Stripey" "Tapir" 3 "10" "6"))
(define vulture (make-animal "Beaky" "Vulture" 10 "9" "6"))
(define attendant1 (make-attendant "Dave" gorilla bat mandrill))
(define attendant2 (make-attendant "John" crocodile ocelot capybara))
(define attendant3 (make-attendant "Joe" potto tapir vulture))
I need a function that takes an animal and returns whether its mealtime, if i take gorilla, then dinner time would be at 10. This is what I've done. ignore the quotes on the numbers above.
(define (meal-time? e1 e2)
(string=? (animal-species e1)
(animal-dinnerhour e2)))
it runs, but wnt give me an output. any reason why it wont give me an output?
edit- (meal-time? gorilla 10)tells me it expects an animal, but given 10.
Your meal-time? function takes two animals as arguments (because you use animal- accessor functions on both arguments), but you call it with an animal and a number. So you get an error message telling you that the second argument should be an animal.
If you call your function with two animals as arguments, you'll get no error any more. You'll get #f. What your function does is: it checks whether the species of the first animal is equal to the the dinner hour of the second animal. Since there is no species whose name is a number, that will never be true.

data definitions in scheme?

I've been working on this today and what I am left to do is develop a function that takes an attendant and returns the total age of the animals the attendant has been assigned to. So if I take for example attendant Dave, I would get 10. Not sure where to start. How would you add the ages up?
(define-struct animal (name species age breakfasthour dinnerhour))
(define-struct attendant (name a1 a2 a3))
(define gorilla (make-animal "Koko" "Gorilla" "4" "8" "10"))
(define bat (make-animal "Bruce" "Bat" "1" "23" "5"))
(define mandrill (make-animal "Manny" "Mandrill" "5" "8" "7"))
(define crocodile (make-animal "Swampy" "Crocodile" "1" "10" "8"))
(define ocelot (make-animal "Ozzy" "Ocelot" "7" "7" "17"))
(define capybara (make-animal "Capy" "Capybara" "4" "6" "8"))
(define potto (make-animal "Spot" "Potto" "2" "2" "6"))
(define tapir (make-animal "Stripey" "Tapir" "3" "10" "6"))
(define vulture (make-animal "Beaky" "Vulture" "10" "9" "6"))
(define attendant1 (make-attendant "Dave" gorilla bat mandrill))
(define attendant2 (make-attendant "John" crocodile ocelot capybara))
(define attendant3 (make-attendant "Joe" potto tapir vulture))
#;(define (meal-time? e1 e2)
(string=? (animal-species e1)
(animal-dinnerhour e2)))
#;(define (animal-template s...)
(...(animal-name s)...
(animal-species s)...
(animal-age s)...
(animal-breakfasthour s)...
(animal-dinnerhour s)...))
#;(define (attendant-template s...)
(...(attendant-name s)...
(attendant-s1 s)...
(attendant-s2 s)...
(attendant-s3 s)...))
Simply use the accessor procedures for each type:
(define (animals-age att)
(+ (animal-age (attendant-a1 att))
(animal-age (attendant-a2 att))
(animal-age (attendant-a3 att))))
Clearly, this works only if the age is a number, in your current code is a string (why?), please consider representing ages as numbers, it makes more sense.

Resources