using sed to convert file contents to scheme list - bash

I would like to convert data.txt into scheme list using sed into the following format:
-Each every line with same starting number will be parsed and combined like so:
data.txt
1,{},344.233
1,{2},344.197
2,{16},290.281
2,{18},289.093
3,{1},220.896
foo.scm
(define v1 '(1 (() 344.233) ((2) 344.197))) ;; this is for first two lines starting with 1
(define v2 '(2 ((16) 290.281) ((18) 289.093))) ;; ... 2
(define v3 '(3 (() 237.558))) ;; ... 3

I know nothing about scheme, so I'd probably do this in awk rather than sed.
[ghoti#pc ~]$ cat data.txt
1,{},344.233
1,{2},344.197
2,{16},290.281
2,{18},289.093
3,{1},220.896
[ghoti#pc ~]$ cat doit.awk
#!/usr/bin/awk -f
BEGIN {
FS=",";
last1=1;
}
$1 != last1 {
printf("(define v%.0f '(%.0f %s))\n", last1, last1, substr(sect,2));
last1=$1; sect="";
}
{
gsub(/[^0-9]/,"",$2);
sect=sprintf("%s ((%s) %s)", sect, $2, $3);
}
END {
printf("(define v%.0f '(%.0f %s))\n", last1, last1, substr(sect,2));
}
[ghoti#pc ~]$ ./doit.awk data.txt
(define v1 '(1 (() 344.233) ((2) 344.197)))
(define v2 '(2 ((16) 290.281) ((18) 289.093)))
(define v3 '(3 ((1) 220.896)))
[ghoti#pc ~]$
It could certainly be written more tightly, but this gets the job done.
UPDATE: (per comments)
[ghoti#pc ~]$ tail -1 data.txt
3,{1,3,4},220.896
[ghoti#pc ~]$ diff -u doit.awk doitnew.awk
--- doit.awk 2012-05-30 00:38:34.549680376 -0400
+++ doitnew.awk 2012-05-30 00:38:52.893810815 -0400
## -10,8 +10,15 ##
last1=$1; sect="";
}
+$2 !~ /}$/ {
+ while ($2 !~ /}$/) {
+ pos=match($0, /,[0-9,]+}/);
+ $0=substr($0, 0, pos-1) " " substr($0, pos+1);
+ }
+}
+
{
- gsub(/[^0-9]/,"",$2);
+ gsub(/[^0-9 ]/,"",$2);
sect=sprintf("%s ((%s) %s)", sect, $2, $3);
}
[ghoti#pc ~]$ ./doitnew.awk data.txt
(define v1 '(1 (() 344.233) ((2) 344.197)))
(define v2 '(2 ((16) 290.281) ((18) 289.093)))
(define v3 '(3 ((1 3 4) 220.896)))
[ghoti#pc ~]$
What's going on here?
In the new block we're adding, test to see whether the second field ends in a }. If it doesn't, we'll loop until it does. For each run of the loop, we'll remove a comma before the }, replacing it with a space.
Sometimes, brute-force works. :-P

In racket (a.k.a. scheme):
#lang racket
;; parse a line (we will join them later)
(define (line-parse l)
(match (regexp-match #px"([0-9]+),\\{([0-9,]*)\\},([0-9.]+)" l)
[(list dc first-num bracket-nums rest)
(list (string->number first-num)
(match bracket-nums
["" empty]
[else (map string->number
(regexp-split #px"," bracket-nums))])
(string->number rest))]
[else
(error "unexpected line format in line: ~s\n" l)]))
;; join together lines that start with the same number
(define (join-lines lines)
(cond [(empty? lines) empty]
[else (join-lines-of-n (first (first lines))
lines
empty)]))
;; gather together lines starting with 'n':
(define (join-lines-of-n n lines accum)
(cond [(empty? lines)
(list (cons n (reverse accum)))]
[(equal? (first (first lines)) n)
(join-lines-of-n n (rest lines) (cons (rest (first lines))
accum))]
[else
(cons (cons n (reverse accum))
(join-lines lines))]))
(define (dress-up line)
(format "~a\n" `(define ,(format "v~s" (first line))
',line)))
(display
(apply
string-append
(map dress-up
(join-lines
(map line-parse
(sequence->list (in-port read-line)))))))
Save this as rewrite.rkt, run it like this:
oiseau:/tmp clements> racket ./rewrite.rkt < foo.txt
(define v1 (quote (1 (() 344.233) ((2) 344.197))))
(define v2 (quote (2 ((16) 290.281) ((18) 289.093))))
(define v3 (quote (3 ((1) 220.896) ((4 5) 2387.278))))
... note that I added a {4,5} line to the input example to test your extension.
also, note that the output uses (quote ...) rather than '(...). This "should work fine"; that is, Scheme readers produce the same output for these two forms, and the resulting file should work fine as scheme input.
If this were my code, I think I wouldn't do the (define v1 ...) dance, and just write the thing out as a big piece of data that a scheme/racket program can slurp in with a single "read", but that's your choice, not mine. Also, there's some ambiguity in your specification re: the uniqueness of the initial indexes; that is, you might "go back" to an earlier line number. For instance, what should be the output when given this input file:
3,{1},1.0
4,{1},1.0
3,{1},1.0
?
Also, note that I chopped out all of the test cases in order to make it look shorter/prettier :).
EDIT: OH! Gather the lines this way, instead. It'll actually be a bit slower, but it reads much more nicely:
#lang racket
;; parse a line (we will join them later)
(define (line-parse l)
(match (regexp-match #px"([0-9]+),\\{([0-9,]*)\\},([0-9.]+)" l)
[(list dc first-num bracket-nums rest)
(list (string->number first-num)
(match bracket-nums
["" empty]
[else (map string->number
(regexp-split #px"," bracket-nums))])
(string->number rest))]
[else
(error "unexpected line format in line: ~s\n" l)]))
;; does the line start with the number k?
(define ((starts-with k) l) (equal? (first l) k))
;; join together lines starting with the same thing:
(define (join-lines lines)
(for/list ([k (remove-duplicates (map first lines))])
(cons k (map rest (filter (starts-with k) lines)))))
(define (dress-up line)
(format "~a\n" `(define ,(format "v~s" (first line))
',line)))
(display
(apply
string-append
(map dress-up
(join-lines
(map line-parse
(sequence->list (in-port read-line)))))))

This might work for you (GNU sed):
sed ':a;$!N;s/^\(\([^,])*\).*\)\n\2/\1/;ta;h;x;s/\n.*//;s/,{\([^}]*\)},\([^,]\+\)/ ((\1) \2)/g;s/,/ /g;s/^\([^ ]*\).*/(define v\1 '\''(&)) ;;...\1/p;x;D' file
Explanation:
Reduce like values to a single line :a;$!N;s/^\(\([^,])*\).*\)\n\2/\1/;ta
Copy pattern space (PS) to hold space (HS). h
Swap to HS x
Chop off previous line. s/\n.*//
Formulate lists. s/,{\([^}]*\)},\([^,]\+\)/ ((\1) \2)/g
Replace any remaining ,'s with spaces. s/,/ /g
Surround lists with function definition and comments and print. s/^\([^ ]*\).*/(define v\1 '\''(&)) ;;...\1/p
Swap back to PS. x
Delete upto previous line and repeat. D

Related

User input to a list

I'm trying to take in user input and add it to a list but I have not been able to get it working. I'm still new to scheme and have been browsing around to try to figure it out but I haven't had any luck.
(display "Continue to enter numbers until satisfied then enter e to end")
(newline)
(define (intlist number)
(define number(read-line))
(cond (number? number)
(cons lst (number))
(else
(display lst)
done')))
this is what I have so far. Any help or direction to where I can learn a bit more is appreciated.
Your solution is almost correct, but it doesn't work, because:
Variable lst doesn't exist and with this expression (number), you are calling some undefined function number.
done' is badly written 'done.
Function cons expects element as first argument and other element or list as second argument.
See these examples:
> (cons 1 2)
'(1 . 2)
> (cons 1 '())
'(1)
> (cons 1 (cons 2 (cons 3 '())))
'(1 2 3)
Last example is important here- your function will be recursive and it will return a cons cell in each step. If I will follow your solution, this can be enough:
(define (list-from-user)
(let ((number (read)))
(if (number? number)
(cons number (list-from-user))
'())))
(Note that I used read instead of read-line, because read-line returns string, and let instead of define.)
If you really want to wait for e, you must decide, what happens if user enters something that isn't number and isn't e- maybe just ignore it?
(define (list-from-user)
(let ((user-input (read)))
(cond ((number? user-input) (cons user-input (list-from-user)))
((eq? user-input 'e) '())
(else (list-from-user)))))
Then just add some wrapping function with output:
(define (my-fn)
(begin (display "Continue to enter numbers until satisfied then enter e to end")
(newline)
(list-from-user)))
and call it
> (my-fn)
Note that my function returns list with numbers, instead of some useless 'done, so I can use that function in other functions.
(define (sum-of-list)
(let ((lst (my-fn)))
(format "Sum of given list is ~a." (apply + lst))))
> (sum-of-list)

Previous result in Chez Scheme

In the Chez Scheme REPL, is it possible to get the previous result? For example in ruby's irb repl, underscore can be used.
For example can I do the following?
> (+ 2 3)
5
> (+ 1 <something>)
And get 6?
Chez Scheme does not have a built in way to do this, but being scheme, we can roll our own:
(define repl
(let ([n 1])
(lambda (expr)
(let-values ([vals (eval expr)])
(for-each (lambda (v)
(unless (eq? (void) v)
(let ([sym (string->symbol (format "$~a" n))])
(set! n (+ n 1))
(printf "~a = " sym)
(pretty-print v)
(set-top-level-value! sym v))))
vals)))))
(new-cafe repl)
repl is now a function that takes an expression, evaluates it, stores the non-void results into ids of the form $N where N is monotonically increasing, and prints out the results. new-cafe is a standard chez function that manages the Reading, Printing, and Looping parts of REPL. It takes a function that manages the Evaluation part. In this case, repl also needs to manage printing since it shows the ids associated with the values.
Edit:
I found a slightly better way to do this. Instead of having a custom repl, we can customize only the printer. Now this function is no longer responsible for also evaluating the input.
(define write-and-store
(let ([n 1])
(lambda (x)
(unless (eq? (void) x)
(let ([sym (string->symbol (format "$~a" n))])
(set! n (+ n 1))
(set-top-level-value! sym x)
(printf "~a = " sym)
(pretty-print x)
(flush-output-port (console-output-port)))))))
(waiter-write write-and-store)
A simple usage example:
> (values 1 2 3 4)
$1 = 1
$2 = 2
$3 = 3
$4 = 4
> (+ $1 $2 $3 $4)
$5 = 10

setq: wrong type argument: listp 1

My group and I are trying to create a list of cons lists. It might look something like this
((2 100032) (4 32413) (6 2131251) ... (232 12))
Unfortunately, we keep getting a "wrong type argument". It might be something simple but I also wonder if we are doing something wrong with cons.
Any responses appreciated:
(defun find-diffs ()
(let ((diffs (list))
(index 0)
(previous 0))
(while (< index (length first-ten-million-primes))
; Add the difference to the list of diffs.
(setq diff (- (aref first-ten-million-primes index) previous))
; We only want to bother recording it if the index is above zero and
; the difference is odd.
(if (and (> index 0) (evenp diff))
(setq diffs
; Can we find this one in our list of diffs?
(if (cdr (assoc diff diffs))
; Yes
; ERROR happens when we call this statement
(setq diffs
(append (cons diff (1+ (car (cdr (assq diff diffs)))))
(assq-delete-all diff diffs)))
; No
(setq diffs (plist-put diffs diff 1)))))
; Set previous value to this one.
(setq previous (aref first-ten-million-primes index))
; Increment the index.
(setq index (1+ index)))
diffs)
Commenting out the (setq diffs) will fix it, but I don't see anything wrong with how we are setting our variables.
Thanks!
The backtrace is huge so I will only post the first part. My team is still confused as to what's wrong so any responses appreciated.
Debugger Backtrace:
Debugger entered--Lisp error: (wrong-type-argument listp 1)
append((2 . 1) nil)
(setq diffs (append (cons diff 1) diffs))
(if (cdr (assoc diff diffs)) (setq diffs (append (cons diff (1+ (car (cdr (assq diff diffs)))))
Your problem is in:
(append (cons diff (1+ (car (cdr (assq diff diffs)))))
(assq-delete-all diff diffs)))
The cons up there create a cell of the form (DIFF . N) which is not a proper list. It's a pair of two element rather than a list of two elements. A list of two elements would have the form (DIFF N) which is a shorthand for (DIFF . (N . nil)).
I haven't tried to understand what is your overall goal, but there are two ways to fix your problem: Either replace
(append (cons diff FOO)
...)
with
(append (list diff FOO)
...)
or with
(cons (cons diff FOO)
...)

Racket - read from stdin until new line

I want to read from stdin element by element but I don't know how to stop when I insert a new line.
Here is my code:
(define readNext
(lambda (tmp)
(let ([a (read)])
(if (equal? a ??????)
tmp
(readNext (cons (read) tmp))))))
I found a solution that works as I expected.
(define a (open-input-string (read-line)))
(define readNext
(lambda (tmp)
(let ([b (read a)])
(if (equal? b eof)
tmp
(readNext (append tmp (list b)))))))
(readNext '())
EDIT: It won't stop on newline but for eof
The function read will read an entire datum.
If you want to read characters until you hit newline,
you will have to one character at a time.
The newline character is written #\newline.
Something like this:
#lang racket
(define read-a-line-as-list
(lambda ()
(let ([c (read-char)])
(if (equal? c #\newline)
'()
(cons c (read-a-line-as-list))))))
(define read-a-line
(lambda ()
(list->string (read-a-line-as-list))))
(read-a-line)
I'm doing it like this
(for ((_ (in-naturals)))
(define l (read-line))
#:break (eof-object? l)
(displayln l))
might not be the most idiomatic, but it works. Use for/list or similar to do something more useful with the lines being read.

How do I delete colons (:) in my lists?

I am starting to learn racket. There is a example which is about time and I need remove colons (:) from my list. For example;
11:30 -> 1130
I want to do it because I will putting them in order which is first, second, third and fourth.
P.S.: Sorry about my English.
(define L (list "22:30"))
(string-append (substring (first L) 0 2) (substring (first L) 3 5))
Output: "2230"
Try this:
(string-replace "11:30" ":" "")
If you need to do something with the digits, you can convert your string into a number or a list of digits. It goes like this:
;; convert a string into an integer
(define S (string->number (string-replace "11:30" ":" "")))
;; produce a list of digits from a given integer
(define (int->list n)
(cond [(zero? n) empty]
[else
(append (int->list (quotient n 10))
(list (remainder n 10)))]))
;; try it in REPL
> (int->list S)
'(1 1 3 0)

Resources