Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure: Complex Iteration Across a List?

I'd like to take a number, 20, and a list. '(1 2 3 4 5 6 7 8 9 10), and return a collection containing two values for each value in the original list: the original value paired with the remainder when diving 20 by that value. It would be nice if the original values were somehow keyed to the remainders, so that I could easily retrieve each number that produced a particular remainder. Basically I want some function func:

user=> (func 20 '(1 2 3 4 5 6 7 8 9 10))
'(:0 1, :0 2, :2 3,... :20 0)

I am however having an incredibly difficult time just figuring out how to iterate through the list. Could someone help me understand how to use the elements of a list independently, and then how to return the element that 20 was divided by and if it returns a remainder?

My thought was to use something like this in a program that calculates square roots. If the numbers were keyed by the remainder, then I could query the collection to get all numbers that divide the input with a remainder of 0.


Here was my preliminary way of going about that.

;; My idea on the best way to find a square root is simple.
;; If I want to find the square root of n, divide n in half
;; Then divide our initial number (n) by all numbers in the range 0...n/2 
;; Separate out a list of results that only only return a remainder of 0.
;; Then test the results in a comparison to see if the elements of our returned 
;; list when squared are equal with the number we want to find a square root of.
;; First I'll develop a function that works with evens and then odds

(defn sqroot-range-high-end [input] (/ input 2))
(sqroot-range-high-end 36) ; 18

(defn make-sqrt-range [input] (range (sqroot-range-high-end (+ 1 input))))
(make-sqrt-range 36) ; '(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18)

(defn zero-culler [input] (lazy-seq (remove zero? (make-sqrt-range input))))
(zero-culler 100) ; '(1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18)

(defn odd-culler [input] (lazy-seq (remove odd? (zero-culler input))))
(odd-culler 100) ; '(2 4 6 8 10 12 14 16 18)

;;the following is where I got stuck
;;I'm new to clojure and programming,
;;and am just trying to learn in a way that I understand

(defn remainder-culler [input]
  (if
    (/ input (first odd-culler (input)))
  input)
  (recur (lazy-seq (input)))
)

(remainder-culler 100)
like image 400
dmbennett Avatar asked Feb 11 '23 20:02

dmbennett


1 Answers

Welcome to Clojure!

A quick note: [1 2 3 4 5 6 7 8 9 10] is a vector, not a list.

When you say "keyed to," then it makes me think you're looking for something that returns a map.

Maps

This is where Clojure's cheatsheet comes in really handy. You're trying to create a map from a function. A map is a kind of collection, so if you go to the "Collections" section on the cheatsheet, and scroll down to maps, you will see a number of categories. You want to create one, so look in that list, and explore the links to Clojure's documentation.

This leads you to the very handy group-by function. You give it a function, and a collection, and it returns a map containing all of the items from that collection, keyed by the result of applying f to each value.

> (group-by #(rem 20 %) [1 2 3 4 5 6 7 8 9 10])
{0 [1 2 4 5 10], 2 [3 6 9], 6 [7], 4 [8]}

If you want to make the keys be actual keywords, you'll have to modify the anonymous function to give back keywords:

> (group-by #(keyword (str (rem 20 %))) [1 2 3 4 5 6 7 8 9 10])
{:0 [1 2 4 5 10], :2 [3 6 9], :6 [7], :4 [8]}

Note that the return values are in vectors. This is because you can't map to two items from a single key (i.e., maps encode functions).

Iteration/List Comprehension

Now, all of that said, I'm not sure it's what you're looking for. You asked, "Could someone help me understand how to use the elements of a list independently, and then how to return the element that 20 was divided by and if it returns a remainder?" This sounds to me like a case for for. For your purposes, you can think of it as iterating, but it's really doing list comprehension.

(for [i [1 2 3 4 5 6 7 8 9 10]]
    (list (rem 20 i) i))

Once again, if you actually want to use keywords instead of values, then you could do:

(for [i [1 2 3 4 5 6 7 8 9 10]]
    (list (keyword (str (rem 20 i))) i))

In this particular case, as Kyle points out, you might just use map:

(map #(list (keyword (str (rem 20 %)))
            %)
     [1 2 3 4 5 6 7 8 9 10])

If you don't like the nested structures that these return, you can use flatten on them.

Filter

But I'm still not sure you want this to solve your problem. In your comments, you have "Separate out a list of results that only only return a remainder of 0." This sounds to me like a case for filter, which as a side-benefit is lazy.

> (filter #(zero? (rem 20 %)) [1 2 3 4 5 6 7 8 9 10])
(1 2 4 5 10)

Ta-da. It just spits out the elements of the original collection that meet your needs.

Hope this helps. It doesn't get you all the way toward your goal, but I hope it gives you some neat tools you might use to get there. You have options! And while you're learning, play around with multiple options. If you read somewhere that one is preferable to another, see if you can figure out why.

like image 54
galdre Avatar answered Feb 16 '23 03:02

galdre