Find empy lines in text file - racket

I've been learning racket for a few days and I'm puzzled with this task, I'm trying to find empty lines in a text file and select a random empty line to INSERT the text "calculation here", this is as far as I have gotten so far.
for example: myfile.txt has the contents:
line1
line2
line3
line4
after the script is run, myfile.txt should now look like:
line1
calculation here
line2
line3
line4
or:
line1
line2
line3
calculation here
line4
un-working code below:
#lang racket
(define (write-to-file host text) (
with-output-to-file host (
lambda () (
write text))
#:exists 'replace))
(define empty-lines '()) ;store line number of empty line (if any)
(define (file-lines text-file)
(file->lines text-file))
(define (add-to-list line-num)
(set! empty-lines (cons line-num empty-lines)))
(let loop ((l (file-lines "myfile.txt")))
(cond ((null? l) #f)
(else
(printf "~s\n" (first l)) ; just for debugging
(cond ((equal? (first l) "") (add-to-list (first l)))(else #f))
(loop (rest l)))))
;now i need to select a random line from the list of empty-lines.
;and write "calculations here" to that line
there's no problem with the read lines method i am using, the problem is detecting and selecting a random empty space to insert my text.

Given a file name, you can read it into a list of lines using file->lines. So for instance:
(for ([line (in-list (file->lines "some-file"))])
(displayln (cond [(zero? (string-length line)) (make-random-line)]
[else line])))
Where make-random-line is some function you define to return a
random string, as you said you wanted to do.
The above reads the entire file into a list in memory. For larger files, it would be better to process things line by line. You can do this using the in-lines sequence:
(with-input-from-file "some-file"
(thunk
(for ([line (in-lines)])
(displayln (cond [(zero? (string-length line)) (make-random-line)]
[else line])))))
Update
Now that I understand your question:
#lang racket
(define lines (file->lines "some-file-name"))
(define empty-line-numbers (for/list ([line (in-list lines)]
[n (in-naturals)]
#:when (zero? (string-length line)))
n))
(define random-line-number (list-ref empty-line-numbers
(random (length empty-line-numbers))))
(for ([line (in-list lines)]
[n (in-naturals)])
(displayln (cond [(= n random-line-number) "SOME NEW STRING"]
[else line])))

(define (read-next-line-iter file)
(let ((line (read-line file)))
(unless (eof-object? line)
(display line)
(newline)
(read-next-line-iter file))))
(call-with-input-file "foobar.txt" read-next-line-iter)
http://rosettacode.org/wiki/Read_a_file_line_by_line#Racket
this function can help you read a file line by line.
check if the length is 0. and replace that line with the comment

look for 2 concurrent \n in the file. I am pretty sure there is a way in racket to do that. store those indices in a list select a pair randomly and replace the second \n with "calculation here\n".

Related

Lisp basic print function getting user input

I am supposed to write a program that gets simple user input as a string and the code supposed to writes back a response (name, are you a person etc.) The program suppose to terminate when word 'bye' is typed. The code is below:
(defun x()
(setq words "empty")
(loop while (string/= words "BYE")
(setq words (read-delimited-list #\~)
(write-line words)
(format t "ROBBIE%: Hello, who are you?")
(case (string-include "I'm" words)
(format t "ROBBIE%: Nice to see you, how are you?")
((string-include "Hi" words)
(format t "ROBBIE%: How are you?")
(or (string-include "fine" words) (string-include "person" words))
(format t "ROBBIE%: No I'm a computer")))
(format t "BYE"))
(x)
However, when I compile this on program 2 errors pop up:
Line2:3 warning: undefined variable: COMMON-LISP-USER:: WORDS
Line3:3 error: during macroexpansion of (LOOP WHILE (STRING/= WORDS "BYE") ...). Use BREAK-ON-SIGNALS to intercept.
I've done programming in python but this is extremely complicated lang for me and I need some help understanding why this isn't working? Any advice is greatly appreciated!
When you do this:
(defun x ()
(setf words "foo"))
then words is not defined. It references some global variable, and if that doesn't exist, it will create it, but possibly with some strange behaviour regarding scope and extent. This is not portable code.
In order to introduce a local variable, use let:
(defun x ()
(let ((words "foo"))
;; words is is scope here
)
;; but not here
)
Loop (in the more usual »extended« form) uses loop keywords for all its clauses. There is no implicit body. In order to do something, you might use do, which allows multiple forms to follow:
(defun x ()
(let ((words "foo"))
(loop while (string/= words "bye")
do (setf words (read-line …))
(format …))))
Case uses compile-time values to compare using eql:
(case something
(:foo (do-a-foo))
((:bar :baz) (do-a-bell))
(t (moooh)))
This doesn't work with strings, because strings are not eql unless they are the same object (i. e. they are eq). In your case, you want a cond:
(cond ((string-include-p words "Whatever")
…)
((string-include-p words "yo man")
…))
For interaction with the user, you'd maybe want to use the bidirectional *query-io* stream:
(format *query-io* "Who am I?")
and
(read-line *query-io*)
Read-line gives you strings, and seems much better suited to your task than read-delimited-list, which has other use cases.
Let me focus on aspects of your code not already covered by other solutions.
Loop
Here is your loop structure:
(let ((words "empty"))
(loop
while (string/= words "BYE")
do
(progn
(setq words (read-line)))))
First of all, after do you don't need (progn ...). You could write equally:
(let ((words "empty"))
(loop
while (string/= words "BYE")
do (setq words (read-line))))
Having to initialize words to some arbitrary value (called sometime a sentinel value) is a code smell (not always a bad thing, but there might be better alternatives). Here you can simplify the loop by using a for clause.
(loop
for words = (read-line)
while (string/= words "BYE")
do ...)
Also, you may want to use until with a string= test, this might be more readable:
(loop
for words = (read-line)
until (string= words "BYE")
do ...)
Search
You can test for string inclusion with SEARCH. Here is a little commented snippet of code to showcase how string manipulation function could work:
(defun test-I-am (input)
(let ((start (search "I'am" input)))
(when start
;; we found an occurrence at position start
;; let's find the next space character
(let ((space (position #\space input :start start)))
(when space
;; we found a space character, the name starts just after
(format nil "Hello ~a!" (subseq input (1+ space))))))))
With this simple algorithm, here is a test (e.g. in your REPL):
> (test-i-am "I'am tired")
"Hello tired!"
Replace read-delimited-list with read-line, case with cond and balance some parentheses. Here is working solution, including some function for string-inclusion:
(defun string-include (string1 string2)
(let* ((string1 (string string1)) (length1 (length string1)))
(if (zerop length1)
nil
(labels ((sub (s)
(cond
((> length1 (length s)) nil)
((string= string1 s :end2 (length string1)) string1)
(t (sub (subseq s 1))))))
(sub (string string2))))))
(defun x ()
(let ((words "empty"))
(format t "ROBBIE%: Hello, who are you?~%")
(loop while (string/= words "BYE") do
(progn
(finish-output)
(setq words (read-line))
(cond ((string-include "I'm" words)
(format t "ROBBIE%: Nice to see you, how are you?~%"))
((string-include "Hi" words)
(format t "ROBBIE%: How are you?~%"))
((or (string-include "fine" words)
(string-include "person" words))
(format t "ROBBIE%: No I'm a computer~%")))))
(format t "BYE")))
Then you just call it:
(x)

How do I use a reader macro directly in Racket?

I am trying to define a reader macro that reads the length of a string. The string will be enclosed in vertical bars (pipes). For example:
|yes| -> 3
|| -> 0
|The quick brown fox| -> 19
|\|| -> 1 — The pipe character can be escaped by preceding it with a backslash.
(let ((x |world|)) (+ |hello| x)) -> 10
I managed to write this:
#lang racket
(define (handle-pipe in [count 0])
(define cur-char (read-char in))
(cond [(eqv? cur-char #\|)
count]
[else
(when (and (eqv? cur-char #\\) ; Handle escape ("\|").
(eqv? (peek-char in) #\|))
(read-char in)) ; Consume |.
(handle-pipe in (+ count 1))]))
(parameterize ([current-readtable
(make-readtable (current-readtable)
#\|
'terminating-macro
(lambda (char in src-name line col pos)
(handle-pipe in)))])
(eval (read (open-input-string "(let ((x |world|)) (+ |hello| x))"))
(make-base-namespace)))
This returns 10, as expected.
The problem now is: I would like to use the reader macro directly in my Racket code instead of having to feed a string into (eval (read (open-input-string ...))). For example, I would like to use the reader macro like this:
#lang racket
(define (handle-pipe in [count 0])
(define cur-char (read-char in))
(cond [(eqv? cur-char #\|)
count]
[else
(when (and (eqv? cur-char #\\) ; Handle escape ("\|").
(eqv? (peek-char in) #\|))
(read-char in)) ; Consume |.
(handle-pipe in (+ count 1))]))
(current-readtable
(make-readtable (current-readtable)
#\|
'terminating-macro
(lambda (char in src-name line col pos)
(handle-pipe in))))
(let ((x |world|)) ; Using the reader macro directly in Racket.
(+ |hello| x))
However, there is an error message when I run the program above:
my-program.rkt:20:9: world: unbound identifier
in: world
location...:
my-program.rkt:20:9
context...:
do-raise-syntax-error
for-loop
[repeats 1 more time]
finish-bodys
lambda-clause-expander
for-loop
loop
[repeats 3 more times]
module-begin-k
expand-module16
expand-capturing-lifts
temp118_0
temp91_0
compile15
temp85_0
standard-module-name-resolver
What did I do wrong? How do I use the reader macro in my code?
That's not possible. The pipeline of compilation/evaluation in Racket is:
Read
Expand macros
Evaluate expanded program
Your configuration of current-readtable is done in step 3, so it cannot influence things that happened already in step 1.
The reason your first code works is that eval starts the pipeline again on the datum you provided it to.
Note that the reader macro is actually intended to be used when you create a new #lang. But since you want it to work with #lang racket, it's not applicable.

Functions inside a loop behaves differently

So I have a loop to just repeat the little text game I have made about dota, but when the function 'play' is called within a loop it doesn't return the result of my cond function, it just takes an input and then moves on to the next loop.
;;;;learn the invoker combo's
(defparameter *invoker-combo* '((cold-snap (3 0 0) 'QQQ);all of the possible invoker combo's
(ghost-walk (2 1 0) 'QQW)
(Ice-Wall (2 0 1) 'QQE)
(EMP (0 3 0) 'WWW)
(Tornado (1 2 0) 'QWW)
(Alacrity (0 2 1) 'WWE)
(Sun-Strike (0 0 3) 'EEE)
(Forge-Spirit (1 0 2) 'QEE)
(Chaos-Meteor (0 1 2) 'WEE)
(Deafening-Blast (1 1 1) 'QWE)))
(defun rand-combo (invoker-combo);returns a random combo
(nth (random (length invoker-combo))invoker-combo))
(defun count-letters (input);converts the keyboard strokes into numbers to be compared as it doesn't matter what order they are in, just that there is the correct quantity of them e.g QQE could also be written QEQ.
(append
(list (count #\Q input)
(count #\W input)
(count #\E input))))
(defun try-for-combo (rand-combo);takes i-p and compares it with the value for the random combo
(print(car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))'Good-job)
((equal i-p "END")(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo)*invoker-combo*)))))))
(defun play ()
(try-for-combo (rand-combo *invoker-combo*)))
(defun loop-play (x)
(loop for i from 0 to x
:do (play)))
If I call the function 'play' I get the following o/p:
FORGE-SPIRIT asdf
("ASDF" WAS WRONG CORRECT IS 'QEE)
or
ALACRITY wwe
GOOD-JOB
But if I call the function 'loop-play' I get the following o/p:
Break 3 [7]> (loop-play 2)
SUN-STRIKE eee
ALACRITY wwe
TORNADO qww
NIL
Can someone explain to me why this is happening?
EDIT: feel free to change the title, I didn't really know what to put.
The indentation and formatting of the code is poor. Please make it easier for you and for us to read the code.
(defun try-for-combo (rand-combo);takes i-p and compares it with the value for the random combo
(print(car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))'Good-job) ; wrong indent level
((equal i-p "END")(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo)*invoker-combo*)))))))
lacks spaces between s-expressions
wrong indentation levels
structure of the code unclear
does not use built-in documentation features
some lines are too long
Better:
(defun try-for-combo (rand-combo)
"takes i-p and compares it with the value for the random combo" ; built in doc
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo)) ; indentation
'Good-job)
((equal i-p "END")
(list 'Thanks 'for 'playing))
(t
(list i-p 'was 'wrong 'correct 'is ; several lines
(caddr (assoc (car rand-combo)
*invoker-combo*)))))))
I would propose to use an editor which actually understands some Lisp formatting. like GNU Emacs / SLIME, Clozure CL's Hemlock, LispWorks' editor...
If you are unsure about formatting, you can also ask Lisp to do it. Clisp is not that good at formatting, but something like SBCL or CCL would do:
* (let ((*print-case* :downcase))
(pprint '(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line)))
(try (count-letters i-p)))
(cond ((equal try (cadr rand-combo))
'Good-job) ((equal i-p "END")
(list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is
(caddr (assoc (car rand-combo)
*invoker-combo*)))))))))
And you get nicely formatted code:
(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase (read-line))) (try (count-letters i-p)))
(cond ((equal try (cadr rand-combo)) 'good-job)
((equal i-p "END") (list 'thanks 'for 'playing))
(t
(list i-p 'was 'wrong 'correct 'is
(caddr (assoc (car rand-combo) *invoker-combo*)))))))
Automatic indenting of Lisp code by the editor saves you a lot of work.
There are hints for manual indentation.
Your try-for-combo function doesn't actually output anything. Rather, it returns values.
In the REPL, if you evaluate a form, like (+ 1 2), it will always print the evaluation of that form at the end (in this case, 3). However, consider instead (+ 1 (print 2)). The print function actually outputs the argument to standard output, then returns the value itself. So this will show (on the repl)
2
3
The 2 is outputted first, because (print 2) itself prints 2. Then, the form (+ 1 (print 2)) is evaluates to the same things as (+ 1 2), or 3.
In your case, your try-for-combo function should look like:
(defun try-for-combo (rand-combo)
(print (car rand-combo))
(let* ((i-p (string-upcase(read-line)))
(try (count-letters i-p)))
(print
(cond
((equal try (cadr rand-combo)) 'Good-job)
((equal i-p "END") (list 'Thanks 'for 'playing))
(t (list i-p 'was 'wrong 'correct 'is (caddr(assoc (car rand-combo) *invoker-combo*))))))
nil))
This will print the result of that cond form, and return 'nil'.
That's just the difference between the output your program does and the output the Lisp system does for each evaluation:
print prints something (a newline and then its argument) and returns a value. The value is printed by the REPL. Thus we see output twice:
[3]> (print "3")
"3"
"3"
Next we do several call to print in a progn. The value of the progn form is printed by the REPL. The first three strings are printed by the code and the last string is printed because of the Lisp REPL printing the value:
[4]> (progn (print "1") (print "2") (print "3"))
"1"
"2"
"3"
"3"

How do I find and insert the average of multiple lines in Emacs / Elisp?

I have a file that looks similar to:
AT 4
AT 5.6
AT 7.2
EG 6
EG 6
S 2
OP 3
OP 1.2
OP 40
and I want to compute the average (I've just made these averages up) for each of the titles and output something like:
AT 5.42
EG 6
S 2
OP 32.1
The file is in order, so all headings will be right under each other, but there are a varying amount of headings. eg. AT has three, but S only has one.
How would I sum together each of these lines, divide by the number of lines, and then replace all of the lines in emacs / elisp?
I decided to try to solve this question while still learning elisp myself. There is perhaps more efficient ways to solve this.
After defining the function, you'll want to set the region around the scores. (If the whole file, then M-<, C-SPC, M->) I figured this would be cleanest since your scores may be in the middle of other text. My function will compute the averages and then insert the answer at the end of the region.
(defun my/averages (beg end)
(interactive "r")
(let ((avgs (make-hash-table :test 'equal))
(answer "")
(curval nil)
(key nil)
(val nil))
; Process each line in region
(save-excursion
(goto-char beg)
(while (< (point) end)
; split line
(let ((split-line
(split-string
(buffer-substring-no-properties
(line-beginning-position) (line-end-position)))))
(setq
key (car split-line)
val (string-to-number (cadr split-line))
curval (gethash key avgs '(0 . 0)))
(puthash key (cons (+ (car curval) 1) (+ (cdr curval) val )) avgs))
; Advance to next line
(forward-line))
; Accumulate answer string
(maphash
(lambda (k v)
(setq answer
(concat answer "\n" k " "
(number-to-string (/ (cdr v) (car v))))))
avgs)
(end-of-line)
(insert answer))))
As a warning, I have zero error checking for lines that do not strictly meet your formatting.
You need libraries dash, s, f, and their functions -map, -sum, -group-by, s-split, f-read-text.
;; average
(defun avg (values)
(/ (-sum values) (length values)))
(-map (lambda (item)
(list (car item)
(avg (-map (lambda (x)
(string-to-number (cadr x)))
(cdr item)))))
(-group-by (lambda (item)
(car item))
(-map (lambda (line)
(s-split " " line t))
(s-split "[\n\r]"
(f-read-text "file.txt")
t))))
Presuming your file is called "file.txt", the code above returns (("AT" 5.6000000000000005) ("EG" 6) ("S" 2) ("OP" 14.733333333333334)).
After that you can convert that into text:
(s-join "\n"
(-map (lambda (item)
(s-join " "
(list (car item)
(number-to-string (cadr item)))))
This string you can write into file using f-write-text. Don't forget you can format ugly floating-point numbers like that:
(format "%.2f" 3.33333333) ; => "3.33"

How to replace string in a file with lisp?

What's the lisp way of replacing a string in a file.
There is a file identified by *file-path*, a search string *search-term* and a replacement string *replace-term*.
How to make file with all instances of *search-term*s replaced with *replace-term*s, preferably in place of the old file?
One more take at the problem, but few warnings first:
To make this really robust and usable in the real-life situation you would need to wrap this into handler-case and handle various errors, like insufficient disc space, device not ready, insufficient permission for reading / writing, insufficient memory to allocate for the buffer and so on.
This does not do regular expression-like replacement, it's simple string replacement. Making a regular expression based replacement on large files may appear far less trivial than it looks like from the start, it would be worth writing a separate program, something like sed or awk or an entire language, like Perl or awk ;)
Unlike other solutions it will create a temporary file near the file being replaced and will save the data processed so far into this file. This may be worse in the sense that it will use more disc space, but this is safer because in case the program fails in the middle, the original file will remain intact, more than that, with some more effort you could later resume replacing from the temporary file if, for example, you were saving the offset into the original file in the temporary file too.
(defun file-replace-string (search-for replace-with file
&key (element-type 'base-char)
(temp-suffix ".tmp"))
(with-open-file (open-stream
file
:direction :input
:if-exists :supersede
:element-type element-type)
(with-open-file (temp-stream
(concatenate 'string file temp-suffix)
:direction :output
:element-type element-type)
(do ((buffer (make-string (length search-for)))
(buffer-fill-pointer 0)
(next-matching-char (aref search-for 0))
(in-char (read-char open-stream nil :eof)
(read-char open-stream nil :eof)))
((eql in-char :eof)
(when (/= 0 buffer-fill-pointer)
(dotimes (i buffer-fill-pointer)
(write-char (aref buffer i) temp-stream))))
(if (char= in-char next-matching-char)
(progn
(setf (aref buffer buffer-fill-pointer) in-char
buffer-fill-pointer (1+ buffer-fill-pointer))
(when (= buffer-fill-pointer (length search-for))
(dotimes (i (length replace-with))
(write-char (aref replace-with i) temp-stream))
(setf buffer-fill-pointer 0)))
(progn
(dotimes (i buffer-fill-pointer)
(write-char (aref buffer i) temp-stream))
(write-char in-char temp-stream)
(setf buffer-fill-pointer 0)))
(setf next-matching-char (aref search-for buffer-fill-pointer)))))
(delete-file file)
(rename-file (concatenate 'string file temp-suffix) file))
It can be accomplished in many ways, for example with regexes. The most self-contained way I see is something like the following:
(defun replace-in-file (search-term file-path replace-term)
(let ((contents (rutil:read-file file-path)))
(with-open-file (out file-path :direction :output :if-exists :supersede)
(do* ((start 0 (+ pos (length search-term)))
(pos (search search-term contents)
(search search-term contents :start2 start)))
((null pos) (write-string (subseq contents start) out))
(format out "~A~A" (subseq contents start pos) replace-term))))
(values))
See the implementation of rutil:read-file here: https://github.com/vseloved/rutils/blob/master/core/string.lisp#L33
Also note, that this function will replace search terms with any characters, including newlines.
in chicken scheme with the ireggex egg:
(use irregex) ; irregex, the regular expression library, is one of the
; libraries included with CHICKEN.
(define (process-line line re rplc)
(irregex-replace/all re line rplc))
(define (quickrep re rplc)
(let ((line (read-line)))
(if (not (eof-object? line))
(begin
(display (process-line line re rplc))
(newline)
(quickrep re rplc)))))
(define (main args)
(quickrep (irregex (car args)) (cadr args)))
Edit: in the above example buffering the input doesn't permit the regexp to span over
many lines.
To counter that here is an even simpler implementation which scans the whole file as one string:
(use ireggex)
(use utils)
(define (process-line line re rplc)
(irregex-replace/all re line rplc))
(define (quickrep re rplc file)
(let ((line (read-all file)))
(display (process-line line re rplc))))
(define (main args)
(quickrep (irregex (car args)) (cadr args) (caddr args)))