轨道
/
R
R
/
练习
/
日志级别
日志级别

日志级别

学习练习

简介

背景

字符串是引号之间的一段文本。 目前所有仍受支持的 R 版本都用 UTF-8 编码字符串,因此世界上大多数语言都能得到支持。

mystring <- "some text"
mystring
#> [1] "some text"
typeof(mystring)
#> [1] "character"

遗憾的是,这套术语可能会让熟悉其他语言的程序员感到困惑。

尽管它的type是character,但 R 并不区分单个字母(在不少其他语言里叫char,但在 R 中不是)和一长串文本。 两者都是一个单独的元素,用单引号' '或双引号" "括起来都可以。

风格指南建议使用双引号,除非你想在字符串内部使用双引号而不进行转义。

str2 <- 'String with "quotes"'  # convenience format
str2
#> [1] "String with \"quotes\""    # standard format

因此,character vector就是由字符串组成的向量,其中每个字符串都是一个单独的元素。

s <- c("some", "strings")
s
#> [1] "some"    "strings"
length(s)
#> [1] 2
length(s[1])  # the number of vector elements, not the string length
#> [1] 1

nchar(s[1])  # the number of characters in the string "some"
#> [1] 4

在上面的例子中,length(s[1])等价于length("some"):它给出的是向量元素的个数,而不是字符串长度。

操作单个字符串当然也可以做到,比如上面例子中的nchar(),但这需要一些特定的函数,我们会在下面讨论。

字符串函数

在 base R 中,大多数事情都能做到,但很多程序员觉得它对字符串操作的支持相当有限,而且让人困惑。

幸运的是,tidyverse的包要直观得多,也更灵活、更自洽。

Base R

仍有大量遗留代码在使用旧式字符串函数。 Exercism 上大多数社区解答也是如此。

此外,对于小任务来说,使用内置函数比导入一个庞大的库更实用。 R 赛道的维护者在编写用于验证练习的范例解答时,往往也会采用这种做法。

因此,了解一些旧方法的用法仍然是有价值的。

nchar

返回字符串中字符的个数。

nchar("string")
#> [1] 6

nchar("Hrōðgār") # a person in Beowulf, written in Old English
#> [1] 7

“字符”究竟指什么,这个话题(很复杂!)超出了本文的范围。 只需留意上面例子中的几点:

  • 尽管 R 的字符串在内部是用 C 实现的,但它们并不以\0结尾(否则长度会多 1)。
  • 多字节的 Unicode 字符会按照你可能预期(或期望)的方式处理。“Hrōðgār”有 7 个字符(也叫“rune”或“码点”),但存储时需要 10 个字节。
paste 和 paste0

把任意数量的字符串合并成一个字符串。

paste可以选择性地指定分隔符:默认是一个空格。

paste0(v)等价于paste(v, sep = "")。

paste("bits", "of", "a", "string")
#> [1] "bits of a string"
paste0("bits", "of", "a", "string")
#> [1] "bitsofastring"
sprintf

为了更精细地控制字符串的组装,R 从 C 中照搬了sprintf函数(以及若干更晚出现的语言:Wikipedia 列出了大约 30 种)。

r <- 5.3
pi <- 3.14159
sprintf("A circle of radius %.1f has area %.2f", r, pi * r^2)
#> [1] "A circle of radius 5.3 has area 88.25"
trimws

可以用trimws()去掉开头和/或结尾的空白字符:

s <- "    messy string   "
trimws(s)
#> [1] "messy string"

还有一个which = "left"(或 "right")形参,用来避免同时修剪两端。

substr

作用于单个字符串,返回两个边界(含边界)之间的子串。

substr("Exercism", 5, 7)
#> [1] "cis"
strsplit

根据分隔字符串,把一个字符串拆分成 R 的列表。 分隔符是必需的(没有默认值),但可以是空字符串。

strsplit("R, Python, Julia", ", ")
[[1]]
#> [1] "R"      "Python" "Julia" 

strsplit("Exercism", "")
[[1]]
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"

列表会在单独的概念中介绍。 如果你还没学到教学大纲的那一部分,现在只需知道unlist()函数(在这个例子里)会把列表转换成向量。

unlist(strsplit("Exercism", ""))
#> [1] "E" "x" "e" "r" "c" "i" "s" "m"

Tidyverse

客气地说,旧式字符串函数并不是 R 能提供的最好的东西(也有不那么客气的看法)。

为了改善这种情况,stringr库从 2009 年起就在持续开发,如今已是 Tidyverse 集合中的重要组成部分。

用library(stringr)或library(tidyverse)把它导入你的命名空间。

一些关键功能包括:

  • 所有函数名都带str_前缀,并力求让用途一目了然。
  • 实参的顺序力求自洽,把输入数据作为第一个实参。
  • 输入可以简单到一个字符串,也可以复杂到复杂的数据结构。函数会尽量适应,并做出最合理的处理。

和所有 Tidyverse 包一样,stringr有完善的文档,通常由包的作者亲自撰写。

插曲:管道快速入门

用管道|>连接函数的用法,会在教学大纲的其他部分详细讨论。

现在,知道以下几点会很有帮助:

  • 管道左侧的结果会成为右侧函数的第一个实参(这里的“右侧”会忽略可选的换行)。
  • 其余实参可以一并写在函数调用中。
  • 在 R 中,我们必须把管道接到函数的调用上,而不仅仅是名字上,所以括号总是必需的(这一点和其他一些语言不同)。

下面的例子演示了这种语法。 各个函数会在后面讨论。

library(stringr)

