跳转到内容

newLISP 字符串简介

来自 Wikibooks,开放书籍,开放世界

字符串

[编辑 | 编辑源代码]

字符串处理工具是编程语言的重要组成部分。newLISP 拥有许多易于使用且功能强大的字符串处理工具,如果您特定需求没有得到满足,可以轻松地将更多工具添加到您的工具箱中。

这是一次对 newLISP 字符串乐团 的导览。

newLISP 代码中的字符串

[编辑 | 编辑源代码]

您可以通过三种方式编写字符串

  • 用双引号括起来
  • 用花括号包围
  • 由标记代码标记

像这样

(set 's "this is a string")
(set 's {this is a string})
(set 's [text]this is a string[/text])

所有三种方法都可以处理最多 2048 个字符的字符串。对于超过 2048 个字符的字符串,始终使用 [text][/text] 标签来包围字符串。

如果您希望处理转义字符,例如 \n 和 \t,或代码数字 (\046),则始终使用第一种方法,即引号。

(set 's "this is a string \n with two lines")
(println s)
this is a string 
with two lines
(println "\110\101\119\076\073\083\080")    ; decimal ASCII
newLISP
(println "\x6e\x65\x77\x4c\x49\x53\x50")    ; hex ASCII
newLISP

双引号字符必须用反斜杠转义,反斜杠也必须用反斜杠转义,如果您希望它们出现在字符串中。

对于小于 2048 个字符的字符串,如果您不希望处理任何转义字符,请使用第二种方法,即花括号(或“大括号”)。

(set 's {strings can be enclosed in \n"quotation marks" \n })
(println s)
strings can be enclosed in \n"quotation marks" \n

这是一种非常有用的编写字符串的方式,因为您不必担心在每个引号字符前加反斜杠,或在其他反斜杠前加反斜杠。您可以在花括号字符串内嵌套花括号对,但不能有未匹配的花括号。我喜欢使用花括号来编写字符串,因为它们朝向正确的方向(而普通 愚蠢 的引号则没有),而且您的文本编辑器可能会平衡和匹配它们。

第三种方法,使用 [text][/text] 标记,适用于跨越多行的较长文本字符串,并且当 newLISP 输出大量文本时会自动使用。同样,您不必担心可以和不能包含哪些字符 - 您可以随意放入任何字符,显而易见的例外是 [/text]。转义字符,例如 \n 或 \046,也不会被处理。

(set 'novel (read-file {my-latest-novel.txt}))

;->
[text]
It was a dark and "stormy" night...
...
The End.
[/text]


如果您想知道字符串的长度,请使用 length

(length novel)
;-> 575196


newLISP 可以轻松处理数百万个字符的字符串。

而不是 length,使用 utf8len 获取 Unicode 字符串的长度

(utf8len (char 955))
;-> 1

(length (char 955))
;-> 2


创建字符串

[编辑 | 编辑源代码]

许多函数,例如文件读取函数,会为您返回字符串或字符串列表。但是,如果您想从头开始构建字符串,一种方法是从 char 函数开始。它将提供的数字转换为具有该代码数字的等效字符字符串。它也可以反转操作,将提供的字符字符串转换为其等效代码数字。)

(char 33)
;-> "!"
(char "!")
;-> 33
(char 955)       ; Unicode lambda character, decimal code
;-> "\206\187"
(char 0x2643)    ; Unicode symbol for Jupiter, hex code
;-> "\226\153\131"


当您运行支持 Unicode 的 newLISP 版本时,最后两个示例可用。由于 Unicode 以十六进制为主,因此您可以为 char 提供以 0x 开头的十六进制数字。要查看实际字符,请使用打印命令

(println (char 955))

λ

;-> "\206\187"
(println (char 0x2643))

;-> "\226\140\152"

(println (char (int (string "0x" "2643"))))    ; equivalent

;-> "\226\140\152"


反斜杠数字是 println 函数的结果,可能是 Unicode 字符的字节值。

您可以使用 char 以其他方式构建字符串

(join (map char (sequence (char "a") (char "z"))))
;-> "abcdefghijklmnopqrstuvwxyz"


这使用 char 找出 az 的 ASCII 代码数字,然后使用 sequence 生成这两个代码数字之间的列表。然后将 char 函数映射到列表的每个元素上,从而生成一个字符串列表。最后,通过 join 将此列表转换为单个字符串。

join 在构建字符串时还可以使用分隔符

(join (map char (sequence (char "a") (char "z"))) "-")
;-> "a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-u-v-w-x-y-z"

join 类似的是 append,它直接对字符串起作用

(append "con" "cat" "e" "nation")
;-> "concatenation"


但更实用的是 string,它将任何数字、列表和字符串集合转换为单个字符串。

(string '(sequence 1 10) { produces } (sequence 1 10) "\n")
;-> (sequence 1 10) produces (1 2 3 4 5 6 7 8 9 10)


请注意,第一个列表没有被求值(因为它被引用),但第二个列表被求值以生成一个数字列表,并且结果列表 - 包括括号 - 被转换为字符串。

string 函数结合花括号和标记等各种字符串标记,是将变量的值包含在字符串中的一种方法

(set 'x 42)
(string {the value of } 'x { is } x) 
;-> "the value of x is 42"

您还可以使用 format 来组合字符串和符号值。请参阅格式化字符串

dup 制作副本

(dup "spam" 10)
;-> "spamspamspamspamspamspamspamspamspamspam"

date 生成一个日期字符串

(date)
;-> "Wed Jan 25 15:04:49 2006"

或者,您可以给它一个自 1970 年以来的秒数进行转换

(date 1230000000) 
;-> "Tue Dec 23 02:40:00 2008"


请参阅处理日期和时间

字符串处理

[编辑 | 编辑源代码]

现在您已经有了字符串,还有许多函数可以对其进行操作。其中一些是破坏性函数 - 它们永久更改字符串,可能会永久丢失信息。另一些是构造性的,生成一个新的字符串,而不影响旧字符串。请参阅破坏性函数

reverse 是破坏性的

(set 't "a hypothetical one-dimensional subatomic particle")
(reverse t)
;-> "elcitrap cimotabus lanoisnemid-eno lacitehtopyh a"

现在 t 已永久更改。但是,大小写转换函数不是破坏性的,它们会生成新的字符串,不会损害旧字符串

(set 't "a hypothetical one-dimensional subatomic particle")

(upper-case t)
;-> "A HYPOTHETICAL ONE-DIMENSIONAL SUBATOMIC PARTICLE"

(lower-case t)
;-> "a hypothetical one-dimensional subatomic particle"

(title-case t)
;-> "A hypothetical one-dimensional subatomic particle"

子字符串

[编辑 | 编辑源代码]

如果您知道要提取字符串的哪个部分,请使用以下构造函数之一

(set 't "a hypothetical one-dimensional subatomic particle")
(first t)
;-> "a"

(rest t)
;-> " hypothetical one-dimensional subatomic particle"

(last t)
;-> "e"

(t 2)            ; counting from 0
;-> "h"

您也可以将此技术应用于列表。请参阅从列表中选择项目

字符串切片

[编辑 | 编辑源代码]

slice 会为您提供现有字符串的切片,从切口处向前计数(正整数)或从末尾处向后计数(负整数),以获得指定数量的字符或直到指定的位点

(set 't "a hypothetical one-dimensional subatomic particle")
(slice t 15 13)
;-> "one-dimension"

(slice t -8 8)
;-> "particle"

(slice t 2 -9)
;-> "hypothetical one-dimensional subatomic"

(slice "schwarzwalderkirschtorte" 19 -1)
;-> "tort"

也有一个快捷方式可以做到这一点。将所需的开始和长度放在字符串前的列表中

(15 13 t)
;-> "one-dimension"

(0 14 t)
;-> "a hypothetical"

如果您不想要连续的字符,而是要为新字符串挑选一些字符,请使用 select 以及一系列字符索引号

(set 't "a hypothetical one-dimensional subatomic particle")
(select t 3 5 24 48 21 10 44 8)
;-> "yosemite"

(select t (sequence 1 49 12)) ; every 12th char starting at 1
;-> " lime"

这对于查找隐藏在文本中的秘密编码消息很有用。

修改字符串的末尾

[编辑 | 编辑源代码]

trimchop 都是构造性字符串编辑函数,从原始字符串的末端向内工作。

chop 从末尾开始工作

(chop t)       ; defaults to the last character
;-> "a hypothetical one-dimensional subatomic particl"

(chop t 9)     ; chop 9 characters off
;-> "a hypothetical one-dimensional subatomic"

trim 可以从两端移除字符

(set 's "        centred       ")
(trim s)            ; defaults to removing spaces
;-> "centred"

(set 's "------centred------")
(trim s "-")
;-> "centred"

(set 's "------centred******")
(trim s "-" "*")    ; front and back
;-> "centred"

push 和 pop 也适用于字符串

[编辑 | 编辑源代码]

您已经看到 pushpop 在向列表添加和删除项目。它们也适用于字符串。使用 push 向字符串添加字符,使用 pop 从字符串中删除一个字符。除非您指定索引,否则字符串将被添加到字符串开头或从字符串开头删除。

(set 't "some ")
(push "this is " t)
(push "text " t -1)
;-> t is now "this is some text"

pop 始终返回弹出的内容,但 push 返回修改后的操作目标。当您想要拆分字符串并按顺序处理各个部分时,它很有用。例如,要打印 newLISP 版本号,该版本号存储为 4 位或 5 位整数,请使用以下方法

(set 'version-string (string (sys-info -2)))
; eg: version-string is now "10303"
(set 'dev-version (pop version-string -2 2))   ; always two digits
; dev-version is "03", version-string is "103"
(set 'point-version (pop version-string -1))   ; always one digit
; point-version is "3", version-string is now "10"
(set 'version version-string)                  ; one or two digits
(println version "." point-version "." dev-version)
10.3.03

从字符串的右侧开始工作并使用 pop 提取信息并将其删除在一个操作中更容易。

修改字符串

[编辑 | 编辑源代码]

更改字符串内部字符有两种方法。要么使用字符的索引号,要么指定要查找或更改的子字符串。

在字符串中使用索引号

[编辑 | 编辑源代码]

要通过索引号更改字符,请使用 **setf**,这是用于更改字符串、列表和数组的通用函数

(set 't "a hypothetical one-dimensional subatomic particle")
(setf (t 0) "A")
;-> "A"
t
;-> "A hypothetical one-dimensional subatomic particle"

您也可以使用 **nth** 和 **setf** 来指定位置

(set 't "a hypothetical one-dimensional subatomic particle")
;-> "a hypothetical one-dimensional subatomic particle"
(setf (nth 0 t) "A")
;-> "A"
t
;-> "A hypothetical one-dimensional subatomic particle"

以下是如何“递增”字符串的第一个(第零个)字母

(set 'wd "cream")
;-> "cream"
(setf (wd 0) (char (+ (char $it) 1)))
;-> "d"
wd
;-> "dream"

$it包含 **setf** 表达式第一部分找到的值,并且它的数值被递增以形成第二部分。

更改子字符串

[编辑 | 编辑源代码]

如果您不想(或不能)使用索引号或字符位置,请使用 **replace**,这是一个功能强大的破坏性函数,它对字符串执行各种有用的操作。以以下形式使用它

(replace old-string source-string replacement)

所以

(set 't "a hypothetical one-dimensional subatomic particle")
(replace "hypoth" t "theor")
;-> "a theoretical one-dimensional subatomic particle"

**replace** 是破坏性的,但是如果您想以构造性方式使用 **replace** 或其他破坏性函数的副作用,而无需修改原始字符串,请使用 **copy** 函数

(set 't "a hypothetical one-dimensional subatomic particle")
(replace "hypoth" (copy t) "theor")
;-> "a theoretical one-dimensional subatomic particle"
t
;-> "a hypothetical one-dimensional subatomic particle"

副本被 **replace** 修改。原始字符串 *t* 不会受到影响。

正则表达式

[编辑 | 编辑源代码]

**replace** 是 newLISP 函数组中的一员,这些函数接受正则表达式以定义文本中的模式。对于大多数函数,您需要在表达式末尾添加一个额外的数字,它指定正则表达式操作的选项:0 表示基本匹配,1 表示不区分大小写的匹配,等等。

(set 't "a hypothetical one-dimensional subatomic particle")
(replace {h.*?l(?# h followed by l but not too greedy)} t {} 0) 

;-> "a  one-dimensional subatomic particle"

有时我会在正则表达式中添加注释,以便几天后阅读代码时知道我试图做什么。在 (?# 和后面的右括号之间的文本会被忽略。

如果您乐于使用与 Perl 兼容的正则表达式 (PCRE),那么您会喜欢 **replace** 及其使用正则表达式的兄弟姐妹(**find**、**regex**、**find-all**、**parse**、**starts-with**、**ends-with**、**directory** 和 **search**)。完整详细信息请参阅 newLISP 参考手册。

您必须将您的模式引导通过 newLISP 阅读器和正则表达式处理器。请记住引号括起来的字符串和花括号括起来的字符串之间的区别?引号允许处理转义字符,而花括号则不处理。花括号有一些优势:它们在视觉上彼此相对,它们没有让人困惑的智能和哑版本,您的文本编辑器可能会为您平衡它们,并且它们允许您在字符串中使用更常见的引号字符,而无需始终对其进行转义。但是,如果您使用引号,则必须将反斜杠加倍,以便单个反斜杠完好无损地到达正则表达式处理器

(set 'str "\s")
(replace str "this is a phrase" "|" 0)  ; oops, not searching for \s (white space) ...
;-> thi| i| a phra|e                    ; but for the letter s 

(set 'str "\\s")
(replace str "this is a phrase" "|" 0)
;-> this|is|a|phrase                    ; ah, better!

系统变量:$0,$1 ...

[编辑 | 编辑源代码]

**replace** 使用一组系统变量 $0,$1,$2,一直到 $15,来更新匹配项。它们指的是模式中的括号表达式,等效于您可能熟悉的 \1,\2(如果您使用过 grep)。例如

(set 'quotation {"I cannot explain." She spoke in a low, eager voice,
with a curious lisp in her utterance. "But for God's sake do what I 
ask you. Go back and never set foot upon the moor again."})

(replace {(.*?),.*?curious\s*(l.*p\W)(.*?)(moor)(.*)} 
    quotation 
    (println {$1 } $1 { $2 } $2 { $3 } $3 { $4 } $4 { $5 } $5)
    4)
$1 "I cannot explain." She spoke in a low $2 lisp  $3 in her utterance.
"But for God's sake do what I ask you. Go back and never set foot upon 
the $4 moor $5 again."

在这里,我们查找了五个模式,它们由任何以逗号开头并以单词 curious 结尾的字符串隔开。$0 存储匹配的表达式,$1 存储第一个括号内的子表达式,依此类推。

如果您更喜欢使用引号而不是我在这里使用的花括号,请记住,某些字符必须用反斜杠进行转义。

替换表达式

[编辑 | 编辑源代码]

前面的示例说明了 **replace** 的一个重要功能,即替换不必仅仅是一个简单的字符串或列表,它可以是任何 newLISP 表达式。每次找到模式时,都会对替换表达式进行求值。您可以使用它来提供动态计算的替换值,或者您可以对找到的文本执行任何其他操作。甚至可以对与找到的文本毫无关系的表达式进行求值。

以下是一个示例:搜索字母 t 后面是字母 h 或任何元音,并打印出 **replace** 找到的组合

(set 't "a hypothetical one-dimensional subatomic particle")
(replace {t[h]|t[aeiou]} t (println $0) 0)
th
ti
to
ti
;-> "a hypothetical one-dimensional subatomic particle"

对于找到的每个匹配的文本片段,第三个表达式

(println $0)

被求值。这是在函数运行时查看正则表达式引擎在做什么的好方法。在这个例子中,原始字符串似乎没有改变,但实际上它确实改变了,因为(println $0)做了两件事:它打印了字符串,并且它将值返回给 **replace**,从而用自身替换找到的文本。无形缝合!如果替换表达式没有返回字符串,则不会发生替换。

您还可以执行其他有用的操作,例如构建匹配项列表以供以后处理,并且您可以使用 newLISP 系统变量和任何其他函数来使用找到的任何文本。

在下一个示例中,我们查找字母 a、e 或 c,并将每个出现的字母强制转换为大写

(replace "a|e|c" "This is a sentence" (upper-case $0) 0)
;-> "This is A sEntEnCE"

作为另一个示例,这里有一个简单的搜索和替换操作,它统计字母 'o' 在字符串中出现的次数,并将原始字符串中的每个出现替换为到目前为止的计数。替换是一个表达式块,这些表达式块被分组到一个 **begin** 表达式中。每次找到匹配项时,都会对该块进行求值

(set 't "a hypothetical one-dimensional subatomic particle")
(set 'counter 0)
(replace "o" t 
 (begin 
  (inc counter)
  (println {replacing "} $0 {" number } counter) 
  (string counter))         ; the replacement text should be a string 
 0)
replacing "o" number 1
replacing "o" number 2
replacing "o" number 3
replacing "o" number 4
"a hyp1thetical 2ne-dimensi3nal subat4mic particle"


**println** 的输出不会出现在字符串中;整个 **begin** 表达式的最终值为计数器的字符串版本,因此它将被插入到字符串中。

以下是一个关于 **replace** 的另一个例子。假设我有一个文本文件,内容如下

1 a = 15
2 another_variable = "strings"
4 x2 = "another string"
5 c = 25 
3x=9


我想编写一个 newLISP 脚本,以 10 为倍数重新编号行,从 10 开始,并将文本对齐,使等号对齐,如下所示

10 a                   = 15
20 another_variable    = "strings"
30 x2                  = "another string"
40 c                   = 25 
50 x                   = 9

(我不知道这是什么语言!)

以下脚本将完成此操作

(set 'file (open ((main-args) 2) "read"))
(set 'counter 0)
(while (read-line file)
 (set 'temp 
   (replace {^(\d*)(\s*)(.*)}        ; the numbering
     (current-line)
     (string (inc counter 10) " " $3) 
     0))
 (println 
   (replace {(\S*)(\s*)(=)(\s*)(.*)}  ; the spaces around =
    temp 
    (string $1 (dup " " (- 20 (length $1))) $3 " " $5) 
    0)))
(exit)

我在 **while** 循环中使用了两个 **replace** 操作,以使事情更清晰。第一个操作将一个临时变量设置为 replace 操作的结果。搜索字符串({^(\d*)(\s*)(.*)})是一个正则表达式,它查找一行开头的任何数字,后面跟着一些空格,后面跟着 *任何东西*。替换字符串((string (inc counter 10) " " $3) 0))由一个递增的计数器值组成,后面跟着第三个匹配项(即我刚刚查找的 *任何东西*)。

第二个 replace 操作的结果被打印出来。我在临时变量 *temp* 中搜索更多具有中间等号的字符串和空格

({(\S*)(\s*)(=)(\s*)(.*)})

替换表达式由重要的发现元素($1、$3、$5)构成,但它还包含一个快速计算,用于计算将等号移至字符 20 所需的空间量,这应该是第一个项目的宽度与位置 20 之间的差(我任意地将位置 20 作为等号的位置)。

正则表达式对于新手来说并不容易,但它们非常强大,特别是与 newLISP 的 **replace** 函数结合使用时,因此值得学习。

测试和比较字符串

[编辑 | 编辑源代码]

您可以对字符串运行各种测试。newLISP 的比较运算符通过查找并比较字符的代码编号来工作,直到做出决定为止

(> {Higgs Boson} {Higgs boson})         ; nil
(> {Higgs Boson} {Higgs})               ; true
(< {dollar} {euro})                     ; true
(> {newLISP} {LISP})                    ; true
(= {fred} {Fred})                       ; nil
(= {fred} {fred})                       ; true

当然,newLISP 的灵活参数处理允许您同时测试大量字符串

(< "a" "c" "d" "f" "h") 
;-> true

这些比较函数还允许您使用单个参数。如果您只提供一个参数,newLISP 会根据第一个参数的类型,帮助您假设您指的是 0 或 ""

(> 1)                               ; true - assumes > 0
(> "fred")                          ; true - assumes > ""

要检查两个字符串是否具有共同特征,您可以使用 **starts-with** 和 **ends-with**,或者使用更通用的模式匹配命令 **member**、**regex**、**find** 和 **find-all**。**starts-with** 和 **ends-with** 很简单

(starts-with "newLISP" "new")       ; does newLISP start with new?
;-> true
(ends-with "newLISP" "LISP")
;-> true

它们还可以接受正则表达式,使用其中一个正则表达式选项(0 是最常用的选项)

(starts-with {newLISP} {[a-z][aeiou](?\#lc followed by lc vowel)} 0)
;-> true
(ends-with {newLISP} {[aeiou][A-Z](?\# lc vowel followed by UCase)} 0)
;-> false

**find**、**find-all**、**member** 和 **regex** 在字符串中查找所有内容。**find** 返回匹配子字符串的索引

(set 't "a hypothetical one-dimensional subatomic particle")
(find "atom" t)
;-> 34

(find "l" t)
;-> 13

(find "L" t)
;-> nil                             ; search is case-sensitive

**member** 检查一个字符串是否在另一个字符串中。它返回字符串的剩余部分,包括搜索字符串,而不是第一个出现的索引。

(member "rest" "a good restaurant")
;-> "restaurant"

**find** 和 **member** 都允许您使用正则表达式

(set 'quotation {"I cannot explain." She spoke in a low,
eager voice, with a curious lisp in her utterance. "But for
Gods sake do what I ask you. Go back and never set foot upon
the moor again."})

(find "lisp" quotation)            ; without regex
;-> 69                             ; character 69

(find {i} quotation 0)             ; with regex
;-> 15                             ; character 15

(find {s} quotation 1)             ; case insensitive regex
;-> 20                             ; character 20

(println "character " 
 (find {(l.*?p)} quotation 0) ": " $0)  ; l followed by a p
;-> character 13: lain." She sp

**find-all** 的工作方式类似于 **find**,但它返回所有匹配字符串的列表,而不是仅第一个匹配项的索引。它始终接受正则表达式,因此 - 这一次 - 您不必在末尾添加正则表达式选项编号。

(set 'quotation {"I cannot explain." She spoke in a low,
eager voice, with a curious lisp in her utterance. "But for
Gods sake do what I ask you. Go back and never set foot upon
the moor again."})

(find-all "[aeiou]{2,}" quotation $0)       ; two or more vowels
;-> ("ai" "ea" "oi" "iou" "ou" "oo" "oo" "ai")

或者您可以使用 **regex**。如果字符串不包含模式,它将返回 **nil**,但是,如果它包含模式,它将返回一个包含匹配的字符串和子字符串以及每个字符串的起始位置和长度的列表。结果可能相当复杂

(set 'quotation 
 {She spoke in a low, eager voice, with a curious lisp in her utterance.})

(println (regex {(.*)(l.*)(l.*p)(.*)} quotation 0))
("She spoke in a low, eager voice, with a curious lisp in
her utterance." 0 70 "She spoke in a " 0 15 "low, eager
voice, with a curious " 15 33 "lisp" 48 4 " in her
utterance." 52 18)


此结果列表可以解释为“第一个匹配项从字符 0 开始,持续 70 个字符,第二个从字符 0 开始,持续 15 个字符,另一个从字符 15 开始,持续 33 个字符”,依此类推。

匹配项也存储在系统变量($0,$1,...)中,您可以使用简单的循环轻松检查这些变量

(for (x 1 4)
 (println {$} x ": " ($ x)))
$1: She spoke in a 
$2: low, eager voice, with a curious 
$3: lisp 
$4: in her utterance.

字符串到列表

[编辑 | 编辑源代码]

两个函数允许您将字符串转换为列表,以便使用 newLISP 广泛的列表处理功能进行操作。名为 **explode** 的函数打开一个字符串,并返回一个单字符列表

(set 't "a hypothetical one-dimensional subatomic particle")
(explode t)

:-> ("a" " " "h" "y" "p" "o" "t" "h" "e" "t" "i" "c" "a" "l"
" " "o" "n" "e" "-" "d" "i" "m" "e" "n" "s" "i" "o" "n" "a"
"l" " " "s" "u" "b" "a" "t" "o" "m" "i" "c" " " "p" "a" "r"
"t" "i" "c" "l" "e")


爆炸很容易用 **join** 反转。**explode** 也可以接受一个整数。这定义了片段的大小。例如,要将一个字符串分成密码学家风格的 5 个字母组,请删除空格并使用 **explode** 如下

(explode (replace " " t "") 5)
;-> ("ahypo" "theti" "calon" "e-dim" "ensio" "nalsu" "batom" "icpar" "ticle")

你可以对 **find-all** 做类似的技巧。不过注意结尾

(find-all ".{3}" t)                 ; this regex drops chars!
;-> ("a h" "ypo" "the" "tic" "al " "one" "-di" "men" 
; "sio" "nal" " su" "bat" "omi" "c p" "art" "icl")

解析字符串

[编辑 | 编辑源代码]

**parse** 是一种将字符串分解并返回各个部分的强大方法。单独使用时,它将字符串分解,通常在单词边界处,吃掉边界,并返回一个包含剩余部分的列表

(parse t)                               ; defaults to spaces...
;-> ("a" "hypothetical" "one-dimensional" "subatomic" "particle")

或者你可以提供一个分隔符,**parse** 会在遇到该字符时将字符串分解

(set 'pathname {/System/Library/Fonts/Courier.dfont})
(parse pathname {/})
;-> ("" "System" "Library" "Fonts" "Courier.dfont")

顺便说一下,我可以通过过滤掉第一个空字符串来消除列表中的第一个空字符串

(clean empty? (parse pathname {/}))
;-> ("System" "Library" "Fonts" "Courier.dfont")

你也可以指定分隔符字符串而不是分隔符字符

(set 't (dup "spam" 8))
;-> "spamspamspamspamspamspamspamspam"

(parse t {am})                          ; break on "am"
;-> ("sp" "sp" "sp" "sp" "sp" "sp" "sp" "sp" "")

最重要的是,你可以指定一个正则表达式分隔符。与 newLISP 中大多数正则表达式函数一样,确保提供选项标志(0 或其他)

(set 't {/System/Library/Fonts/Courier.dfont})
(parse t {[/aeiou]} 0)                  ; split at slashes and vowels
;-> ("" "Syst" "m" "L" "br" "ry" "F" "nts" "C" "" "r" "" "r.df" "nt")

这是众所周知的快速且不太可靠的 HTML 标签剥离器

(set 'html (read-file "/Users/Sites/index.html"))
(println (parse html {<.*?>} 4))        ; option 4: dot matches newline

为了解析 XML 字符串,newLISP 提供了 **xml-parse** 函数。见 使用 XML.

在对文本使用 **parse** 时要小心。除非你明确指定你要什么,否则它会认为你传递给它的是 newLISP 源代码。这可能会产生令人惊讶的结果

(set 't {Eats, shoots, and leaves ; a book by Lynn Truss})
(parse t)
;-> ("Eats" "," "shoots" "," "and" "leaves")    ; she's gone!

分号在 newLISP 中被认为是注释字符,因此 **parse** 忽略了它以及该行上的所有后续内容。使用分隔符或正则表达式告诉它你真正想要什么

(set 't {Eats, shoots, and leaves ; a book by Lynn Truss})
(parse t " ")
;-> ("Eats," "shoots," "and" "leaves" ";" "a" "book" "by" "Lynn" "Truss")

或者

(parse t "\\s" 0)                   ; white space
;-> ("Eats," "shoots," "and" "leaves" ";" "a" "book" "by" "Lynn" "Truss")

如果你想以其他方式分割字符串,请考虑使用 **find-all**,它返回一个包含匹配模式的字符串列表。如果你可以将分割操作指定为正则表达式,那么你很幸运。例如,如果你想将数字分成三组数字,请使用此技术

(set 'a "1212374192387562311")
(println (find-all {\d{3}|\d{2}$|\d$} a))
;-> ("121" "237" "419" "238" "756" "231" "1")

; alternatively
(explode a 3)
;-> ("121" "237" "419" "238" "756" "231" "1")

该模式必须考虑结尾处有 2 位或 1 位数字的情况。

**parse** 在分隔符完成工作后会吃掉它们 - **find-all** 会找到东西并返回它找到的东西。

(find-all {\w+} t )                     ; word characters
;-> ("Eats" "shoots" "and" "leaves" "a" "book" "by" "Lynn" "Truss")

(parse t {\w+} 0 )                      ; eats and leaves delimiters
;-> ("" ", " ", " " " "; " " " " " " " " " "")

其他字符串函数

[编辑 | 编辑源代码]

还有其他与字符串一起工作的函数。**search** 在磁盘上的文件中查找字符串

(set 'f (open {/private/var/log/system.log} {read}))
(search f {kernel})
(seek f (- (seek f) 64))                ; rewind file pointer
(dotimes (n 3)
 (println (read-line f)))
(close f)

此示例在 system.log 中查找字符串kernel. 如果找到,newLISP 将文件指针倒带 64 个字符,然后打印出三行,显示上下文中的行。

还有一些函数用于处理 base64 编码的文件以及加密字符串。

格式化字符串

[编辑 | 编辑源代码]

值得一提的是 **format** 函数,它允许你将 newLISP 表达式的值插入到预定义的模板字符串中。使用 %s 表示模板内字符串表达式的位置,使用其他 % 代码包含数字。例如,假设你想要显示一个文件列表,如下所示

folder: Library
 file:  mach

一个适合文件夹(目录)的模板如下所示

"folder: %s" ; or
"  file: %s"

为 **format** 函数提供一个模板字符串,后跟产生文件或文件夹名称的表达式 (f)

(format "folder: %s" f) ; or
(format "  file: %s" f)

当此表达式被求值时,f 的内容将被插入到 %s 所在的字符串中。使用 **directory** 函数以这种格式生成目录列表的代码如下所示

(dolist (f (directory)) 
 (if (directory? f)
  (println (format "folder: %s" f))
  (println (format "  file: %s" f))))

我正在使用 **directory?** 函数来选择正确的模板字符串。典型的列表如下所示

folder: .
folder: ..
  file: .DS_Store
  file: .hotfiles.btree
folder: .Spotlight-V100
folder: .Trashes
folder: .vol
  file: .VolumeIcon.icns
folder: Applications
folder: Applications (Mac OS 9)
folder: automount
folder: bin
folder: Cleanup At Startup
folder: cores
...

有很多格式代码可用于生成你想要的输出。使用数字来控制字符串和数字的对齐方式和精度。只需确保格式字符串中的 % 结构与之后出现的表达式或符号匹配,并且两者数量相同。

以下是一个其他示例。我们将以十进制、十六进制和二进制形式显示前 400 个左右的 Unicode 字符。我们将使用 **bits** 函数来生成二进制字符串。我们在格式字符串之后将一个包含三个值的列表提供给 **format**,该格式字符串包含三个条目

(for (x 32 0x01a0)
 (println (char x)                 ; the character, then
   (format "%4d\t%4x\t%10s"        ; decimal \t hex \t binary-string
    (list x x (bits x)))))
   32       20     100000
!  33       21     100001
"  34       22     100010
#  35       23     100011
$  36       24     100100
%  37       25     100101
&  38       26     100110
'  39       27     100111
(  40       28     101000
)  41       29     101001
...

使 newLISP 思考的字符串

[编辑 | 编辑源代码]

最后,我必须提到 **eval** 和 **eval-string**。这两者都允许你将 newLISP 代码提供给 newLISP 进行求值。如果它是有效的 newLISP,你将看到求值的结果。**eval** 需要一个表达式

(set 'expr '(+ 1 2))
(eval expr)
;-> 3

**eval-string** 需要一个字符串

(set 'expr "(+ 1 2)")
(eval-string expr)
;-> 3

这意味着你可以使用我们遇到的任何函数来构建 newLISP 代码,然后让 newLISP 对其进行求值。**eval** 在定义宏(延迟求值直到你选择执行的函数)时特别有用。见 .

你可以使用 **eval** 和 **eval-string** 来编写编写程序的程序。

以下有趣的新 LISP 代码片段不断地无意识地重新排列几个字符串并尝试对结果进行求值。不成功的尝试会被安全地捕获。当它最终变成有效的 newLISP 时,它将被成功求值,结果将满足结束条件并结束循环。

(set 'code '(")" "set" "'valid" "true" "("))
(set 'valid nil)
(until valid
 (set 'code (randomize code))
 (println (join code " "))
 (catch (eval-string (join code " ")) 'result))
true 'valid set ) (
) ( set true 'valid
'valid ( set true )
set 'valid true ( )
'valid ) ( true set
set true ) ( 'valid
true ) ( set 'valid
'valid ( true ) set
true 'valid ( ) set
'valid ) ( true set
true ( 'valid ) set
set ( 'valid ) true
set true 'valid ( )
( set 'valid true )

我使用过显然使用这种编程技术编写的程序...

华夏公益教科书