Home > Blockchain >  How to overwrite at a specific line of plain text file (.txt) in Racket?
How to overwrite at a specific line of plain text file (.txt) in Racket?

Time:09-20

I'm a newbie using the Racket language, but I got really stuck with the following problem working with text files. Here's an example of what I am trying to do:

I have a file (test.txt) that contains the following:

((A 10 "Ciel" 10)
(B 15 "Sprite" 10)
(C 20 "Coca-Cola" 10)
(D 12 "Fuze Tea" 10)
(E 50 "Hattori Sake" 10)
(F 40 "02Linn Soju" 10)
(G 16 "Fanta" 10)
(H 25 "Canada Dry" 10)
(A12 54 "Cigars" 10)
(Kiero 78 "Calculator" 10))

And what I want is to overwrite the contents of a line; let's say that at line three I want to replace with: (W 10 "Liquor" 10). This is what I'm expecting:

((A 10 "Ciel" 10)
(B 15 "Sprite" 10)
(W 10 "Liquor" 10)
(D 12 "Fuze Tea" 10)
(E 50 "Hattori Sake" 10)
(F 40 "02Linn Soju" 10)
(G 16 "Fanta" 10)
(H 25 "Canada Dry" 10)
(A12 54 "Cigars" 10)
(Kiero 78 "Calculator" 10))

Before asking this question I was researching on other StackOverflow posts, and I came across a similar situation to mine here. However, it's not the way I want to solve my problem because of these points:

  • The solution in that post is with two files. I'm expected to only overwrite/update/modify the same file (test.txt)
  • The proposal there is using for-loops; since this is for a series of exercises that are expected to encourage recursion and functional programming I'm not allowed to use them, nor set! set-car!, or anything that resembles the imperative paradigm.
  • Lastly, their solution only inserts a text between lines, but it does not overwrite one as such.

I don't want to seem like I'm just coming here to copy-paste a solution from comments, so I did put some effort to code a function. It kinda works, but not in the way I expected. Here's my attempt:

(define (write-line-to-file str line-number filename) 
    (with-output-to-file filename #:exists 'update
        (lambda () (when (>= line-number 1) (newline)) 
            (printf str)(newline))))

Here's my function calling:

(write-line-to-file "(W 10 \"Liquor\" 10)" 2 "test.txt")

And here's what I got:


(W 10 "Liquor" 10)
 15 "Sprite" 10)
(C 20 "Coca-Cola" 10)
(D 12 "Fuze Tea" 10)
(E 50 "Hattori Sake" 10)
(F 40 "02Linn Soju" 10)
(G 16 "Fanta" 10)
(H 25 "Canada Dry" 10)
(A12 54 "Cigarros" 10)
(Kiero 78 "Calculadora" 10))

Any idea how to modify the function to do the following:

((A 10 "Ciel" 10)
(B 15 "Sprite" 10)
(W 10 "Liquor" 10)
(D 12 "Fuze Tea" 10)
(E 50 "Hattori Sake" 10)
(F 40 "02Linn Soju" 10)
(G 16 "Fanta" 10)
(H 25 "Canada Dry" 10)
(A12 54 "Cigars" 10)
(Kiero 78 "Calculator" 10))

Instead of this:


(W 10 "Liquor" 10)
 15 "Sprite" 10)
(C 20 "Coca-Cola" 10)
(D 12 "Fuze Tea" 10)
(E 50 "Hattori Sake" 10)
(F 40 "02Linn Soju" 10)
(G 16 "Fanta" 10)
(H 25 "Canada Dry" 10)
(A12 54 "Cigars" 10)
(Kiero 78 "Calculator" 10))

CodePudding user response:

First off, don't try to modify the file in place; as you're discovering, that only really works when the sequence of new bytes you're writing is the same length as the ones you're trying to replace. Otherwise more than you intended gets overwritten, or partial old data remains. Instead, read the original file, make changes in memory, and then write out the new contents.

Secondly, since your data is an s-expression, don't think in terms of lines, think in terms of list elements.

For example, to replace the Nth (0-based) element of a list in a file:

#lang racket/base

(require racket/pretty racket/list)

(define (replace-nth-element-in-file filename n new-elem)
  (let ([lst (with-input-from-file filename read)])
    (with-output-to-file filename #:exists 'truncate/replace
      (lambda () (pretty-write (list-set lst n new-elem))))))
(replace-nth-element-in-file "test.txt" 2 '(W 10 "Liquor" 10))

If you do want to/have to go by actual lines, port->lines is a good start.

  • Related