How to change a value of sub maps of a map? - clojure

I am new to Clojure and functional programming and now I am stuck with a problem. I get such a data structure:
{
:service1 \a
:service2 \b
:service3 \c
:default \d
:alert-a {
:duration "00:00-23:59"
:if-alert true
:continuous-times 2
:time-interval [2 6 9 15 30 60]
:times -1
}
:alert-b {
:duration "09:00-23:00"
:if-alert true
:continuous-times 2
:time-interval [2 6 9 15 30 60]
:times -1
}
:alert-c {
:duration "00:00-23:59"
:if-alert true
:continuous-times 5
:time-interval [5]
:times 1
}
:alert-d {
:duration "00:00-23:59"
:if-alert true
:continuous-times 5
:time-interval [5 15 30 60]
:times -1
}
}
This is something read from a config file. I want to change all the :duration value to a DateTime object using clj-time. So I can get something like:
{
:service1 \a
:service2 \b
:service3 \c
:default \d
:alert-a {
:duration DateTime Object
:if-alert true
:continuous-times 2
:time-interval [2 6 9 15 30 60]
:times -1
}
:alert-b {
:duration DateTime Object
:if-alert true
:continuous-times 2
:time-interval [2 6 9 15 30 60]
:times -1
}
:alert-c {
:duration DateTime Object
:if-alert true
:continuous-times 5
:time-interval [5]
:times 1
}
:alert-d {
:duration DateTime Object
:if-alert true
:continuous-times 5
:time-interval [5 15 30 60]
:times -1
}
}
But the data structure is immutable. This is an easy problem in other languages but now I don't know how to do it after a whole afternoon.
So can anyone give me some suggestions? Am I using a bad data structure? Or this problem can be somehow solved in a functional way.

