R语言字符串及条件和循环

上一篇笔记记录了三个专题:数据框排序,表达矩阵画箱线图,数据框各种连接方式的内容与示例。

以下记录几个专题的内容与示例:

  • 玩转字符串

  • 条件和循环

  • 隐式循环(apply与lapply)

专题1:玩转字符串

stringr包里专用于处理字符串向量的函数,这些函数有一个共同特点,str开头。

1.1 检测字符串长度:str_length(x)

1.2 字符串拆分:str_split()

1.3 按位置提取字符串:str_sub()

1.4 字符检测:str_detect(x,"h"):重点掌握用法

1.5 字符串替换:str_replace()/str_replace_all()

1.6 字符删除:str_remove()/str_remove_all()

基础包里也有处理字符串函数,但是比较零散。

rm(list = ls())
if(!require(stringr))install.packages('stringr')
library(stringr)

1.1 检测字符串长度

字符串:一个引号里面所有的东西,不管有多少个字母,包括符号,空格。

字符:一个字母就是一个字符,空格也是一个字符。

x <- "The birch canoe slid on the smooth planks."
x
str_length(x)
##统计向量里的字符个数,数一下字符串里面有多少个字符。
length(x)
##统计向量里的元素个数,运行完上面的代码,返回是1个元素,1代表一个”引号“括起来的字符串,不管引号里边有多少字母与空格之类的,有多少个引号就有多少个字符串,

1.2 字符串拆分

按照某个符号(字符)进行分割。拆分向量字符串后成列表,加参数simplify = T(这个参数以后用得比较多)成为矩阵,矩阵转换数据框,字符型转换为数值型。

如果是一个长字符串,就取[[1]],如果是多个元素组成的向量,就加一个参数simplify = T

x <- "The birch canoe slid on the smooth planks."
str_split(x," ")
##按照空格去分割,按照空格去把x的字符串拆分成若干个字符串,有多少个空格就拆多少个,有7个空格,拆成8个。
# [[1]]
# [1] "The"     "birch"   "canoe"   "slid"    "on"      "the"    
# [7] "smooth"  "planks."
### [[1]]列表才有的,用class()函数查看是不是列表
class(str_split(x," "))
#[1] "list"

##就一个向量没必要保持列表的属性。
x2 = str_split(x," ")[[1]];x2  
##[[1]]提取列表元素里唯一一个元素。
class(x2)
#[1] "character"

####为什么str_split拆分向量会成列表,因为不是只拆一个字符串的,可以拆一个字符串向量。
y = c("jimmy 150","nicker 140","tony 152")
str_split(y," ")
##" ",以空格为单位拆分,都会生成列表,考虑到维度

##每次拆为一个列表,需要简化,不能简化为数据框,只能简化为矩阵,可以用as.data.frame转化为数据框
str_split(y," ",simplify = T)
##simplify = T为简化为字符型的矩阵,转化为矩阵后,用$符号或是[]对数据框的某一列操作,用as.numeric把字符串转换为数值型。

str_c(x2,collapse = " ")
##与paste0有点类似。" "为分割符把向量连接,内部的连接
str_c(x2,1234,sep = "+")
##外部的连接,给每个元素加1234

1.3 按位置提取字符串

x <- "The birch canoe slid on the smooth planks."
str_sub(x,5,9)
#[1] "birch"
#把字符串的第5到第9位抠出来,单独组成一个字符串

##自己思考:如果一个向量里有很多个字符串怎么提取,除了下标,还有其它方法

1.4 字符检测(重点)

检测字符串或是字符串向量里是否含有某个关键词(一个字母或是单词都可以),返回一个与向量等长的逻辑值(TRUE或FALSE)。

x <- "The birch canoe slid on the smooth planks."
x2 = str_split(x," ")[[1]];x2
str_detect(x2,"h")
#[1]  TRUE  TRUE FALSE FALSE FALSE  TRUE  TRUE FALSE
#检测x2里每个元素(向量)里是否含有h这个字母,含有就返回TRUE,不含就返回FALSE。

##比较细化的两个函数
str_starts(x2,"T")
#[1]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##是否以T开头,返回逻辑值,是就返TRUE,否就返FALSE

str_ends(x2,"e")
#[1]  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE
##是否以e结尾,返回逻辑值,是就返TRUE,否就返FALSE

1.5 字符串替换

替换单个和替换全部

x <- "The birch canoe slid on the smooth planks."
x2 = str_split(x," ")[[1]];x2

