Reputation: 19
Question from a total newbie with Clojure. Task is pretty simple, but I'm having hard time finding the best way to do this - I need to set up input, where user could give me a list (user should determine how long) of natural numbers and the program should just return a sum of these numbers.
Maybe this is totally wrong already:
(defn inputlist[naturallist]
(println "Enter list of natural numbers:")
(let[naturallist(read-line)] ))
Upvotes: 1
Views: 3441
Reputation: 1681
If you don't care about negative scenarios (wrong input, syntax issues), the quickest solution would be to evaluate the user's input putting it into parens:
(defn sum-ints []
(let [input (read-line)
ints (read-string (str "(" input ")"))]
(reduce + 0 ints)))
Usage:
user=> (sum-ints)
1 2 3 4 5
15
The read-string
function evaluates an text expression (1 2 3 4 5)
in that case. Since numeral literals turn into numbers, the result will be just a list of numbers.
Upvotes: 0
Reputation: 5020
Welcome to Clojure—and StackOverflow!
Here's how to do it:
(defn input-numbers-and-sum []
(print "Enter list of natural numbers: ")
(flush)
(->> (clojure.string/split (read-line) #"\s+")
(map #(Integer/parseInt %))
(reduce +)))
Here's how it works:
Calling print
rather than println
avoids printing a newline character at the end of the line. This way, the user's input will appear on the same line as your prompt.
Since there was no newline, you have to call flush
to force the output buffer containing the prompt to be printed.
split
splits what the user typed into a sequence of strings, divided where a regular expression matches. You have to say clojure.string/split
rather than just split
because split
is not in Clojure's core library. clojure.string/
specifies the library. #"\s+"
is a regular expression that matches any number of consecutive whitespace characters. So, if your user types " 6 82 -15 "
, split
will return ["6" "82" "-15"]
.
map
calls the standard Java library function Integer.parseInt
on each of those strings. Integer/parseInt
is Clojure's Java interop syntax for calling a static method of a Java class. The #(...)
is terse syntax that defines an anonymous function; the %
is the argument passed to that function. So, given the sequence of strings above, this call to map
will return a sequence of integers: [6 82 -15]
.
reduce
calls the +
function repeatedly on each element of the sequence of integers, passing the sum so far as an argument along with the next integer. map
and reduce
actually take three arguments; the next paragraph tells how the third paragraph gets filled in.
->>
is the "thread-last macro". It rewrites the code inside it, to pass the output of each expression but the last as the last argument of the following expression. The result is:(reduce + (map #(Integer/parseInt %) (clojure.string/split (read-line) #"\s+")))
Most people find the version with ->>
much easier to read.
That might seem like a lot to do something very simple, but it's actually bread and butter once you're used to Clojure. Clojure is designed to make things easy to combine; map
, reduce
, and ->>
are especially useful tools for hooking other functions together.
I've included links to the documentation. Those are worth a look; many contain typical examples of use.
There are other ways to parse numbers, of course, some of which are shown in the answers to this question. What I've written above is an "idiomatic" way to do it. Learn that, and you'll know a lot of the everyday, must-know techniques for programming in Clojure.
Upvotes: 1
Reputation: 29958
Here is one way of doing it:
> lein new app demo
> cd demo
Edit the project.clj
and src/demo/core.clj
so they look as follows:
> cat project.clj
(defproject demo "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.9.0"]
[org.clojure/tools.reader "1.1.3.1"] ]
:main ^:skip-aot demo.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
> cat src/demo/core.clj
(ns demo.core
(:require
[clojure.tools.reader.edn :as edn]
[clojure.string :as str] ))
(defn -main []
(println "enter numbers")
(let [input-line (read-line)
num-strs (str/split input-line #"\s+")
nums (mapv edn/read-string num-strs)
result (apply + nums) ]
(println "result=" result)))
with result
> lein run
enter numbers
1 2 3 <= you type this, then <enter>
input-line => "1 2 3"
num-strs => ["1" "2" "3"]
nums => [1 2 3]
result => 6
You may wish to start looking at some beginner books as well:
Upvotes: 4
Reputation: 50017
The quickest way to do this I can think of is
#(apply + (map #(Integer/parseInt %) (re-seq #"\d+" (read-line))))
This defines an anonymous function which:
\d+
to search for strings of consecutive digits. Each string of consecutive digits is added to a sequence, which is subsequently returned. So, for example, if you type in 123, 456, 789
the re-seq
function will return the sequence '("123" "456" "789")
.#(Integer/parseInt %)
for each element in the list returned by the re-seq
invocation, creating another list of the results. So if the input is '("123" "456" "789")
the output will be '(123 456 789)
.+
function to the list of numbers, summing them up, and returns the sum.Et voila! The end result is, you type in a string of numbers, separated by non-numeric characters, you get back the sum of those numbers. If you want to be a little neater, promote code re-use, and in general make this a bit more useful you could break this up into separate functions:
(defn parse-string-list [s]
(re-seq #"\d+" s))
(defn convert-seq-of-int-strings [ss]
(map #(Integer/parseInt %) ss))
(defn sum-numbers-in-seq [ss]
(apply + ss))
Invoking this in a Lisp-y way would look something like
(sum-numbers-in-seq (convert-seq-of-int-strings (parse-string-list (read-line))))
or, in a more Clojure-y way
(-> (read-line)
(parse-string-list)
(convert-seq-of-int-strings)
(sum-numbers-in-seq))
Best of luck.
Upvotes: 2
Reputation: 1
Yep, readline is the correct way to do it.
But each element from readlines
is essentially an instance of java.lang.Character
, and since you want the sum, you'd prefer to convert them to integer before summing the elements of list.
(defn input-list
[]
(print "Enter list of natural numbers")
(let [nums (read-line)]
(reduce + (map #(Integer/parseInt %) (clojure.string/split nums #"\s+")))
This might not be the most idiomatic way to do it, but feel free to tweak it.
Also, please do clean up on your variable/function names.
Edit : (Integer/parseInt %)
might cause an error if used directly since the input is an instance of characters, not string. So we can use clojure.string/split
to convert user input to a sequence of strings, and use Integer/parseInt %
for conversion.
In fact, a more readable version can be written using thread-first macros :
(defn input-list []
(print "Enter list of natural numbers: ")
(->> (clojure.string/split (read-line) #"\s+")
(map #(Integer/parseInt %))
(reduce +)))
This is a more clojurey way to do it.
Upvotes: 0