How can I traverse through my result? [closed] - clojure

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
This is what I currently have:
{:total-pages 1, :total-results 1, :items [{:item-atributes {:title Tolkien Calendar 2017, :product-group Book, :manufacturer Harper Voyager, :author J. R.
R. Tolkien}, :SalesRank 12016, :item-links [{:description Technical Details, :url https://www.amazon.com/Tolkien-Calendar-2017-J-R/dp/tech-data/0062566938
%3FSubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0062566938} {:description Ad
d To Baby Registry, :url https://www.amazon.com/gp/registry/baby/add-item.html%3Fasin.0%3D0062566938%26SubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401
-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0062566938} {:description Add To Wedding Registry, :url https://www.amazon.com/gp/r
egistry/wedding/add-item.html%3Fasin.0%3D0062566938%26SubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D3
86001%26creativeASIN%3D0062566938} {:description Add To Wishlist, :url https://www.amazon.com/gp/registry/wishlist/add-item.html%3Fasin.0%3D0062566938%26Su
bscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0062566938} {:description Tell A
Friend, :url https://www.amazon.com/gp/pdp/taf/0062566938%3FSubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creati
ve%3D386001%26creativeASIN%3D0062566938} {:description All Customer Reviews, :url https://www.amazon.com/review/product/0062566938%3FSubscriptionId%3DAKIAI
6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0062566938} {:description All Offers, :url https://ww
w.amazon.com/gp/offer-listing/0062566938%3FSubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26cre
ativeASIN%3D0062566938}], :detail-page-url https://www.amazon.com/Tolkien-Calendar-2017-J-R/dp/0062566938%3FSubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D2
15401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0062566938, :asin 0062566938}]}
I got this from a result and wanted to traverse through it.

No, that's not XML. You can use get-in to traverse your nested structure.