##替换一个字符。
str_replace(x2,"o","A")
#[1] "The"     "birch"   "canAe"   "slid"    "An"      "the"    
#[7] "smAoth"  "planks."
##把字母A替换字母o,但是不全替换,比如两个oo字母连在一起,只替换第一个匹配字符,如smAoth还有第二个o
##如果连在一起的两个字母,只能替换第一个。

##替换所有包含的字符。
str_replace_all(x2,"o","A")
#[1] "The"     "birch"   "canAe"   "slid"    "An"      "the"    
#[7] "smAAth"  "planks."
#匹配上所有都要替换掉

##替换单个还是全部和应用场景有关。

1.6 字符删除

删除单个字符或是删除多个字符

x <- "The birch canoe slid on the smooth planks."

str_remove(x," ")
#[1] "Thebirch canoe slid on the smooth planks."
##去掉空格,只删除第一个空格

str_remove_all(x," ")
#[1] "Thebirchcanoeslidonthesmoothplanks."
##删除所有的空格

1.7 字符定位

x <- "The birch canoe slid on the smooth planks."
x2 = str_split(x," ")[[1]];x2

str_locate(x2,"th")

str_locate(x2,"h")

与sum和mean连用,可以统计匹配的个数和比例

x <- "The birch canoe slid on the smooth planks."
x2 = str_split(x," ")[[1]];x2

sum(str_detect(x2,"h"))
#[1] 4

mean(str_detect(x2,"h"))
#[1] 0.5

1.8 提取匹配到的字符

提取单个或是提取多个匹配到的字符串,使用str_extract_all()时,使用参数simplify = T

x <- "The birch canoe slid on the smooth planks."
x2 = str_split(x," ")[[1]];x2

str_extract(x2,"o|e")
#[1] "e" NA  "o" NA  "o" "e" "o" NA 
#提取o或e,默认只提取第一个,不含有就返回NA

str_extract_all(x2,"o|e")
##返回是一个列表

str_extract_all(x2,"o|e",simplify = T)
#simplify = T,使其返回不是列表,而是简化的矩阵

注意:在对字符串函数操作时,记得检查

专题2:条件语句和循环语句

条件语句常用到,循环语句不常用,后面也可多练习。

2.1条件语句

if条件语句:如果...就...

2.1.1 if(){ }
if(){
CODE1
}
##if()括号里面只能是一个逻辑值,不能是一串逻辑值,因为不支持循环。
##如果符合,逻辑值为TRUE,就执行代码;如果不符合,逻辑值为FALSE,就不执行代码,什么都不做。
  • (1)只有if,没有else,那么条件是FALSE时就什么都不做
i = -1
if (i<0) print('up')
#[1] "up"
if (i>0) print('up')
##什么都不返回,代码:print('up')
##只有一句代码,可以把{}省略掉

理解下面代码

if(!require(tidyr)) install.packages('tidyr')
##如果这个tidyr包成功不加载,返回TRUE,就安装tidyr,如果加载成功,返回FALSE就不要安装。注意有!,反着来。
  • (2) if加上else

if条件语句:如果...就...,否则...

if(){
CODE1
}else{
CODE2
}
##if()括号里面只能是一个逻辑值,如果是TRUE就是执行CODE1,如果是FALSE就是执行else后CODE2

结合else

i =1
if (i>0){
  print('+')
} else {
  print("-")
}
#[1] "+"
来自生信技能树小洁老师的课件图

if和if..else函数用的不多。ifelse函数用的比较多

2.1.2 ifelse函数(重点掌握,常用函数)

这个函数是支持向量的,if()的括号里只能是一个逻辑值,但是ifelse()的括号里的逻辑值数量没有限制。

ifelse函数永远只有3个参数:ifelse(x, yes, no),无论怎么嵌套永远只有3个参数,除非作者修改。

x:逻辑值/逻辑值向量的代码

yes:逻辑值为TRUE时的返回值

no:逻辑值为FALSE时的返回值

i=1
ifelse(i>0,"+","-")
##i>0为第一个参数x,一定是逻辑值,一个逻辑值或是逻辑值向量
##"+"为第二个参数yes,x对应的逻辑值为TRUE,这句代码返回输入值是什么。当x对应位置的逻辑值是TRUE的时候,ifelse整句代码输出结果是"+"
##"-"为第三个参数no,当i>0为FALSE时,返回值是"-"

