IO in racket is painful

1,284 views
Skip to first unread message

rom cgb

unread,
Mar 22, 2016, 4:33:37 PM3/22/16
to Racket Users
Hi,

I recently started using Racket and i couldn't find a good alternative to C's scanf/printf in the racket standard library. When doing programming challenges, you often need to comply to a specific input/ouput thus having something like scanf for string to values and printf for values to string is comfy.

For example, let's say a challenge where you simply have to output the input but you need to translate the strings input into values and then those values back to strings.

The given input(and excepted ouput) is

3
[04, foo, 03.5]
[05, bar, 04.6]
[06, fun, 05.7]

In C, you would simply do

#include <stdio.h>

int main(void)
{
int n;

// Read number of rows
scanf("%d\n", &n);

// Output number of rows
printf("%d\n", n);

// Process rows
for (unsigned int i; i < n; ++i)
{
int a;
char b[20];
float c;

// Read row
scanf("[%d, %[^,], %f]\n", &a, b, &c);

// Output row
printf("[%02d, %s, %04.1f]\n", a, b, c);
}
}

Now, for a solution in Racket, You first have to read the first line and convert it into a number.

(define rows (string->number (read-line)))

Then, for all rows, read and split the string. The best that i have found to do that is using regular expressions.

(define split-row (regexp-match #rx"\\[(.+), (.+), (.+)\\]" (read-line)))

Then you have to manually convert the substrings into values

(define a (string->number (second split-row)))
(define b (third split-row))
(define c (string->number (fourth split-row)))

Then you have to manually convert the values back into strings

(printf "[~a, ~a, ~a]\n"
(~a a #:width 2 #:align 'right #:pad-string "0")
b
(~r c #:min-width 4 #:precision 1 #:pad-string "0")))

This is way more tedious than with the classical input/output format, especially when you are doing coding challenges.

Final Racket solution:

#lang racket

(define rows (string->number (read-line)))

(for ([in-range rows])
(define split-row (regexp-match #rx"\\[(.+), (.+), (.+)\\]" (read-line)))

(define a (string->number (second split-row)))
(define b (third split-row))
(define c (string->number (fourth split-row)))

(printf "[~a, ~a, ~a]\n"
(~a a #:width 2 #:align 'right #:pad-string "0")
b
(~r c #:min-width 4 #:precision 1 #:pad-string "0")))


Having something like (not necessary the same specifiers as with printf)

(string-scan "[%d, %s, %f]" "[45, foo, 10.9]") -> '(45 "foo" 10.9)

and a proper output formatter would be comfy.

Are racket devs against such a thing in the standard library ?
How you guys are actually doing IO in racket ?

Matthew Butterick

unread,
Mar 22, 2016, 6:34:39 PM3/22/16
to rom cgb, Racket Users
> Then, for all rows, read and split the string. The best that i have found to do that is using regular expressions.
>
> (define split-row (regexp-match #rx"\\[(.+), (.+), (.+)\\]" (read-line)))
>
> Then you have to manually convert the substrings into values
>
> (define a (string->number (second split-row)))
> (define b (third split-row))
> (define c (string->number (fourth split-row)))

This kind of destructuring & conversion from strings to values can sometimes be more conveniently handled by converting your source data into something that looks like Racket S-expressions (in this case, by removing the commas) and then calling `read` to do the rest, e.g.

#lang racket
(with-input-from-file "data.txt"
(λ _ (for/list ([ln (in-lines)])
(read (open-input-string (string-replace ln "," ""))))))

Stephen Chang

unread,
Mar 22, 2016, 8:07:39 PM3/22/16
to Matthew Butterick, rom cgb, Racket Users
A "read" variant that works with format strings would be a useful
addition I think.

Matthew, your solution is clever :) (but with symbols instead of strings).

Another alternative could be to use match:

#lang racket

(define (trim-brackets str)
(string-trim str #rx"\\[|\\]|,"))

(define (pad n w)
(~r n #:pad-string "0" #:min-width w))

(parameterize ([current-input-port (open-input-file "file.txt")])
(for ([ln (in-lines)])
(match (string-split (trim-brackets ln) ", ")
[(list (app string->number x)) (displayln x)]
[(list (app string->number x) y (app string->number z))
(printf "[~a, ~a, ~a]\n" (pad x 2) y (pad z 4))])))

or if you are willing to write a macro:

#lang racket

(define (trim-brackets str)
(string-trim str #rx"\\[|\\]|,"))

(define (pad n w)
(~r n #:pad-string "0" #:min-width w))

(define-match-expander numstr
(syntax-rules () [(_ x) (app string->number x)]))

(parameterize ([current-input-port (open-input-file "file.txt")])
(for ([ln (in-lines)])
(match (string-split (trim-brackets ln) ", ")
[(list (numstr x)) (displayln x)]
[(list (numstr x) y (numstr z))
(printf "[~a, ~a, ~a]\n" (pad x 2) y (pad z 4))])))



(yes, I didnt close the port)
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

WarGrey Gyoudmon Ju

unread,
Mar 22, 2016, 8:28:28 PM3/22/16
to rom cgb, Racket Users
In Racket, (read) and (write) know all the builtin datatypes which are already structured more than a stream of bytes (like in C).
Thus, you don't need scanf to tell Racket what is the type of the next token.

That *is* painful in a situation like coding challenges since the input format is language independent (however actually it's the C style).
Of course this kind of situation also has its own fixed format, you can define your own read tables :
1. the "," is special in Racket, you need to drop off them first;  (with-input-from-string "[04 foo 03.5]" read) gives you '(4 foo 3.5) directly.
2. Symbols are internal Strings, you need (symbol->string) to covent them into normal Strings (Yes, sometimes, I think if there are string-like or bytes-like APIs that work on symbols directly). 


WarGrey Gyoudmon Ju

unread,
Mar 22, 2016, 8:32:44 PM3/22/16
to rom cgb, Racket Users
On Wed, Mar 23, 2016 at 8:26 AM, WarGrey Gyoudmon Ju <juzhe...@gmail.com> wrote:
In Racket, (read) and (write) know all the builtin datatypes which are already structured more than a stream of bytes (like in C).
Thus, you don't need scanf to tell Racket what is the type of the next token.

That *is* painful in a situation like coding challenges since the input format is language independent (however actually it's the C style).
Of course this kind of situation also has its own fixed format, you can define your own read tables :
1. the "," is special in Racket, you need to drop off them first;  (with-input-from-string "[04 foo 03.5]" read) gives you '(4 foo 3.5) directly.
2. Symbols are internal Strings, you need (symbol->string) to covent them into normal Strings (Yes, sometimes, I think if there are string-like or bytes-like APIs that work on symbols directly). 


Sorry, please forget the Read Table, it makes things more complex here. 

Mark Engelberg

unread,
Mar 22, 2016, 10:06:29 PM3/22/16
to WarGrey Gyoudmon Ju, rom cgb, Racket Users
Hi, I have coached several teams on using Racket in programming contests.

In our local contests, the most common input format is to have one line per dataset, and each dataset is typically several pieces of data separated by spaces.  For this common input format, the 2htdp/batch-io teachpack is the most convenient tool.

Here is a document I give to beginner students about how to use the teachpack for the common case:
https://docs.google.com/document/d/1rJ9psWwB6qbYrYSKriQi15IpgN1qjQSxE4riAPHhQE8/edit?usp=sharing

But the advanced students sometimes run into files like the one you describe, where the input has a somewhat different.  I teach the students to build up a little toolkit of useful functions that they can write very quickly once at the beginning of the contest -- functions that are useful for "slicing and dicing" any file into a list of datasets.

Here are three such functions, that come in handy:

#lang racket
(require srfi/1)

; f->l stands for file->lines. We use the shorter name for less typing in a contest, and to avoid conflict with built-in file->lines
; A hard lesson we learned is that in contests, the input files aren't always "clean".  Sometimes there are stray spaces at the
; front or end of certain lines, or extra blank lines at the end of the file.  This version of file->lines sanitizes the input.
(define (f->l file)
  (reverse (drop-while (curry equal? "") (reverse (map string-trim (file->lines file))))))

; convert takes a string as an input and converts it to a number if it is a number, otherwise, leaves it alone
(define (convert s)
  (if (string->number s)(string->number s) s))

; string-parse takes a string which is strings and/or numbers separated by spaces, and returns a list of those elements
; So (string-parse "hello 1 2.0 world") returns (list "hello" 1 2.0 "world")
(define (string-parse s)
  (map convert (string-split s)))


Once these three functions are written (once at the beginning of the contest), you can tackle a problem like yours as follows:

; Now we solve the contest problem

; We don't need the initial number of lines, because Racket is awesome.
; Let's take a moment to pity the students who use languages that force them to read
; the data into a fixed-length array.

; Now, let's do it our way, and drop the number of lines with a simple call to rest.

(define lines (rest (f->l "C:/temp/infile.dat")))

; We can put a line into a format consumable by string-parse by getting rid of brackets and commas
; regexp-replace* is a very useful function to remember
; Make a habit out of using #px rather than #rx, as it supports some additional features that can be useful,
; and it's easier to just always use #px instead of trying to recall which features require which regexp type.

(define datasets (for*/list ([line lines]) (string-parse (regexp-replace* #px"\\[|\\]|," line ""))))

; So that's all it took with our "library" of three functions in place.
; It just took two lines to massage the input file into a list of datasets.
; Now we implement the logic of processing a given dataset.
; If you know match, you might want to write it this way:

(define (process-dataset ds)
  (match ds
    [`(,a ,b ,c)
     (printf "[~a, ~a, ~a]\n"
          (~a a #:width 2 #:align 'right #:pad-string "0
          b
          (~r c #:min-width 4 #:precision 1 #:pad-string "0"))]))

(for ([dataset datasets]) (process-dataset dataset))

; Alternatively, if you don't know match, you can write it as follows, taking advantage of apply.

(define (process-dataset a b c)

  (printf "[~a, ~a, ~a]\n"
          (~a a #:width 2 #:align 'right #:pad-string "0")
          b
          (~r c #:min-width 4 #:precision 1 #:pad-string "0")))

(for ([dataset datasets]) (apply process-dataset dataset))


With most problems, it only takes a few lines like this to manipulate the input file into a list of datasets, so my students have done well without a scanf function.  Not that it wouldn't hurt to have it in specific situations, but it's usually not necessary.

Good luck in the contest!



Alexis King

unread,
Mar 23, 2016, 12:06:18 AM3/23/16
to rom cgb, Racket Users
Honestly, judging from the responses in this thread, it would seem there
may be a hole in Racket’s toolbox. Nothing I’ve seen so far is
particularly stellar, especially since this is a problem that does not
seem like it should be hard.

It may be overkill for this use case, but I would probably use the
parsack package, since I think its interface is pretty intuitive.
However, it would be nice to have some sort of inverse to printf that
parses values given a template string. Racket has a very good set of
tools for formatting text, but it seems that doing the inverse is much
harder, which seems to be against Racket’s “batteries included”
philosophy.

Would anyone object to a scanf-like function in Racket itself? The
obvious point of difficulty is how to handle errors, given that parsing
can fail but printing cannot. Should it throw an exception, or should it
return #f? I’m not sure there is great precedent set here, though
throwing seems to be the Racket way in other places.

Stephen Chang

unread,
Mar 23, 2016, 12:20:41 AM3/23/16
to Alexis King, rom cgb, Racket Users
> It may be overkill for this use case, but I would probably use the
> parsack package, since I think its interface is pretty intuitive.

:) I had started writing up a parsack example, and I was all set to
admonish the OP for not creating a parser when you want a parser but
then I saw it was for a programming contest where I guess this sort of
scanf/regexp hackery is ok?

> Should it throw an exception, or should it
> return #f? I’m not sure there is great precedent set here, though
> throwing seems to be the Racket way in other places.

printf throws an exception when there's a type mismatch, eg (printf "~b" "1")

(thanks for the example Ben)

Mark Engelberg

unread,
Mar 23, 2016, 1:09:30 AM3/23/16
to Stephen Chang, Alexis King, rom cgb, Racket Users
On Tue, Mar 22, 2016 at 9:20 PM, Stephen Chang <stc...@ccs.neu.edu> wrote:
:) I had started writing up a parsack example, and I was all set to
admonish the OP for not creating a parser when you want a parser but
then I saw it was for a programming contest where I guess this sort of
scanf/regexp hackery is ok?

Generally the purpose of these sorts of programming contests is to solve as many programming problems as you can in the span of, say, 3 hours.  So it is all about writing code as fast as you can, nothing else counts.

One of the ground rules in the programming contests in my area is that you cannot use any libraries that don't come with the language.  So if it comes with the Racket distribution, that's fine.  But you can't pre-code up a library of functions you would like to use, and you can't download and install a package from Planet or Github.  Does Parsack come with Racket?  Is Parsack the quickest way (in terms of time to write the code) to dice the file into a list of datasets?  Those are the questions that matter here.

George Neuner

unread,
Mar 23, 2016, 5:33:06 AM3/23/16
to racket...@googlegroups.com
On Tue, 22 Mar 2016 21:06:14 -0700, Alexis King
<lexi....@gmail.com> wrote:

>Would anyone object to a scanf-like function in Racket itself?

No.

But the question is how to specify input patterns to make it really
useful. Racket's native printf is pretty basic. Should scanf mirror
the native printf? Or should it mirror something like SRFI-48 format,
which has more functionality?

Or should it work with predicates that mirror racket/format output
functions?

Or, in general, should it accept any suitable predicate?
[e.g., string->number]


>The
>obvious point of difficulty is how to handle errors, given that parsing
>can fail but printing cannot. Should it throw an exception, or should it
>return #f? I’m not sure there is great precedent set here, though
>throwing seems to be the Racket way in other places.

Printing can fail both for formatting and I/O errors.

C's scanf returns the count of patterns matched (in order from the
beginning of the string), but I don't recall offhand whether that
includes patterns that use '*' to suppress assignment to an argument.
C's scanf also allows to limit expected field widths.

And an I/O error does not lose matches that preceded the error. Any
arguments that were bound to values before the error occurred retain
their values.

Not that a Racket scanf necessarily has to work anything like C's.


Returning multiple values presents a problem if there is a mistake in
the input unless you have a distinct *unbound* value for unmatched
patterns (which #f is not).

I suppose #<void> works for an unbound indicator, but parsing
functions are expected to fail due to mistakes in the input. Returning
(values ...) from such functions ranks rather high on my offense meter
because I see little value to expecting, e.g., 10 return values and
getting back 8 of them set to #<void>. And returning too few values
results in an program error.

Perhaps the best thing would be just to return a list of the
successful matches. I would only throw I/O errors, not match failures
... similar to the way regex-match et al work.


George

rom cgb

unread,
Mar 23, 2016, 12:35:26 PM3/23/16
to Racket Users
Thanks all for the interesting replies.

About using an external package, there also the case like on www.hackerrank.com where you have to run the code in their own environment (eg: http://i.imgur.com/iSSPLGy.png).

Ty Coghlan

unread,
Mar 23, 2016, 4:17:09 PM3/23/16
to Racket Users
On Wednesday, March 23, 2016 at 12:35:26 PM UTC-4, rom cgb wrote:
> Thanks all for the interesting replies.
>
> About using an external package, there also the case like on www.hackerrank.com where you have to run the code in their own environment (eg: http://i.imgur.com/iSSPLGy.png).

I use racket for hackerrank and coding contests all the time, and I find it's read syntax really useful. For instance, I would parse this into a list of lists by doing
(for/list ([i (in-range (read))])
(map (lambda (x) (if (list? x) (cadr x) x)) (read))).

Then, to print out results, I normally do
(define (main n)
(unless (= 0 n)
(begin (printf "~a ... ~a\n", args...)
(main (sub1 n)))).

Another really useful helper is
(define (read->list n)
(if (= 0 n) '()
(cons (read) (read->list (sub1 n)))).

You can customize these by doing for/vector, for/fold, etc., and there hasn't been a hackerrank contest I've run into that I haven't been able to do with some technique like this.

Ty Coghlan

unread,
Mar 23, 2016, 4:49:50 PM3/23/16
to Racket Users
> I use racket for hackerrank and coding contests all the time, and I find it's read syntax really useful. For instance, I would parse this into a list of lists by doing
> (for/list ([i (in-range (read))])
> (map (lambda (x) (if (list? x) (cadr x) x)) (read))).
>
> Then, to print out results, I normally do
> (define (main n)
> (unless (= 0 n)
> (begin (printf "~a ... ~a\n", args...)
> (main (sub1 n)))).
>
> Another really useful helper is
> (define (read->list n)
> (if (= 0 n) '()
> (cons (read) (read->list (sub1 n)))).
>
> You can customize these by doing for/vector, for/fold, etc., and there hasn't been a hackerrank contest I've run into that I haven't been able to do with some technique like this.

Correction, not "read syntax". My PL professor would fail me for that. I'd say that there are very few hackerrank and other contests input format that can't be parsed as an s-expression in some way (unless they do weird things with parentheses, in which case you have to use read-line/char/byte).

Just note that a value with a comma in front of it is parsed as a list where the first item in the list is the unquote, and the second item is the value itself. Regardless, it's fairly easy to use the common list functions to extract the data you want.

rom cgb

unread,
Mar 24, 2016, 8:38:22 AM3/24/16
to Racket Users
A possible case study: there a scanf procedure in slib, a pseudo standard library for Scheme[1][2]. It does mutate the passed arguments like with C's scanf which i think is not the idiomatic way to do things in Racket[3]

doc: http://people.csail.mit.edu/jaffer/slib/Standard-Formatted-Input.html#Standard-Formatted-Input
code: http://cvs.savannah.gnu.org/viewvc/slib/slib/scanf.scm?view=markup

[1] https://en.wikipedia.org/wiki/SLIB
[2] http://people.csail.mit.edu/jaffer/SLIB.html
[3] http://blog.racket-lang.org/2007/11/getting-rid-of-set-car-and-set-cdr.html

Sam Tobin-Hochstadt

unread,
Mar 24, 2016, 8:40:15 AM3/24/16
to rom cgb, Racket Users

Right, that's the library that I borrowed for the code I posted. Fortunately the code didn't need to mutate pairs so it seems to work.

Sam


Jos Koot

unread,
Mar 24, 2016, 3:59:59 PM3/24/16
to rom cgb, Racket Users
Hi

In all computer languages it is more difficult to read data than to write
them, I think.
Racket (like more lisp-like languages) is rather easy on this:
give it sexprs to read and you have no problems.
If you want to accept other types of input, you need a parser.
In the past I have made programs accepting a specific language for its
input, often in Fortran.
In Fortran even reading a Fortran-like expression is cumbersome!

Designing the forms input may have includes the necessarity to design a
procedure that transforme the input to data that can be handled within the
language used for the procedures that have to handle these data.

Moreover, Racket provides elaborated tools to define and implement the
language of the input you want to read.

my2cents, or may be 1 only.
Jos

Hendrik Boom

unread,
Mar 25, 2016, 11:21:14 AM3/25/16
to Jos Koot, rom cgb, Racket Users
On Thu, Mar 24, 2016 at 08:59:50PM +0100, Jos Koot wrote:
> Hi
>
> In all computer languages it is more difficult to read data than to write
> them, I think.

Perhaps because when you write data you know what you are writing and
are in control. But when you are reading, who knows what might be
lurking at the threshold of the file?

-- hendrik

Richard Cleis

unread,
Mar 26, 2016, 1:37:11 AM3/26/16
to Racket Users
"Lurking thresholds" are fun:

I used pre-Racket to read files of numerical data, created by different agencies across the country.
The code looked for something that looked like a date (out of about 10 formats), and moved on from there to read a few hundred lines of gradually changing groups of numbers. 

The code was quite good at rummaging beyond various useless headers that were created over the years by new programmers attempting to improve the world... until one day, when someone actually made a truly useful header with an example that contained representative data.

rac
-- 
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Hendrik Boom

unread,
Mar 26, 2016, 11:04:18 AM3/26/16
to Richard Cleis, Racket Users
On Fri, Mar 25, 2016 at 11:28:25PM -0600, Richard Cleis wrote:
> "Lurking thresholds" are fun:

The Lurker at the Threshold is, of course, a classic horror story written by August Derleth and H.P. Lovecraft.

-- hendrik

m.eck...@icloud.com

unread,
Mar 31, 2016, 9:54:15 PM3/31/16
to Racket Users, romain...@gmail.com
Isn't it true that the scanf format string is just a poor man's regular expression pattern (i.e. just character classes) that however also allows you to specify how to convert matches? It may be a succinct notation, but it is also misleading (for example, a single space in the format string matches any amount of whitespace in the input!?). And it is inflexible: What if I create a new data structure? The implementation will not provide for a conversion.

I don't think there is need for a c-like scanf in racket. Why not just use regexp-match and do something like this:

(define (regexp-match-format pattern input . conversion-procs)
(define reports (rest (regexp-match pattern input)))
(for/list ([r reports]
[c conversion-procs])
(c r)))

And use it like this:

(regexp-match-format #rx"\\[(.+), *(.+), *(.+)\\]"
(read-line)
string->number
values
string->number)

Sorawee Porncharoenwase

unread,
Jul 21, 2017, 1:56:07 AM7/21/17
to Racket Users, m.eck...@icloud.com
Sorry for reviving an old thread. As someone who did a lot of programming competition in the past, I can totally see why IO in Racket is difficult. As mark.engelberg said, the goal is to solve as many problems as possible. After the competition is over, it's over. No one is going to care about maintainability or good coding practice if it makes coding slower. For this reason, I wouldn't use parsack if possible. While elegant, there's too much to write which is a waste of time.

Luckily, major programming competitions (say, Facebook Hacker Cup, Google Code Jam, early IOI, ACM-ICPC, etc.) usually have simple input format. Data are usually separated by spaces. Rarely spaces matter. rom gcb says that the input is:

3
[04, foo, 03.5]
[05, bar, 04.6]
[06, fun, 05.7]

but in these competitions, the input is more likely to be:

3
04 foo 03.5
05 bar 04.6
06 fun 05.7

What really gets in the way is that strings in the input could be something that Racket's (read) doesn't recognize. As an example, a string might be #asd. This would cause (read) to error "read: bad syntax `#a'".

Indeed, using (read-line) along with regex would solve the problem. The downside is that it requires a lot of writing and is heavily task-dependent. What I want is a uniform way to extract data easily like scanf.

I write the following module for this purpose. It's a lot of code, so it would be useless in closed competitions like ACM-ICPC. However, for online competitions like FB Hacker Cup, I find it pretty useful.


#lang racket

(provide (all-defined-out))

(define read-next
(let ([buffer #f])
(lambda (#:to-eol [to-eol #f])
(define (more!) (set! buffer (read-line)) (read-next #:to-eol to-eol))
(match buffer
[(? string?)
(cond
[to-eol (let ([ret buffer]) (set! buffer #f) ret)]
[else
(set! buffer (string-trim buffer #:right? #f))
(match (regexp-match #px"\\S+" buffer)
[(list ret) (set! buffer (regexp-replace #px"^\\S+" buffer "")) ret]
[#f (more!)])])]
[_ (more!)]))))

(define (%list n typ) (thunk (for/list ([_ n]) (typ))))
(define (%gets) (read-next #:to-eol #t))
(define (%num) (string->number (read-next)))
(define (%str) (read-next))
(define-syntax-rule (scan [v m] ...) (begin (define v (m)) ...))

(module+ test
(require rackunit)
(parameterize
([current-input-port
(open-input-string
(string-append " 2 3 \n"
"1 2 3\n"
" 4 5 6 \n"
" \n"
" \n"
" \n"
"\n"
" '\"has-quotes\"' blah blah "))])
(scan [n %num]
[m %num]
[matrix (%list n (%list m %num))]
[trailing-spaces %gets]
[str-with-quotes %str]
[line-with-space %gets])
(check-equal? n 2)
(check-equal? m 3)
(check-equal? matrix (list (list 1 2 3) (list 4 5 6)))
(check-equal? trailing-spaces " ")
(check-equal? str-with-quotes "'\"has-quotes\"'")
(check-equal? line-with-space " blah blah ")))

For example, https://code.google.com/codejam/contest/6254486/dashboard#s=p1 can be solved as follows:

(scan [n %num])
(for ([i n])
(scan [s %str])
(define simp (regexp-replaces s '([#px"\\++" "+"] [#px"\\-+" "-"])))
(define len (string-length simp))
(printf "Case #~a: ~a\n"
(add1 i)
(match (list (string-ref simp 0) (even? len))
[(or (list #\+ #t) (list #\- #f)) len]
[(or (list #\+ #f) (list #\- #t)) (sub1 len)])))

Matthias Felleisen

unread,
Jul 21, 2017, 1:49:23 PM7/21/17
to Sorawee Porncharoenwase, Racket Users, m.eck...@icloud.com

> On Jul 21, 2017, at 1:56 AM, Sorawee Porncharoenwase <sorawee_por...@brown.edu> wrote:
>
> Sorry for reviving an old thread. As someone who did a lot of programming competition in the past, I can totally see why IO in Racket is difficult. As mark.engelberg said, the goal is to solve as many problems as possible. After the competition is over, it's over. No one is going to care about maintainability or good coding practice if it makes coding slower.


Which is why I think programming competitions are a direct attempt to undermine computer science and computer-science education.

Having said that, if you want to use Racket for competitions, you and Mark Engelberg should get together and produce a high-utility IO library. We can then include it in the distribution.


Neil Van Dyke

unread,
Jul 21, 2017, 3:13:47 PM7/21/17
to Racket Users
Racket's underlying I/O seems sophisticated to me, and contest-friendly
parsing conveniences can be layered atop that.

IIRC, a long time ago, Olin Shivers made some parsing stuff for Scheme,
which you might want to steal or at least look at. I'm not sure where
it all went, or who else was involved, but I'd start looking here:

https://scsh.net/docu/html/man-Z-H-7.html#node_chap_6
https://scsh.net/docu/html/man-Z-H-8.html#node_chap_7
https://scsh.net/docu/html/man-Z-H-9.html#node_chap_8

The Racket package catalog is also worth a look. I mostly know my own
old packages, so I'll mention a few parsing-related ones:

For the broad category of CSV format data files (in addition to lots of
quoting and escaping conventions, can also handle inputs like ASCII
tables with columns separated by vertical bars, for example):
http://www.neilvandyke.org/racket/csv-reading/

For JSON parsing, you probably want to use the core Racket JSON stuff,
but at one point I accidentally made a folding JSON parser
(unfortunately, not using `syntax-parse`) that might come in handy for
big data:
http://www.neilvandyke.org/racket/json-parsing/

For HTML parsing and rewriting, an ancient Scheme library probably still
works (but you'll want to get comfortable with SXML ahead of time,
especially if you're new to old-school list processing):
http://www.neilvandyke.org/racket/html-parsing/
http://www.neilvandyke.org/racket/sxml-intro/

If a contest ever requires that your program modify its own source file,
we got yo back:
http://www.neilvandyke.org/racket/progedit/

Aside on programming contests: I see them as a good *side* thing, kept
in perspective. It's an alternative way that some people get excited
about programming and problem-solving, and then put in the work and
learn things along the way. Two provisos:

(1) We have to be aware that contests can be anti-engineering, and
remember to also learn engineering. Know when to be in contest mode,
vs. normal engineering mode, vs. somewhat sloppier mode, vs. urgent yet
it must work perfectly and resiliently the first time or the asteroid
will destroy Earth engineering mode, vs. the asteroid mode plus it might
also need keep deflecting asteroids for years after mode.

(2) Don't let the existence of programming contests discourage people
from learning programming or other STEM stuff. The contests are
artificial, and only loosely related to programming/STEM goals. Some
people have a big head start on being good at contests, but, if you want
to do programming and other STEM stuff, you can get good at that without
ever having to be good at contests, nor even try contests. (Related:
try to find and remember a balance between humility and confidence -- in
school, immediately post-school, and later. If we're always thinking
that we have more to learn, but that we do know some things, and can
build upon that to help tackle goals that seem hard, I think that's a
good starting point for finding this balance.)

Brian Mastenbrook

unread,
Jul 21, 2017, 5:33:27 PM7/21/17
to racket...@googlegroups.com
Just as a counterpoint, I have this kind of ad-hoc
parse-this-produce-that problem all the time in the "real" world. When
the logic of the problem is sufficiently complex I'll swallow the
overhead of doing it in Racket, or I'll use Perl to transform the input
into something I can read in Racket. It would be nice to have an I/O
library that made it easy, but I don't have any specific thoughts about
how to do that.

I don't think computer science education should ignore this kind of
problem though. It's very important to teach students how to solve
problems methodically with a design recipe, how to collaborate with
others, and how to reason about their programs. But it's also important
that programmers (and also or even especially those who don't program
for a living) be comfortable with using the machine to solve or automate
the solution to one-off problems that would otherwise require a lot of
manual fiddling with data. Being fluent with this kind of programing
gives people the confidence they need to solve smaller problems or
explore potential solutions to large problems in an unstructured manner
before tackling the methodical, "right" solution.

--
Brian Mastenbrook
br...@mastenbrook.net
https://brian.mastenbrook.net/

Gustavo Massaccesi

unread,
Jul 21, 2017, 6:21:38 PM7/21/17
to Brian Mastenbrook, Racket-Users List
IIRC the previous discussion, one way to fix this problem is to make a
package that can parse strings easily using a C like format, like the
expected in the contests. Something like

#lang racket
(require compatibility/scanf)

(let-values ([(x y) (scanf "%d %d" (read-line))])
(display (+ x y)))

It's not very idiomatic, but it may be helpful for this particular use.

IIRC another problem is that in the contest you are not allowed to
install the packages you wish, you can only use the default
installation. But I think that the idea is not to allow everything in
the default installation.


So my proposal is to add another installation mode for Racket

* minimal: Only the core and some minimal packages
* main: The normal set of packages, (only idiomatic) batteries included.
* community (new): everything in the ring 1, including the kitchen
sink. With a minimal curation and a minimal baseline of features (like
no breaking the installation). (And a compatible license.) The
packages may disappear/change/reappear from version to version. All
the additional packages may be experimental/unstable/unsuported.

Is this acceptable in a competition?
Is this acceptable as another "official" Racket distribution?

Gustavo

David Storrs

unread,
Jul 22, 2017, 3:30:52 PM7/22/17
to Gustavo Massaccesi, Brian Mastenbrook, Racket-Users List
One thing that would solve a lot of this issue would be if the pregexp syntax added support for named captures as in Perl and the PCRE library that exports them.  Something like this:

#lang at-exp racket

(define str "[04, foo, 03.5]")
(define pat @(pregexp @~a{\[(?<item-num>\d+),\s*(?<name>\w+),\s*(?<price>[.\d]+)}))

(match str
  [pat (println (~a "item name: " name ", number: " item-num ", price: $" price))])

Alternatively, if 'match' made the results of a successful regexp test available to the bodies on the RHS then you could do the same thing by accessing the result list.  Perhaps if match would allow the RHS to be a function?

(match "foo"
  [#px"oo.+(.)" (lambda (res) (println (~a "Regexp match results were: " res)))])

Output:
'("oobar" "r")


> For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscribe@googlegroups.com.

Jon Zeppieri

unread,
Jul 22, 2017, 4:49:48 PM7/22/17
to David Storrs, Gustavo Massaccesi, Brian Mastenbrook, Racket-Users List
On Sat, Jul 22, 2017 at 3:30 PM, David Storrs <david....@gmail.com> wrote:
>
> Alternatively, if 'match' made the results of a successful regexp test
> available to the bodies on the RHS then you could do the same thing by
> accessing the result list. Perhaps if match would allow the RHS to be a
> function?
>
> (match "foo"
> [#px"oo.+(.)" (lambda (res) (println (~a "Regexp match results were: "
> res)))])
>
> Output:
> '("oobar" "r")
>

This, at least, already exists:

(match "foobar"
[(pregexp #px"oo.+(.)" res)
(println (~a "Regexp match results were: " res))])

David Storrs

unread,
Jul 22, 2017, 5:41:34 PM7/22/17
to Jon Zeppieri, Gustavo Massaccesi, Brian Mastenbrook, Racket-Users List
Oh, cool.  I did not know about that.  Thanks, Jon.

Matthew Butterick

unread,
Jul 22, 2017, 8:29:36 PM7/22/17
to David Storrs, Racket-Users List
On Jul 22, 2017, at 12:30 PM, David Storrs <david....@gmail.com> wrote:

One thing that would solve a lot of this issue would be if the pregexp syntax added support for named captures as in Perl and the PCRE library that exports them. 

Alternatively, if 'match' made the results of a successful regexp test available to the bodies on the RHS then you could do the same thing by accessing the result list.  Perhaps if match would allow the RHS to be a function?


This could also be a light syntactic abstraction over `cond`:

#lang at-exp racket

(define-syntax (regexp-case stx)
  (syntax-case stx (=> else)
    [(_ STR [PAT => PROC] ... [else . ELSE-BODY])
     #'(cond
         [(regexp-match PAT STR) => PROC] ...
         [else . ELSE-BODY])]
    [(_ STR [PAT => PROC] ...) #'(regexp-case STR [PAT => PROC] ... [else #f])]))

(define str "[04, foo, 03.5]")
(define pat @(pregexp @~a{\[(\d+),\s*(\w+),\s*([.\d]+)}))
(regexp-case str
             [pat => (λ (res) (match-let ([(list _ item-num name price) res])
                                (println (~a "item name: " name ", number: " item-num ", price: $" price))))])
                                
(regexp-case "foobar"
             [#px"oo.+(.)" => (lambda (res) (println (~a "Regexp match results were: " res)))])
Reply all
Reply to author
Forward
0 new messages