"Monday-25-February" |> 
  str_split("-") |>     
  unlist() |>               # [1] "Monday"   "25"       "February"
  str_sub(1, 3) |>          # [1] "Mon" "25"  "Feb"
  str_to_upper() |>         # [1] "MON" "25"  "FEB"
  str_flatten(collapse = ", ")
#> [1] "MON, 25, FEB"
管理长度

str_length统计码点个数(类似于nchars)。

还有其他各种处理空白字符的函数,包括str_trim()和str_squish()。 在任何一个数据科学项目的开头,这往往都是一项至关重要的清理工作。

# The first 3 kernels in JuPyteR notebooks:
str_length(c("Julia", "Python", "R"))           #> [1] 1 6 5
#> [1] 5 6 1
str_pad(c("Julia", "Python", "R"), 8, "right")  # add spaces if necessary
#> [1] "Julia   " "Python  " "R       "

str_trim("Julia    Python    R    ")   # remove leading & trailing whitespace
#> [1] "Julia    Python    R"
str_squish("Julia   Python  R    ")  # trim, collapse multiple spaces 
#> [1] "Julia Python R"
截取子串

函数str_sub(string, start, end)会返回从start到end(含两端)的子串,而str_sub_all()可以作用于多个字符串。

这里非常灵活。 start和end默认分别是第一个和最后一个字符。 它们可以是下标的向量。 和向量索引相比,这里有一点很讨喜:负值会像 Python 那样从字符串末尾往回数。

s <- "abcdefgh"
str_sub(s, 2, 4)
#> [1] "bcd"
str_sub(s, 5)
#> [1] "efgh"
str_sub(s, -3, -2)
#> [1] "fg"
str_sub(s, c(1, 4), c(2, 5))
#> [1] "ab" "de"
str_sub(s, c(1, 4, 6), c(2, 5, -1))
#> [1] "ab"  "de"  "fgh"

s |> str_sub(2, 4) # these functions are designed for pipes
#> [1] "bcd"
合并字符串

str_flatten把一个字符向量转换成一个字符串。 collapse实参会被插入到每个元素之间,默认是""。

c("R", "Julia", "Python") |> str_flatten()
#> [1] "RJuliaPython"
c("R", "Julia", "Python") |> str_flatten(collapse = " - ")
#> [1] "R - Julia - Python"

str_c把多个字符向量转换成一个字符向量,必要时会进行循环补齐。

str_c(LETTERS[1:8], 1:8, sep = ":")
#> [1] "A:1" "B:2" "C:3" "D:4" "E:5" "F:6" "G:7" "H:8"

str_glue为 R 带来了简单的字符串插值。 大括号{ }里的表达式会被求值,并替换为其文本表示。

r <- 5.3
pi <- 3.14159
str_glue("A circle of radius {r} has area {pi * r^2}")
A circle of radius 5.3 has area 88.2472631
拆分字符串

R 的一个非常常见的用途,是把杂乱的数据整理成一致的格式以便分析。 为了帮助做到这一点,有多种把文本拆分成子串的方法。 大多数都超出了当前的范围。

str_split_1是最简单的,它按照指定的模式(没有默认模式)把单个字符串拆分成由若干片段组成的向量。

s <- "R Julia Python"
str_split_1(s, " ")
#> [1] "R"      "Julia"  "Python"
修改字符串

有些语言(主要是欧洲语言)区分大写和小写字母。 有一组stringr函数可以按符合区域设置的方式在两者之间转换:

  • str_to_lower()
  • str_to_upper()
  • str_to_title():把每个单词的首字母变成大写
  • str_to_sentence():把句子的首字母变成大写
str_to_upper("elixir")    # defaults to locale = "en"
#> [1] "ELIXIR"
str_to_upper("ελληνικά")  # infers Greek
#> [1] "ΕΛΛΗΝΙΚΆ"

str_to_title("the cat sat on the mat")
#> [1] "The Cat Sat On The Mat"
str_to_sentence("the cat sat on the mat")
#> [1] "The cat sat on the mat"

基于模式匹配进行子串替换时,str_replace()会替换模式的第一次出现,str_replace_all()会替换所有出现。

基于字符下标进行子串替换时,有一种 str_sub() 的赋值形式。

 s <- "abcde"
str_sub(s, 3, 4) <- "yz"
s
#> [1] "abyze"

说明

在这个练习中,你将处理日志行。

每一行日志都是一个字符串,格式如下:"[<LEVEL>]: <MESSAGE>"。

日志级别一共有三种:

  • INFO
  • WARNING
  • ERROR

你有三个任务,每个任务都会给出一行日志,要求你对它做点什么。

你可以自由选用 base R 中的旧式函数,也可以使用stringr库,两者都能给出简短可行的解法。

1. 从日志行中提取消息

实现message函数,返回日志行中的消息:

message("[ERROR]: Invalid operation")
#> "Invalid operation"

开头和结尾的空白字符都应去掉:

message("[WARNING]:  Disk almost full\r\n")
#> "Disk almost full"

2. 从日志行中获取日志级别

实现log_level函数,返回日志行的日志级别,并且要用小写形式返回:

log_level("[ERROR]: Invalid operation")
#> "error"

3. 重新格式化日志行

实现reformat函数,重新格式化日志行:把消息放在前面,日志级别放在后面并加上括号:

reformat("[INFO]: Operation completed")
#> "Operation completed (info)"
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
R Exercism

准备好开始 日志级别 了吗?

注册 Exercism,借助 21 个概念111 个练习 和真人导师指导,学习并掌握 R,全部免费。