x=rnorm(3)
#[1]  0.9020748  1.4268635 -0.1594190
ifelse(x>0,"+","-")
#[1] "+" "+" "-"
##ifelse()相当于完成一个替换,把一个逻辑值向量里的TRUE换成一个"+",把一个逻辑值向量里的FALSE换成一个"-",以此来输出一个新的向量。

这里不是只x>0,逻辑值或是逻辑值向量,也可以是用代码生成的逻辑值向量,ifelse()只管替换。

生成逻辑值向量的函数:str_detect()

x <- c("The", "birch", "canoe", "slid","on","the", "smooth planks.")
str_detect(x,"h")
#[1]  TRUE  TRUE FALSE FALSE FALSE  TRUE  TRUE
#返回逻辑值向量

ifelse(str_detect(x,"h"),"+","-")
##[1] "+" "+" "-" "-" "-" "+" "+"
##如果x每个元素里含有关键词h,输出为+,如果不含h,输出为-

ifelse(str_detect(x,"h"),"control","treat")
#[1] "control" "control" "treat"   "treat"   "treat"   "control"
#[7] "control"
##ifelse(str_detect(x,"h"),"+","-")里的"h"位置除了h可写单词或者是几个字母;"+","-"的位置可以写任何关键词。

ifelse与str_detect两个函数连用实现一个强大的功能:

根据x这个向量是或否含有某个关键词来把它分成两类,一类是含有关键词(TRUE)的叫A,一类是不含关键词(FALSE)的叫B,A和B的名称可以随便替换。

多个条件

  • if后面可以跟多几个else if。if()的括号里只能有一个逻辑值,不能是向量和前面一样。
i = 0
if (i>0){
  print('+')
} else if (i==0) {
  print('0')
} else if (i< 0){
  print('-')
}
#[1] "0"
ifelse(i>0,"+",ifelse(i<0,"-","0"))
#[1] "0"
##分三种情况,i>0,i==0,i< 0分别输出+,0,-。
  • ifelse()函数也支持多个条件

如果需要向量化的类似操作,里面有3个或是多个数字,可以用ifelse函数,它能支持多个条件。

i=(-1,0,2)
ifelse(i>0,"+",ifelse((i<0),"-","0"))

##如果i>o,输入+,否则..再进行一轮循环比较,如果i< 0输出-,否则输出

这个方法以后在实战中非常常用,结合练习题代码巩固

2.2循环语句

2.2.1 for循环

重复运算,对x里的每个元素i进行同一操作。#x通常是向量和列表,数据框有更好的循环

for(i in x){
CODE
}
##x是一个向量,i是一个抽象的东西,指代x里面的一个元素,第一轮循环就会带入x的第一个元素,第二轮循环时就会带入x的第二个元素。CODE代码是以i为中心去写的。for循环终究有终点,因为向量都是有长度的,运行x的最后一个元素会终止。

两种循环方式:

##第一种循环方式:元素循环
x <- c(5,6,0,3)
s=0
for (i in x){
  s=s+i
  print(c(i,s))
}
# [1] 5 5
# [1]  6 11
# [1]  0 11
# [1]  3 14
##每轮循环进行累加:没运行前,s=0,第一个元素5运行完后,s=5,参与下一个元素的运行以此类推,上一个元素得到的新s结果会参与到下一个元素里。
##for (i in x):直接拿x里的四个元素进行循环,即第一轮循环i=5..第四轮循环i=3

##第二种循环方式:下标循环
x <- c(5,6,0,3)
s = 0
for (i in 1:length(x)){
  s=s+x[[i]]
  print(c(x[[i]],s))
}
# [1] 5 5
# [1]  6 11
# [1]  0 11
# [1]  3 14
##s=s+x[[i]]:使用[[]],大神给的建议是,当你需要把向量取子集的代码写在{}里面,有时候用一个[]取子集,结果比较诡异,用[[]]取子集,一般没什么问题。用[[]]相当于加上一道保险杠,写在for保险操作,仅在循环这样操作,下标循环比较灵活。
##下标循环,常用,不限于向量或是列表,只要能取子集的数据结构
##for (i in 1:length(x)),拿向量的下标进行循环,第一轮循环=1..第四轮循环i=4

总结:两种循环都有应用,元素循环比较简单,但应用更广的是下标循环,因为它比较灵活,下标可以替换成数据框的列,也可以替换成列表里的指定元素。这样就可以实现对数据框,列表比较便的替换。下标循环还有一个不可替代的好处,就是能把结果保存下来。

如何将结果存下来?

#补充知识:列表的第二种写法(生成方式):
a = list()
a[[1]]=1:10
a[[2]]=iris
...##可以一直往后添加列表的元素。

