Fork me on GitHub
#beginners
<
2021-01-02
>
roelof10:01:27

why is here s unknown

roelof10:01:33

(defn reverse-string  
  []
  ''
  [s]
  (let [[first & rest] (seq s)]
    (conj (reverse-string rest) first)))

bronsa10:01:35

what is [] '' supposed to be?

roelof10:01:22

I try to say that if the string is empty . it must be returning a empty string

Kannan Ramamoorthy11:01:57

I assume you are trying to create a mult-arity function one for no-arg and another for normal string. Just to correct the syntax, your function would look like this.

(defn reverse-string  
  ([]
  "")
  ([s]
  (let [[first & rest] (seq s)]
    (conj (reverse-string rest) first))))

Kannan Ramamoorthy11:01:17

The function contain logical error, the zero arity variant will not be called for empty string/seq rather when it is called with no argument. I leave it upto you to experiment.

roelof11:01:47

Then I think I need a if then in the [s] part and deleted the [] part

Kannan Ramamoorthy11:01:13

yeah. thats right..

roelof11:01:02

actual: java.lang.ClassCastException: class java.lang.String cannot be cast to class clojure.lang.IPersistentCollection

roelof11:01:15

`

(defn reverse-string  
  [s]
  (if (empty? s)
    ""
    (let [[first & rest] (seq s)]
      (conj (reverse-string rest) first))
    ))

Jakub Zika11:01:03

Yeah, conj wont work here (`cons` does).

Mno11:01:01

I mean youll get a nested list of lists, but the order will be correct

roelof11:01:33

(defn reverse-string  
  [s]
  (if (empty? s)
    ""
    (let [[first & rest] (seq s)]
      (cons (reverse-string rest) first))
    ))


(reverse-string "robot")
gives now
; Execution error (IllegalArgumentException) at reverse-string/reverse-string (form-init5861935267258777464.clj:8).
; Don't know how to create ISeq from: java.lang.Character

Joe12:01:27

Check out the argument order forhttps://clojuredocs.org/clojure.core/cons - it's the other way around from https://clojuredocs.org/clojure.core/conj: collection last instead of first

roelof12:01:23

oke, it compiles but does not reverse ir

Joe12:01:06

No, because (*cons* \r '(\t \o \b \o)) will put r at the start šŸ™‚ You could check out the different between the outputs of these to see the difference between cons and conj on different types of collections:

(cons \r `(\t \o \b \o))
(cons \r `[\t \o \b \o])

(conj `(\t \o \b \o) \r)
(conj [\t \o \b \o] \r)
But I think what you want to get out of your recursive function is a string, correct? If so I would think it would be better to have the recursive part of your function use string concatenation rather than collection operators.

šŸ‘ 6
mathias_dw15:01:17

str is your friend šŸ™‚

roelof16:01:37

Thanks, did not think about that one

roelof16:01:05

but replacing cons with str does do the job

roelof11:01:46

so I think back to the drawing board

pavlosmelissinos13:01:48