Although you are working with immutable datastructures, you can easily and efficiently return new datastructures that are based on the originals.
In this case, the simplest (if repetitive) solution would be:
(-> m
(update-in [:alert-a :duration] parse-duration)
(update-in [:alert-b :duration] parse-duration)
(update-in [:alert-c :duration] parse-duration)
(update-in [:alert-d :duration] parse-duration))
The important thing to realize here is that update-in does not mutate the datastructure it's working on. Instead it returns a new datastructure with the modifications applied.
The threading macro -> allows the new datastructure to be threaded through the update-in operations, so that the final returned value is the original datastructure with all of the updates applied.
The parse-duration function would probably look a bit like this:
(defn parse-duration
"Convert duration in HH:MM-HH:MM format"
[s]
(let [[t1 t2] (clojure.string/split s #"-"))
(Period. (clj-time.coerce/to-date-time t1)
(clj-time.coerce/to-date-time t2)))

In functional programming you don't modify collection, but instead create new collection with needed values substituted by new ones. Fortunately, Clojure comes with a bunch of useful functions for this. For your case update-in should work well. It takes a collection (e.g. map), sequence of nested keys and a function to apply to the most nested value defined by key sequence. For example:
> (def m {:a 1 :b 2 :c {:c1 1 :c2 2}})
#'sandbox5448/m
> m
{:a 1, :c {:c1 1, :c2 2}, :b 2}
> (update-in m [:c :c1] str)
{:a 1, :c {:c1 "1", :c2 2}, :b 2}
Note how value 1 from key sequence [:c :c1] was converted to "1".
So, converting :duration field of :alert-a to DateTime is as easy as writing:
> (update-in your-map [:alert-a :duration] string-to-date)
where string-to-date is you converter function.

Related

Failed to update a map with assoc in Clojure [duplicate]

This question already has answers here:
Why does (assoc-in everything ...) not change everything?
(2 answers)
Closed 2 months ago.
I have following map in my Clojure code:
typeList {"int" {"type" ["integer"]
"minimum" -2147483648
"maximum" 2147483647}
"bigint" {"type" ["integer"]
"minimum" -9223372036854775808
"maximum" 9223372036854775807}}
I am trying to add some new values to that map and I am using assoc key for that; however it seems it is not adding the new value since the println is not giving the new keyword.
For example, let's add "asd" to the map:
(assoc typeList "asd" {"type" ["integer"]})
However, when I try to print the new list, it returns as following:
(println typeList)
{int {type [integer], minimum -2147483648, maximum 2147483647}, bigint
{type [integer], minimum -9223372036854775808, maximum
9223372036854775807}}
Am I missing something? Couldn't figure that out since I am newbie in Clojure.
assoc will not change the existing value typeList, instead it returns a new value with the key "asd". In other words, typeList will be unchanged and the return value from assoc will contain the "asd" key.
Try this instead:
(def updatedTypeList (assoc typeList "asd" {"type" ["integer"]}))
(get typeList "asd")
;; => nil
(get updatedTypeList "asd")
;; => {"type" ["integer"]}

How to use a command all the 4th item nested lists in netlogo

I have built a long nested list having the following profile:
set my-list [A 1 2 3 4] [B 5 6 7 8] [C 9 10 11 12]
I'd like to apply the meancommand to the fourth item of each nested lists, so in the example to
4 8 12
but without building a list in loop that would look like [4 8 12] (to save computing time).
Is it possible ?
using let comp mean (item i item 4 (my-list)) or let comp mean (item 4 (my-list)) aren't obviously working.
The answer would be useful to other part of the model that I'm building.
Thanks for your time.
The map primitive is very well suited for these sorts of calculations with lists. It allows you to perform a reporter separately for each part of a list and then returns the results as a new list.
let test-list [1 2 3]
show map [x -> x + 1] test-list
;> [2 3 4]
In your case, you would use map to cycle through your list of lists, and use the item primitive to extract the necessary number from each sublist (map [x -> item 4 x ] my-list). This then returns them as a list of which you can take the mean.
to check-mean-2
let my-list [["a" 1 2 3 4] ["b" 5 6 7 8] ["c" 9 10 11 12]]
let my-mean mean map [x -> item 4 x ] my-list
print my-mean
end
EDIT: Although mine looks more efficient on first glance, Matteo's version actually runs quicker (at least on my machine)
globals [my-list]
to setup
set my-list [["a" 1 2 3 4] ["b" 5 6 7 8] ["c" 9 10 11 12]]
end
to check-mean
let timer-list []
repeat 10 [
reset-timer
repeat 1000000 [
let the-sum 0
let i 0
while [i < length my-list] [
set the-sum (the-sum + item 4 (item i my-list))
set i i + 1
]
let my-mean the-sum / i
]
let the-timer timer ; ~0.207
show the-timer
set timer-list lput the-timer timer-list
]
show word "mean: " (mean timer-list) ; 0.210
end
to check-mean-2
let timer-list []
repeat 10 [
reset-timer
repeat 1000000 [
let my-mean mean map [x -> item 4 x ] my-list
]
let the-timer timer
show the-timer
set timer-list lput the-timer timer-list
]
show word "mean: " (mean timer-list) ; 0.235
end
ANOTHER EDIT: Finally two more versions using reduce instead of map. Version 3 is the fastest of them all but you should take notice of the fact that my-list has a 0 added to it in this version. This might make is slightly less conveniet for other purposes. You can also add this 0 to it during the calculation as seen in version 4, but that drives up the time again.
to check-mean-3
set my-list [0 ["a" 1 2 3 4] ["b" 5 6 7 8] ["c" 9 10 11 12]]
let timer-list []
repeat 10 [
reset-timer
repeat 1000000 [
let my-sum reduce [ [x y] -> x + item 4 y] my-list
let my-mean my-sum / (length my-list - 1)
]
let the-timer timer
show the-timer
set timer-list lput the-timer timer-list
]
show word "mean: " (mean timer-list) ; 0.170
end
to check-mean-4
set my-list [["a" 1 2 3 4] ["b" 5 6 7 8] ["c" 9 10 11 12]]
let timer-list []
repeat 10 [
reset-timer
repeat 1000000 [
let my-new-list fput 0 my-list
let my-sum reduce [ [x y] -> x + item 4 y] my-new-list
let my-mean my-sum / (length my-list - 1)
]
let the-timer timer
show the-timer
set timer-list lput the-timer timer-list
]
show word "mean: " (mean timer-list) ; 0.226
end
First things first: did you mean to say that such nested list is built such as the one below?
set my-list [[A 1 2 3 4] [B 5 6 7 8] [C 9 10 11 12]]
Note the extra pair of square brackets, that make this actually a list of lists. Even if that was the case, NetLogo wouldn't let you use that syntax:
Either because A, B and C are lacking quotation marks if you intend them to be strings;
Or because, if A, B and C are variables, NetLogo expects literal values (we can get around this problem by using (list ...) instead of []).
In the first case, it would have to be:
set my-list [["a" 1 2 3 4] ["b" 5 6 7 8] ["c" 9 10 11 12]]
In the second case, it would have to be:
set my-list (list (list a 1 2 3 4) (list b 5 6 7 8) (list c 9 10 11 12))
All of the above just to make sure we are all on the same page (in general, please make sure that the code you post in your question is valid for the language you are asking about. As you can see, it would save a lot of time and space!).
Anyway I imagine that what you come up with is something of this type:
[["a" 1 2 3 4] ["b" 5 6 7 8] ["c" 9 10 11 12]]
I would use a while loop to iterate through the inner lists. You can create a local variable to keep track of the sum of the numbers you extract as you iterate through the inner lists, and then divide that sum by the number of times you extracted a number:
to check-mean
let my-list [["a" 1 2 3 4] ["b" 5 6 7 8] ["c" 9 10 11 12]]
let the-sum 0
let i 0
while [i < length my-list] [
set the-sum (the-sum + item 4 (item i my-list))
set i i + 1
]
print the-sum / i
end
From the answers above and adding a step in the procedure (mean and sd for groups of items having the same "region" in the list), here-below is my final code using map as the mean and sd are already calculated in a while-loop. Moreover, I assumed that calculating manually the standard deviation would create even more lists of list and complicate the code.
to create-successor-list
set successor-list map [inner-list -> (list inner-list 0 0 ) ] region-data
let i 0
while [i < length region-data] [
let current-item item i region-data
set temp-profitability-list (filter [current-inner-list -> (item 1 current-inner-list = current-item)] profitability-list )
set prof-mean mean map [x -> item 4 x ] temp-profitability-list
set prof-sd standard-deviation map [x -> item 4 x ] temp-profitability-list
set successor-list replace-item i successor-list (replace-item 1 item i successor-list (prof-mean))
set successor-list replace-item i successor-list (replace-item 2 item i successor-list (prof-sd))
set i i + 1
set temp-profitability-list [ ]
]
end

Getting values from multiple lists to one in Red language

I want to get values from 2 series into one but it is not working:
I have 2 series:
a: [1 2 3]
b: [4 5 6 7]
I want to get all values in one list, so that I can access them as allv/1, allv/2... allv/7.
Following is not working since it makes a series of series and not series of values:
allv: [a b]
print allv ; => prints all 6 values, but following do not work:
print allv/1 ; => gives a (desired is 1)
print allv/2 ; => gives b (desired is 2)
print allv/3 ; => gives none (desired is 3)
I tried following function to combine values in one series:
mycombine: function [ll][
temp: []
foreach l ll [
repeat i length? l [
temp: append temp l/:i ] ]
temp]
mycombine [a b]
But above gives error:
*** Script Error: length? does not allow word! for its series argument
*** Where: length?
*** Stack: rncombine
The series has got converted into word and is not working.
How can I solve this?
Just append
a: [1 2 3]
b: [4 5 6 7]
c: [8 9 10]
d: [11 12 13 14]
>> allv: append a b
== [1 2 3 4 5 6 7]
>> a
== [1 2 3 4 5 6 7]
does what you want. But beware even a contains all values, as you have appendend the values of b to the block a. If you want a new block you have to use
allv: append copy a b
If you want to append more series you can do
>> foreach x [a b c d] [ append [] reduce x]
== [1 2 3 4 5 6 7 8 9 10 11 12 13 14]
Instead of reduce also get x is working.
Another way of combining
>> compose [(a) (b) (c)]
== [1 2 3 4 5 6 7 8 9 10 ]

Method cannot be found but clojure.reflector shows otherwise

I am trying to create a dstu2 client from clojure using hapi fhir. As a template i use https://github.com/jamesagnew/hapi-fhir/blob/master/examples/src/main/java/example/GenericClientExample.java
But I am not able to execute
ctx.setPerformanceOptions(PerformanceOptionsEnum.DEFERRED_MODEL_SCANNING);
in clojure
What i do is the following:
(def fhir-context (. FhirContext forDstu2))
=> #'emrspp.fhir-resources/fhir-context
(def opts PerformanceOptionsEnum/DEFERRED_MODEL_SCANNING)
=> #'emrspp.fhir-resources/opts
But then the followint fails:
(.setPerformanceOptions fhir-context opts)
=>
CompilerException java.lang.IllegalArgumentException: No matching method found: setPerformanceOptions for class ca.uhn.fhir.context.FhirContext
clojure reflection gives the following :
(pprint (filter #(= "setPerformanceOptions" (str (:name %))) (:members (r/reflect fhir-context))))
=>
~
({:name setPerformanceOptions,
:return-type void,
:declaring-class ca.uhn.fhir.context.FhirContext,
:parameter-types [ca.uhn.fhir.context.PerformanceOptionsEnum<>],
:exception-types [],
:flags #{:varargs :public}}
{:name setPerformanceOptions,
:return-type void,
:declaring-class ca.uhn.fhir.context.FhirContext,
:parameter-types [java.util.Collection],
:exception-types [],
:flags #{:public}})
nil
The imports section is:
(:import [org.hl7.fhir.instance.model.api IBaseOperationOutcome IBaseResource ]
7 [ca.uhn.fhir.context FhirContext PerformanceOptionsEnum]
8 [ca.uhn.fhir.model.base.resource BaseOperationOutcome ]
9 [ca.uhn.fhir.model.dstu2.resource Bundle
10 Conformance Observation
11 OperationOutcome
12 Organization Parameters
13 Patient Provenance]
14 [ca.uhn.fhir.model.dstu2.valueset AdministrativeGenderEnum IssueSeverityEnum]
15 [ca.uhn.fhir.model.primitive DateDt IdDt InstantDt]
16 [ca.uhn.fhir.rest.api MethodOutcome SummaryEnum ]
17 [ca.uhn.fhir.rest.client IGenericClient ServerValidationModeEnum interceptor.LoggingInterceptor ]
18 [ca.uhn.fhir.rest.method.SearchStyleEnum ]
19 [ca.uhn.fhir.rest.param.DateRangeParam ]
20 [ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException ]
21 )
with no :requires except pprint and reflection
Any hits of what happens regarding the method setPerformanceOptions that appears to be there but be executed ????
I figured it out after hours. I closer look at the namespace : http://hapifhir.io/apidocs/ca/uhn/fhir/context/FhirContext.html
reveals that the passing argument needs to be a java collection and thus
(.setPerformanceOptions fhir-context opts)
must be changed to
(.setPerformanceOptions fhir-context (java.util.ArrayList. [opts]))
or simplier
(.setPerformanceOptions fhir-context [opts] )

Update tree in clojure

I have a tree (list of lists), I would like to modify a selected node of the tree.
[
[:a1]
[
[:b1, b2]
[:c1, c2]
]
]
For example, I would like to append b3 to the node (array) with b entries.
[
[:a1] ; 0
[ ; 1
[:b1, b2, b3] ; 1, 0
[:c1, c2] ; 1, 1
]
]
Question: how to update a node given list of indices where I can find the target node ([1, 0])? In other languages, with a mutable tree I could simply find the array, and do target_node.append("b3"), which is impossible in clojure.
I would like to avoid explicitly using mutable Java objects.
Related
Update hierarchical / tree structure in Clojure
update-in will do what you want:
user=> (def t [[:a1] [[:b1 :b2] [:c1 :c2]]])
#'user/t
user=> (update-in t [1 0] conj :b3)
[[:a1] [[:b1 :b2 :b3] [:c1 :c2]]]