延迟读取球拍中的自定义类型
Lazy reads of custom types in Racket
我是 Racket 的新手,我正在尝试编写一个函数来读取文件的行,将每一行解析为 struct
,return 我的惰性序列数据类型。这是我的输入格式的一个简单示例(具有行名和列名的矩阵)。我的实际输入格式还包括一个 header 行,我在这里省略它,并且包含非常大的文件,这就是我需要懒惰的原因。
R1 1.0 2.3 1.2
R2 1.2 3.1 3.4
这是我最近的尝试:
(struct row (key data))
(define (read-matrix in)
(for [(line (in-lines in))]
(let ([fields (string-split line "\t")]
(row (first fields) (list->vector (map string->number (rest fields))))
)))
我还尝试了许多其他方法,包括使用 call-with-input-file
。我对上述方法的问题是,如果我使用 #lang racket
它不是懒惰的,并且 #lang lazy
string-split
未定义。我应该补充一点,在我的用例中,我想要的语义是在使用完整个序列后关闭端口,因为我可以保证要么使用整个序列,要么程序终止。
那么,我走在正确的轨道上吗?我应该采取什么方法来解决这个问题?谢谢!
尝试使用 SRFI-13 中的函数,这是一个字符串处理库,在 #lang lazy
中也可用:
(require srfi/13)
然后这样做:
[fields (string-tokenize line)]
最后我发现答案就是用Racket的sequence、streams和generator库来做这种东西。生成器特别好,允许一个简单的 Python-like "yield" 函数。这些功能允许惰性序列,而无需 #lang lazy
.
提供的全面惰性评估
我正在离线撰写这个答案,回来后发现您已经回答了大部分问题。如果细节对任何人有帮助,我都会 post。
如果你真的需要#lang lazy
,又想用string-split
,我想你可以直接(require racket/string)
来用吗?
我不确定我是否完全理解 "lazy" 在这里的意思。如果您担心的话,使用 in-lines
不会将整个文件吸入内存。它将一次处理一行。
你可以做的一件事是定义一个辅助函数,它处理读取和解析行,检查 eof
,并自动关闭输入端口:
(struct row (key data)
#:transparent)
;; Example couple lines of input to use below.
(define text "R1 1.0 2.3 1.2\nR2 1.2 3.1 3.4")
;; read-matrix-row : input-port? -> (or/c eof row?)
;;
;; Given an input port, try to read another row.
(define (read-matrix-row in)
(match (read-line in)
[(? eof-object?)
(close-input-port in)
eof]
[line (match (string-split line " ")
[(cons key data)
(row key (list->vector (map string->number data)))])]))
您可以通过多种方式使用此功能。一种方法是 in-producer
:
;; Example use with in-producer:
(let ([in (open-input-string text)])
(for/list ([x (in-producer read-matrix-row eof in)])
x))
;; => (list (row "R1" '#(1.0 2.3 1.2))
;; (row "R2" '#(1.2 3.1 3.4)))
该示例使用 for/list
生成 list
。当然,如果你有一个巨大的输入文件,那将产生一个巨大的 list
。但是您可以将它们一一显示,或者将它们一一写入文件或数据库:
;; Example use, displaying one by one.
(let ([in (open-input-string text)])
(for ([x (in-producer read-matrix-row eof in)])
(displayln x))) ;or write to some file, for example
如果您更喜欢 stream
界面,则可以很容易地从任何序列(包括“in-producer”)创建流:
;; If you prefer a stream interface, we can use sequence->stream to
;; transform the producer sequence into a stream:
(define (matrix-row-stream in)
(sequence->stream (in-producer read-matrix-row eof in)))
;; Example interactive use of the stream
(define stm (matrix-row-stream (open-input-string text)))
(stream-empty? stm) ;#f
(stream-first stm) ;(row "R1" '#(1.0 2.3 1.2))
(stream-empty? (stream-rest stm)) ;#f
(stream-first (stream-rest stm)) ;(row "R2" '#(1.2 3.1 3.4))
(stream-empty? (stream-rest (stream-rest stm))) ;#t
我是 Racket 的新手,我正在尝试编写一个函数来读取文件的行,将每一行解析为 struct
,return 我的惰性序列数据类型。这是我的输入格式的一个简单示例(具有行名和列名的矩阵)。我的实际输入格式还包括一个 header 行,我在这里省略它,并且包含非常大的文件,这就是我需要懒惰的原因。
R1 1.0 2.3 1.2
R2 1.2 3.1 3.4
这是我最近的尝试:
(struct row (key data))
(define (read-matrix in)
(for [(line (in-lines in))]
(let ([fields (string-split line "\t")]
(row (first fields) (list->vector (map string->number (rest fields))))
)))
我还尝试了许多其他方法,包括使用 call-with-input-file
。我对上述方法的问题是,如果我使用 #lang racket
它不是懒惰的,并且 #lang lazy
string-split
未定义。我应该补充一点,在我的用例中,我想要的语义是在使用完整个序列后关闭端口,因为我可以保证要么使用整个序列,要么程序终止。
那么,我走在正确的轨道上吗?我应该采取什么方法来解决这个问题?谢谢!
尝试使用 SRFI-13 中的函数,这是一个字符串处理库,在 #lang lazy
中也可用:
(require srfi/13)
然后这样做:
[fields (string-tokenize line)]
最后我发现答案就是用Racket的sequence、streams和generator库来做这种东西。生成器特别好,允许一个简单的 Python-like "yield" 函数。这些功能允许惰性序列,而无需 #lang lazy
.
我正在离线撰写这个答案,回来后发现您已经回答了大部分问题。如果细节对任何人有帮助,我都会 post。
如果你真的需要#lang lazy
,又想用string-split
,我想你可以直接(require racket/string)
来用吗?
我不确定我是否完全理解 "lazy" 在这里的意思。如果您担心的话,使用 in-lines
不会将整个文件吸入内存。它将一次处理一行。
你可以做的一件事是定义一个辅助函数,它处理读取和解析行,检查 eof
,并自动关闭输入端口:
(struct row (key data)
#:transparent)
;; Example couple lines of input to use below.
(define text "R1 1.0 2.3 1.2\nR2 1.2 3.1 3.4")
;; read-matrix-row : input-port? -> (or/c eof row?)
;;
;; Given an input port, try to read another row.
(define (read-matrix-row in)
(match (read-line in)
[(? eof-object?)
(close-input-port in)
eof]
[line (match (string-split line " ")
[(cons key data)
(row key (list->vector (map string->number data)))])]))
您可以通过多种方式使用此功能。一种方法是 in-producer
:
;; Example use with in-producer:
(let ([in (open-input-string text)])
(for/list ([x (in-producer read-matrix-row eof in)])
x))
;; => (list (row "R1" '#(1.0 2.3 1.2))
;; (row "R2" '#(1.2 3.1 3.4)))
该示例使用 for/list
生成 list
。当然,如果你有一个巨大的输入文件,那将产生一个巨大的 list
。但是您可以将它们一一显示,或者将它们一一写入文件或数据库:
;; Example use, displaying one by one.
(let ([in (open-input-string text)])
(for ([x (in-producer read-matrix-row eof in)])
(displayln x))) ;or write to some file, for example
如果您更喜欢 stream
界面,则可以很容易地从任何序列(包括“in-producer”)创建流:
;; If you prefer a stream interface, we can use sequence->stream to
;; transform the producer sequence into a stream:
(define (matrix-row-stream in)
(sequence->stream (in-producer read-matrix-row eof in)))
;; Example interactive use of the stream
(define stm (matrix-row-stream (open-input-string text)))
(stream-empty? stm) ;#f
(stream-first stm) ;(row "R1" '#(1.0 2.3 1.2))
(stream-empty? (stream-rest stm)) ;#f
(stream-first (stream-rest stm)) ;(row "R2" '#(1.2 3.1 3.4))
(stream-empty? (stream-rest (stream-rest stm))) ;#t