I'm beginning to realize I don't understand time. šŸ˜„ Assuming {:deps {clojure.java-time/clojure.java-time {:mvn/version "0.3.2"}}} and (require '[java-time :as t]) : 1. What's the correct way to compare a t/local-date to a t/instant? t/before? and t/after? don't seem to work on those types. e.g. this doesn't work: (t/before? (t/local-date ".yyyy" "31.12.2020") (t/instant)) => class java.time.Instant cannot be cast to class java.time.chrono.ChronoLocalDateTime 2. What's the correct way to convert a date string to a java-time instant referring to the beginning of that date (midnight)? e.g. as expected, this doesn't work either (t/instant ".yyyy" "31.12.2020") because the time information is missing from the string representation (`Unsupported field: InstantSeconds`)

pavlosmelissinos13:01:05

(t/truncate-to (t/local-date-time (t/local-date ".yyyy" "31.12.2020")) :days) gives a local-date-time at the beginning of the day but I still haven't found a way to convert or compare that to an instant and the expression already seems way too verbose for what I'm trying to do (at least by clojure standards)...

Christian14:01:20

I want to thread two list into a map function, but I cant figure out how. This is what I want to have in the end:

(reduce + (map #(if (= %1 %2) 0 1) strand1 strand2))
And this is what I tried. I was hoping for better readability. What do you think about that?
(->>
   [strand1 strand2]
   (#(if (= %1 %2) 0 1))
   (map)
   (reduce +)
   )
As far as I understand all the work around threading is for better readability, isn't it?

mathias_dw15:01:31

what do you want in the end? The number of pair-wise equal elements?

mathias_dw15:01:49

I would write

(->> (map (fn [a b] (if (= a b) 1 0)) strand1 strand2)
                            (reduce +))

mathias_dw15:01:36

if you really want the threading to start with the data:

(->> [strand1 strand2]
(apply map (fn [a b] (if (= a b) 1 0)))
                            (reduce +))

mathias_dw15:01:48

(sorry, the indentation is messed up)

Max Deineko16:01:12

@christiaaan First to note where your second snippet will fail to evaluate successfully: ā€¢ the anonymous function will get a single argument [strand1 strand2] but expects two -- to fix that you'd need to apply it; ā€¢ (map) after that will be evalutated as (map 0) or (map 1) -- since the function above returns a scalar ā€¢ (reduce +) expects a collection as argument but will get a second function instead -- but at this point we already lost information we need to compute what the first snippet does. For the sake of exercise you can besides already suggested possibilities use e.g. (map vector s1 s2) :

(map vector '(1 2 3 4) '(:a :b :c))
;; => ([1 :a] [2 :b] [3 :c])
to combine two sequences into pairs:
(->> (map vector '(1 2 0 7 4) '(:a :b 0 7))
     (map (fn [[x y]] (if (= x y) 0 1)))
     (reduce +))
or
(->> [s1 s2]
     (apply map vector)
     (remove #(apply = %))
     count)
Can't say which is more readable, since it's somewhat subjective -- in this case I think I'd prefer the original expression. Ideally your environment should be able to help you evaluate and inspect intermediate values easily -- e.g. in the second snippet above, it's probably easier to proceed adding threading steps one by one, when you know that what you have so far works and can see what it produces.

Max Deineko16:01:01

@U0G4JDMLM if I'm reading OP correctly, it's the number of pairwise differing elements šŸ™‚

Christian16:01:39

Thank you for all the replies. I think I get why I failed. The remove is interesting, I should remember it.

roelof17:01:54

Is cursive "better" then vs code with calva for learning clojure and later maybe use a web framework ?

Michael W17:01:07

It's about the same, either one works great for learning, and they both work well in general. calva has a bit of an edge with documentation though.

roelof17:01:05

oke, then I think I stay with vs code and calva

seancorfield17:01:48

My general advice for beginners, as far as editors are concerned, is: use the editor you already know best if it has a Clojure integration (else use an editor that is at least similar to one you already know). It's helpful to not have to learn both Clojure and a completely new editor at the same time!

šŸ‘ 36
roelof17:01:22

@seancorfield there you are completety right

caumond20:01:42

hi, I consider building a map with a complex key (a potentially complex value map). Will it be idiomatically efficient or need I to replace the map with some hash mechanisms?

phronmophobic20:01:36

as long as the map key doesn't have any mutable data, that seems idiomatic to me

caumond20:01:07

and efficient? I'll have a certain volumetry to adress

noisesmith20:01:50

hash maps are immutable and their hash value is cached

noisesmith20:01:25

"replace the map with some hash mechanisms" is just duplicating the work clojure.core already does

āž• 3
caumond20:01:40

it is clearly answering my querstion, @noisesmith

noisesmith20:01:44

of course if the map hashing becomes a bottleneck you'll want a custom lookup (eg. indirection by id with an id generator?) but in that case the usage of the map itself will be an even bigger bottleneck in most cases

caumond20:01:43

yes, clear

caumond20:01:06

but I need some stress tests before arriving to that point

Ben Sless06:01:45

using collections as values does have a hidden cost. While the hash values are cached, at the end of getting from a map there is a comparison between the found key and the provided one (as they might) have the same hash. Comparing collections is very expensive in relation to getting a value from a map

Ben Sless06:01:08

This is where it happens:

public Object find(int shift, int hash, Object key, Object notFound){
		int bit = bitpos(hash, shift);
		if((bitmap & bit) == 0)
			return notFound;
		int idx = index(bit);
		Object keyOrNull = array[2*idx];
		Object valOrNode = array[2*idx+1];
		if(keyOrNull == null)
			return ((INode) valOrNode).find(shift + 5, hash, key, notFound);
		if(Util.equiv(key, keyOrNull))
			return valOrNode;
		return notFound;
	}

noisesmith06:01:48

this is a great point, but also note that the collection equality check is cheap if the key used for lookup and the key in the map are the same object in memory

Ben Sless07:01:18

True, for every other case, however, I have seen a sharp drop off in performance when hashing collections, making it a non-starter for anything which requires speed or high throughput =\ A plausible middle ground is creating synthetic keys via different methods, either string concatenation or keyword generation

caumond20:01:35

Somehow I missed that messages. Thx @UK0810AQ2 , it s helpfull. @noisesmith up to now, I have no guarantee that the same value share the same object. Then re designed it and finally found a different approach to simplify my maps and more surely reuse the same value. That solution, not fully implemented yet, start to sound better!! Thx a lot.

Christian20:01:45

Is it possible to see which values have been run by a memoized functions to avoid collisions?

caumond20:01:07

I'm not sure to catch your point, but I put the values in the map exactly to remember which one has already been executed and which has not

caumond20:01:29

does it answer your question?

Christian20:01:55

I'd do so as well, but with this exercise I have no control about the arguments. I could define a mutable global map for that? Sounds very un-clojure

caumond20:01:56

Uncloaked (;p

Christian20:01:56

Is there a way to call a function without arguments that gives a new value everytime? like 1,2,3,4,5

phronmophobic20:01:04

(fn [] (rand-int 6))

Christian20:01:06

yes, but it would be possible to get 1,3,1,4,5,1

Christian20:01:34

I was thinking about increment everytime I call it. To give something a unique ID

seancorfield20:01:45

@christiaaan You can use let-over-`defn`:

(let [value (atom 0)]
  (defn values [] (swap! value inc)))
Then each call returns a new incremented value.

noisesmith20:01:23

that also creates global top level singleton, so it often ends up evolving to one more level of nesting:

(defn value-generator []
  (let [value (atom 0)]
    (fn [] (swap! value inc))))

Christian20:01:09

That sounds really good. I have no idea what happens there, though

Christian20:01:33

What does atom do?

seancorfield20:01:58

atom is for managed mutability in Clojure.

Christian20:01:16

okay, I should read more about this

seancorfield20:01:17

swap! applies the function to the current value "atomically"

noisesmith21:01:01

and importantly in this case, it returns the new value

seancorfield21:01:19

And you deref an atom to get the value out. swap! just happens to return the new value which is convenient for the case above.

Christian21:01:38

With the code form @noisesmith I have to ((value-generator)) and it returns 1, if I call it again it's still 1.

Christian21:01:51

or do I call it in the wrong way?

caumond21:01:40

it is because value-generator is a builder

caumond21:01:56

you need to call it once, the function which is given has the expected behavior

noisesmith21:01:36

@christiaaan eg. (def values (value-generator)) - then you have something you can call to get numbers

noisesmith21:01:54

but the point is its meant to be more flexible, so you don't need to bind it in a def

noisesmith21:01:15

but if you just need a def @seancorfieldā€™s original version is better

Christian21:01:26

(defn value-generator []
  ;; credit to seancorfield and noisesmith @ clojurians slack
  (let [value (atom 0)]
    (fn [] (swap! value inc))))

(defn test[]
  (let [id (value-generator)]
    id))

((test))
;; => 1

((test))
;; => 1

seancorfield21:01:33

e.g.,

(defn -main [& args]
  (let [a-values (value-generator)
        b-values (value-generator)]
    ... (a-values) ...
    ... (b-values) ...
    ...))
so now you can have two streams of values.

noisesmith21:01:08

@christiaaan you're creating a new object and calling it once and throwing it away each time

noisesmith21:01:30

you need to hold onto the object in order to call it again

noisesmith21:01:30

(ins)user=> (def values (value-generator))
#'user/values
(ins)user=> (values)
1
(cmd)user=> (values)
2

Christian21:01:24

So I need a global variable that I call again and again?

noisesmith21:01:34

no, see @seancorfieldā€™s example

seancorfield21:01:36

@christiaaan See my -main example.

noisesmith21:01:47

you need to hold onto it in some context and reuse it, that's all

seancorfield21:01:52

You can pass the value streams into functions you call from -main

Christian21:01:18

Maybe I think to object oriented about that šŸ˜•

noisesmith21:01:47

if you think objected oriented about it it makes perfect sense

noisesmith21:01:54

it's a factory returning a function with a memory

noisesmith21:01:13

you need a handle to the function returned in order to use that memory

Christian21:01:32

So Sean's example is wrapped in the main function and has a permanent handle on the builder

noisesmith21:01:54

not permanent, but scoped to the body of the let inside -main, but yes

noisesmith21:01:31

it's not the builder he holds on to either - the builder is global

Christian21:01:31

I don't have that in my exercise, that's what is causing me problems

noisesmith21:01:36

he has a handle to the thing built

noisesmith21:01:00

(unlike what you did, building a new one with a fresh memory every time)

Christian21:01:28

I think I have an Idea, will come back to you. Thank you very much for the patient explanation

caumond21:01:13

yes, the help given here is amazing. Thx all guys. I'm still not skilled enough to participate, but I'm on the way, (;p

noisesmith21:01:55

@christiaaan value generator can also be used like this (repeatedly captures the function and uses it... repeatedly to generate a sequence)

user=> (repeatedly 100 (value-generator))
(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100)

mathias_dw21:01:34

@christiaaan i was just going to say (before @noisesmithā€™s last post) that it sounds a bit like you should play with the lazy sequences in clojure a bit more. The previous answers are of course correct, but maybe what you're looking for is rather just something like (range)

noisesmith21:01:14

yeah, for higher order functions that's often the right thing

Christian21:01:36

Would range remember the last value it gave me?

dpsutton21:01:57

can you post the exercise you are working on?

noisesmith21:01:06

no, but inside a higher order function you often don't need to use state like that

noisesmith21:01:41

it's a common fp technique to replace hidden state / generators with iterative consumption of a sequence in a HoF

Christian21:01:14

I think I overthink it, but I want to make it work. https://exercism.io/my/solutions/17ad9da210054c20a08e31fd4987ea6a " Manage robot factory settings. When robots come off the factory floor, they have no name. The first time you boot them up, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811. Every once in a while we need to reset a robot to its factory settings, which means that their name gets wiped. The next time you ask, it will respond with a new random name. The names must be random: they should not follow a predictable sequence. Random names means a risk of collisions. Your solution must ensure that every existing robot has a unique name."

Christian21:01:16

It works now, like I want it:

(ns robot-name)

(defn value-generator []
  ;; credit to seancorfield and noisesmith @ clojurians slack
  (let [value (atom 0)]
    (fn [] (swap! value inc))))

(def bot-serial-number (value-generator))

(defn robot []
  bot-serial-number)

(defn robot-name [robot] 
)

(defn reset-name [robot] ;; <- arglist goes here
  ;; your code goes here
)

(def firstbot (robot))
(firstbot)
;; => 1
(def secondbot (robot))

(secondbot)
;; => 2

noisesmith21:01:31

I find it extremely strange that robot returns bot-serial-number instead of calling it

Christian21:01:36

It's just the start. In my first draft I took a random value and made the name-string from it.

noisesmith21:01:03

like, if you called secondbot again, it would return 3

noisesmith21:01:24

(so would firstbot, if you called that first)

Christian21:01:25

My idea was, that i can get a new Id this way, when I have to rename it

noisesmith21:01:54

(= firstbot secondbot) should return true

noisesmith21:01:58

you haven't assigned anything

noisesmith21:01:03

you've shared copies of a generator

Christian21:01:11

hm, good argument

noisesmith21:01:42

by "should" above I mean, that's how I read your code, I don't actually think that's a good idea

Christian21:01:01

Me neither, now that you mentioned it

Christian21:01:13

(defn robot []
  (bot-serial-number))


(def firstbot (robot))
firstbot
;; => 1

(def secondbot (robot))
secondbot
;; => 2

(= firstbot secondbot)
;; => false

Christian21:01:21

Now they have unique IDs.

dpsutton21:01:22

also, "they should not follow a predictable sequence" seems to me to disallow this autoincrementing approach you're attempting here

Christian21:01:13

I wanted to use the id as a seed for some random number mumbo-jumbo. But I have not read that far into the rand-possibilites in clojure

dpsutton21:01:29

i don't think that will necessarily prevent collisions though. but my knowledge of random number generators and seeds is pretty superficial

Christian21:01:26

I could just generate a list, randomize it and pick the nth element via id...

šŸ‘ 3
Christian21:01:26

a lazy permutation of [A-Z]{2}\d{3} could do the trick

dpsutton21:01:52

i doubt that the integer sequences generated by different seeds are distinct like that

dpsutton21:01:30

(let [r1 (java.util.Random. 23) r2 (java.util.Random. 24) f #(into #{} (take 50000 (iterator-seq (.iterator (.ints %)))))] (clojure.set/intersection (f r1) (f r2)))
#{1304654219}

dpsutton21:01:55

here using seeds 23 and 24 there's a common element to the first 50,000 elements

dpsutton21:01:05

and in fact its of course impossible to partition the longs into partitions like this as each long can't select a set of longs not included in another long's set due to the pigeonhole principle.

Christian21:01:45

I was thinking: I make a list of every possible name from AA000 to ZZ999. Then pick the nth element, which is my bots id.

Christian21:01:06

This is predictable. So I use a permutation of that list.

šŸ‘ 3
Christian21:01:25

To keep memory in check, best case would be to make that list a lazy seq

dpsutton21:01:39

that sounds like exactly a "predictable sequence" the exercise is telling you to avoid. and you'd still need to remember which you have picked to avoid that choice. and if you're gonna keep a set of chosen bot names, there's no need for the original sequence

Christian21:01:15

you would have to know the permutation of that list to predict the next name

Christian21:01:45

as the id is unique and every increasing, I could not pick a name twice, no need to remember.

andy.fingerhut21:01:45

"predictable" is not a very precisely defined word, I don't think. What you can predict depends upon what you know about the code that is generating the sequence, really. If you know it is a simple pseudo-random number generator of several kinds, those are all deterministic, so if you know (or can infer from observing their outputs) the internal state, you can predict the rest of the sequence after that.

andy.fingerhut21:01:10

If you have some form of random number generator that you believe is difficult enough for others to predict, you can use it to generate random IDs, and simply keep trying if a generated one matches one generated earlier -- that works well as long as you don't use over about half of the possible values -- using too large a fraction of possible values will slow down that technique quite a bit.