x <- c(5,6,0,3)
s = 0
result = list()
for(i in 1:length(x)){
  s=s+x[[i]]
  result[[i]] = c(x[[i]],s)
}
result
##每过一轮循环,列表就会多一个元素

#补充2:cbind()是按列拼接
cbind(result[[1]],
      result[[2]],   
      result[[3]],
      result[[4]])

do.call(cbind,result)
#      [,1] [,2] [,3] [,4]
# [1,]    5    6    0    3
# [2,]    5   11   11   14
##得到的列表结果看起来比较麻烦,简化
##do.call()是批量对列表进行操作,把列表里的每个元素做为生成出矩阵的每一列

2.3插播:长脚本管理方式

假如有500行代码,不做拆分也不做任何操作,会变得非常臃肿,得运行完才出结果知道是对还是错,没有办法一段段去调试。有两种方法:

2.3.1 代码分成多个脚本:保存为Rdata

每个脚本最后保存Rdata,下一个脚本开头清空再加载

代码是用来处理数据,画图,保存数据或是读取之类的。总是围绕图片和数据,图片可以保存,数据可以用Rdata保存

#数据下载
rm(list = ls())
options(stringsAsFactors = F)
library(GEOquery)
gse_number = "GSE56649"
eSet <- getGEO(gse_number, 
               destdir = '.', 
               getGPL = F)
class(eSet)
length(eSet)
eSet = eSet[[1]]
#(1)提取表达矩阵exp
exp <- exprs(eSet)
exp[1:4,1:4]
exp = log2(exp+1)
boxplot(exp)
#(2)提取临床信息
pd <- pData(eSet)
#(3)调整pd的行名顺序与exp列名完全一致
p = identical(rownames(pd),colnames(exp));p
if(!p) exp = exp[,match(rownames(pd),colnames(exp))]
#(4)提取芯片平台编号
gpl_number <- eSet@annotation
save(gse_number,pd,exp,gpl_number,file = "step1output.Rdata")
##把gse_number,pd,exp,gpl_number这是三个数据打包保存到step1output.Rdata里,以后直接可以使用保存的数据,不需要运行上面的代码。

##使用的时候直接load保存的step1output.Rdata。
rm(list = ls())
load(file="step1output.Rdata")
##.Rdata"放在当前工作目录下

不断清空再调试,清空是不要让上一步残留的结果影响调试

2.3.2 if结合逻辑值

if(F){…},则{}里的脚本被跳过(不被执行),

if(T){…},则{}里的脚本被运行,

凡是带有{}的代码,均可以被折叠。

#第一段代码
if(F){
    a=1
    b=a^2
    d=a+b+a^2
}

#第二段代码
if(T){
    a=1
    b=a^2
    d=a+b+a^2
}

有段代码不行运行也不想删掉,有两个选择,一是在每行代码前加#号注释,二是把不想运行的代码放在if(F){}的大括号里,想运行时把F改为T,用if的一个好地方,凡是有{}地方都可以折叠。在R语言控制台的边框数字编号处点击三角箭头,可实现长串代码折叠。

#months<-c(1,3,5,9,12)
#scores <- c(5,6,5,6,7)
#speaking <- c("good","fine","good","fine","excellent")
#resultdata <- data.frame(months,scores,speaking)
##在每行代码前面加#号,表示注释不运行。

if(F){rownames(resultdata) <- c("once","twice","third","fourth","fifth")
     colnames(resultdata) <- c("times","pionts","oraltest")
}
##if(F)不运行{}里面的代码,注意{}里面的完整的两句代码不需要逗号

专题3:隐式循环

不是用for语句而是用函数来实现循环,比for循环应该广一些。

apply:处理数据框和矩阵;

lapply:处理列表和向量。

3.1 矩阵/数据框的隐式循环---apply

apply 处理矩阵或数据框

apply(X, MARGIN, FUN, …) ,apply()函数有三个参数

X是数据框/矩阵名:对数据框或是矩阵进行循环运算的一个函数);

MARGIN的位置为1表示行,为2表示列:只能单独对所有对行或是对所有列运算,不能同时对行和列一起运算);

FUN是函数:函数一般带括号,但是FUN作为一个函数里的参数时,不需要带括号,在do.call(cbind,result)里,cbind函数作为do.call函数里的参数,也不带括号。

对X的每一行/列进行FUN这个函数

test<- iris[1:6,1:4]
class(test)
#[1] "data.frame"