As #jmargolistvt said, you have a Clojure data structure (nested maps & arrays), not xml.
Also, next time please print using the prn instead of println or similar, as it is important to keep the double-quote chars around strings like "Harper Voyager".
I simplified the data a bit, and have this:
(ns clj.core
(:require
[tupelo.core :as t]
[clojure.pprint :as pp]
)
(:gen-class))
(t/refer-tupelo)
(def data
{:total-pages 1, :total-results 1, :items
[ {:item-atributes
{:title "Tolkien Calendar 2017", :product-group "Book",
:manufacturer "Harper Voyager"
:author "J. R. R. Tolkien" }, :SalesRank 12016, :item-links [{:description "Technical Details" } ]
} ] } )
(pp/pprint data)
(defn -main [& args]
)
which yields:
~/clj > lein run
{:total-pages 1,
:total-results 1,
:items
[{:item-atributes
{:title "Tolkien Calendar 2017",
:product-group "Book",
:manufacturer "Harper Voyager",
:author "J. R. R. Tolkien"},
:SalesRank 12016,
:item-links [{:description "Technical Details"}]}]}
At this point, you can add
(newline)
(doseq [item (data :items) ]
(newline)
(pp/pprint item)
(newline)
(spyx (item :SalesRank))
(spyx (get-in item [:item-atributes :title])))
to get:
{:item-atributes
{:title "Tolkien Calendar 2017",
:product-group "Book",
:manufacturer "Harper Voyager",
:author "J. R. R. Tolkien"},
:SalesRank 12016,
:item-links [{:description "Technical Details"}]}
(item :SalesRank) => 12016
(get-in item [:item-atributes :title]) => "Tolkien Calendar 2017"
Your project.clj must include this to make the (spyx ...) part work:
:dependencies [
[tupelo "0.9.9"] ...

Related

Clojure - transform flat map to nested with predecided structure

I'm a newbie to clojure and i'm trying to convert a messages that come in a particular format into another.
ie, i have to convert something like:
{
:image-url ["https://image.png"],
:topic "Some title",
:id "88ebaf91-a01d-4683-9aa7-629bb3ecea01",
:short-description "Some Description",
:mobile-deeplink "https://deeplink.com/link",
:partner-name "partner"}
Into something like
{
:title "Some title",
:id "88ebaf91-a01d-4683-9aa7-629bb3ecea01",
:content {
:url ["https://image.png"],
:description "Some Description",
:deeplink "https://deeplink.com/link",
:partner "partner"}}
So in effect, there is a combination of renaming keys and nesting the flat map
What I have done so far was something on the lines of:
(let [message-map {
:image-url :purl
:topic :title
:partner-name :partner
:short-description :description
:mobile-deeplink :deeplink}]
(defn- map-to-body
[message]
(-> message
(clojure.set/rename-keys message-map)
;;some sort of (assoc-in) <- this is where i need help in
)))
Combining assoc-in, a path conversion table, and reduce could be more self-describing and maintainable. You could choose to reduce over either the conversion table or the input message, whichever makes more sense for the data you have.
(defn transform [m]
(let [pp '([:image-url [:content :url]]
[:topic [:title]]
[:id [:id]]
[:short-description [:content :description]]
;; etc.
)]
(reduce
(fn [o [mk ok]]
(assoc-in o ok (get m mk)))
{}
pp)))
You could chain-assoc-in here, but I think you are easier off
using select-keys. select-keys lets you extract only the keys
from a map into a new map, you need. So you can select :id/:title for
the outer map and the rest to assoc to :content.
E.g.
(require 'clojure.set)
(defn transform
[message]
(let [message-map {:image-url :url
:topic :title
:partner-name :partner
:short-description :description
:mobile-deeplink :deeplink}
renamed (clojure.set/rename-keys message message-map)]
(assoc ; XXX
(select-keys renamed [:title :id])
:content (select-keys renamed [:url :description :deeplink :partner]))))
(def src {:image-url ["https://image.png"],
:topic "Some title",
:id "88ebaf91-a01d-4683-9aa7-629bb3ecea01",
:short-description "Some Description",
:mobile-deeplink "https://deeplink.com/link",
:partner-name "partner"})
(def tgt {:title "Some title",
:id "88ebaf91-a01d-4683-9aa7-629bb3ecea01",
:content {
:url ["https://image.png"],
:description "Some Description",
:deeplink "https://deeplink.com/link",
:partner "partner"}})
(assert (= (transform src) tgt))

Clojure: Update value of record field

I have defined record to store User details and Address Details.
(defrecord User [id name address])
(defrecord Address [id location street city state])
(def usr (User. 1 "Abc"
(Address. 1 "Location 1" "Street" "NY" "US")))
I have updated "name" to "BCD" using the below code
(assoc usr :name "BCD")
Output:
#async_tea_party.core.User{:id 1, :name "BCD", :address #async_tea_party.core.Address{:id 1, :location "Location 1", :street "Street", :city "NY", :state "US"}}
(usr)
OutPut:
#async_tea_party.core.User{:id 1, :name "Abc", :address #async_tea_party.core.Address{:id 1, :location "Location 1", :street "Street", :city "NY", :state "US"}}
New value of name field has not updated and It still shows old value.
How can I update "name" field permanently in "User" record?
(def usr (User...)) is kind of immutable. You cannot change it.
When you do (assoc usr :name "BCD") you are not changing it. You create a new one. In order to do what you want you need an atom.
(def usr (atom (User. 1 "Abc"
(Address. 1 "Location 1" "Street" "NY" "US"))))
(:name #usr) ;; "Abc"
(swap! usr assoc :name "BCD")
(:name #usr) ;; "BCD"
This is called immutability and is one of the main reasons for me to like clojure so much.
To understand the reasoning why this behaviour is so beneficial, reading values and state really helped me

I can't figure out how to log/infof the string value of a LazySeq [duplicate]

This question already has an answer here:
Clojure printing lazy sequence
(1 answer)
Closed 7 years ago.
LazySeq is kicking my butt when I try to log its value.
(require '[clojure.tools.logging :as log])
(def layer->multipart [{:name "layer-name" :content "veg"} {:name "layer-tag" :content "abs"}])
(def field->multipart [{:name "field-id" :content "12345"} {:name "field-version" :content "v1"}])
(log/infof "concat is %s" (concat layer->multipart field->multipart))
; => 2016-02-16 16:31:11,707 level=INFO [nREPL-worker-38] user:288 - concat is clojure.lang.LazySeq#87177bed
; WTF is clojure.lang.LazySeq#87177bed?
I've check the How to convert lazy sequence to non-lazy in Clojure answer and it suggests that all I need to do is doall and all my dreams will come true. But alas...no.
(log/infof "concat is %s" (doall (concat layer->multipart field->multipart)))
; => 2016-02-16 16:31:59,958 level=INFO [nREPL-worker-40] user:288 - concat is clojure.lang.LazySeq#87177bed
; still clojure.lang.LazySeq#87177bed is not what I wanted
I've observed that (pr-str (concat layer->multipart field->multipart)) does what I want, but it makes no sense; The docs for pr-str say something about "pr to a string" and the docs for pr say "Prints the object(s) to the output stream that is the current value of *out*.". I don't want anything going to *out*, I just want the string value returned so the logger can use it!
(log/infof "concat is %s" (pr-str (concat layer->multipart field->multipart)))
; => 2016-02-16 16:42:02,927 level=INFO [nREPL-worker-1] user:288 - concat is ({:content "veg", :name "layer-name"} {:content "abs", :name "layer-tag"} {:content "12345", :name "field-id"} {:content "v1", :name "field-version"})
; this is what I wanted but I don't want anything going to *out*...or do I?
What do I have to do to get the effect of the pr-str variant without worrying about anything inadvertently getting dumped to stdout (I'm guessing that is what *out* is)? I want the lazy sequence to be fully realized for logging (it never gets too big...it only ends up as lazy as an accident of concat).
How can I log the full value of my LazySeq?
The problem is that behind the scenes the logger is calling .toString on your lazy sequence. Try this:
user=> (.toString (concat layer->multipart field->multipart))
;; "clojure.lang.LazySeq#87177bed"`
What you really want is to convert the contents of the sequence into a string. For example:
(log/infof "concat is %s" (apply str (concat layer->multipart field->multipart)))
;; Feb 16, 2016 5:10:19 PM clojure.tools.logging$eval420$fn__424 invoke
;; INFO: concat is {:name "layer-name", :content "veg"}{:name "layer-tag", :content "abs"}{:name "field-id", :content "12345"}{:name "field-version", :content "v1"}
By the way, pr-str is fine too. As it name says it prints to a string, not to *out*. You're using that string.

Clojure transform map values which are vectors to set

I am new at Clojure. I have a map like
{:title "The Little Schemer"
:authors [friedman , felleisen]}
I want to transform it to:
{:title "The Little Schemer"
:authors #{friedman , felleisen}}
I attempted like:
(def friedman {:name "Daniel Friedman" :birth-year 1944})
(def felleisen {:name "Matthias Felleisen"})
(defn old-book->new-book [book]
(set (:authors book)
)
)
(println (old-book->new-book {:title "The Little Schemer"
:authors [friedman , felleisen]}))
; => Output: #{{:name Daniel Friedman, :birth-year 1944} {:name Matthias Felleisen}}
; => Expected-Output: #{friedman , felleisen}
Here the defs friedman and felleisen gets executed and there results are getting transformed to set. But, I want the function names to be converted to set instead of their results.
First of all try to println this:
(println {:title "The Little Schemer"
:authors [friedman , felleisen]})
The output will be:
{:title The Little Schemer, :authors [{:name Daniel Friedman, :birth-year 1944} {:name Matthias Felleisen}]}
So, what happened here? As You know in this context friedman and felleisen is a variables, so, if You print them - they will be appear in a print message by values. For example:
(def a 1)
(println a)
Will print 1, because of a is just a variable.
The code which you shown here do what You want and when you print it - then values of variable of friedman and felleisen substitute by values.
Your vector of autors((:authors [friedman felleisen])) after disposing the function set will be converted to a set, what we saw from Your output.

Clojure read object from a file and extract data

I am very new to closure so I am not fully sure how to do this. If I have a file data.txt with the following:
[
{:name "Steve"}
{:name "Issac"}
{:name "Lucas"}
{...}
]
I want to be able to read the contents of each :name tag and do something with the return value (in this case it will be printing to the console). I looked up online and found there is a method called reader and I understand how to open a file.
The Closure syntax confuses me slightly so I am not sure how to do this.
there should be 2 possiblities:
1) raw clojure by means clojure.core/read-string
(read-string "['q 2 \"test\"]")
;; [(quote q) 2 "test"]
2) via clojure.edn/read-string
(clojure.edn/read-string "['q 2 \"test\"]")
;; ['q 2 "test"]
the 2nd one should be faster and safer (but does not eval and stuff),
but is only for edn format (this is a subset of clojure code)
the string dummy (i.e from your data.txt)
;; a string, just for demo
(def s "[{:name \"Steve\" email: \"foo#bar.com\" }
{:name \"Issac\"}
{:name \"Lucas\"}]")
the rest is plain clojure, if you have trouble with clojure maps here is the doc
(doseq [name (map :name (clojure.edn/read-string s))]
(println name))
;; Steve
;; Issac
;; Lucas
;; nil