apply(test, 2, mean)
# Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
# 4.9500000    3.3833333    1.4500000    0.2333333 
##第一行向量的名字,用数据结构把两种信息组合起来。
##test为数据框名,2表示每一列,FUN为mean函数,整句代码的意思是对数据框test的每一列求平均值,

apply(test, 1, sum)
#   1    2    3    4    5    6 
# 10.2  9.5  9.4  9.4 10.2 11.4 
#对数据框test每一行求和

mean和sum是现成的函数,比较简单的例子,apply函数真正强大之处是结合自定义函数

如何挑出一个表达矩阵里方差最大的1000个基因

方法:

1.计算每个基因的方差(每个基因是每一行,方差var)

2.每个基因方差排列

3.最后1000个数字所对应的基因

具体操作代码:

load("test2.Rdata")
##里面保存的数据框名字是test
dim(test)
apply(test, 1, var)
##计算数据框test方差
sort(apply(test, 1, var))
##从小到大排序,最大的1000应在最后面
tail(sort(apply(test, 1, var)),1000)
##用tail函数取最后1000个方差最大的基因,默认参数是6不写,这里是1000,写上
names(tail(sort(apply(test, 1, var)),1000))
#提取基因名字,挑出1000个基因名字

3.2 lapply(list, FUN, …)

3.2.1列表的隐式循环—lapply

对列表/向量中的每个元素(向量)实施相同的操作(列表没有行列,对列表每个元素操作),所以lapply比apply简单。

lapply函数返回结果是一个列表

test <- list(x = 36:33,y = 32:35,z = 30:27);test

返回值是列表,对列表中的每个元素(向量)求均值(试试方差var,分位数quantile)

lapply(test,mean)
##列表没有行和列,对列表里每个元素批量操作。
##对列表里每个元素进行求平均值,结果以列表的形式展示
##返回是有多个向量组成的列表

lapply(test,fivenum)
#返回是多个数字组成的向量
#fivenum 五个数值:最小值、下四分位数数、中位数、上四分位数、最大值
3.2.2列表的隐式循环—lapply

lapply函数返回结果是一个列表,需要简化。sapply 简化结果,直接返回矩阵或向量

test <- list(x = 36:33,y = 32:35,z = 30:27);test
sapply(test,mean)
##输出结果为一个有名字的向量,合理简化
sapply(test,fivenum)
##对于复杂的数据结构,简化成矩阵

class(sapply(test,fivenum))

4.补充知识点

4.1 花样缺失值

处理NA

library(tidyr)
### 原始数据
X<-data.frame(X1 = LETTERS[1:5],X2 = 1:5)
X[2,2] <- NA
X[4,1] <- NA
X
### 1.去掉含有NA的行,可以选择只根据某一列来去除
drop_na(X)##去掉含有NA的函数
drop_na(X,X1)##只看X1这一行,有缺失值就去掉,没有就留下
drop_na(X,X2)##只看X2这一行,有缺失值就去掉,没有就留下

### 2.替换NA
replace_na(X,list(X2=0))

### 3.用上一行的值填充NA
X
fill(X,X2)##把X上的X2行的NA值填充上一行是几,就替换成几。临床医生比较受用,比如三位病人占到一行

在实战中画图时特别是画火山图,pca图时,有NA值的存在,没法画图。用基础包的函数,可以判断向量、矩阵、数据框等数据是否有缺失值,有的话,再用函数去除。

a<-data.frame(X1 = LETTERS[1:5],X2 = 1:5)
a[2,2] <- NA
a[4,1] <- NA
a
#is.na(a)##如果表达矩阵或是数据框的函数有几万行,这个函数不能单用
table(is.na(a))
#FALSE  TRUE 
#  8     2 
b=na.omit(a)
##na.omit()是去除NA值的函数
table(is.na(b))
#FALSE 
#    6 

4.2 R语言遍历、创建、删除文件

写代码代替点鼠标

dir()
file.create()
file.exists()
file.remove()
file.rename(from, to)
file.append(file1,file2)
file.copy(from,to,overwrite=recursive,recursive=FALSE,
          copy.mode = TRUE, copy.date = FALSE)
file.symlink(from, to)
file.link(from, to)

dir.create("ashu")
unlink("ashu",recursive = T)

摘抄小洁老师课件的内容,以后有时间再慢慢探索应用。

说明

以上内容是听生信技能树小洁老师的R语言线上课,根据自己的理解记录下来,小洁老师授课非常细心,对不同水平的同学都照顾到,并且补充很多技巧以及注意事项。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容