第4章  排序:智能收件箱

有监督学习与无监督学习:有监督学习已有明确的输出实例;无监督学习在开始处理数据时预先并没有已知的输出实例。

理论上邮件的优先级特征:

  • 社交特征:收件人与发件人之间的交互程度
  • 内容特征:收件人对邮件采取行为(回复、标记等)与某些特征词之间相关
  • 线程特征:记录用户在当前线程下的交互行为
  • 标签特征:检查用户通过过滤器给邮件赋予的标签(标记)

由于数据量不足,本文用于替代的优先级特征:(需要抽取的元素)

  • 社交特征:来自某一发件人的邮件量(发件人地址)
  • 时间量度:(接受时间日期)
  • 线程特征:是否活跃(主题)
  • 内容特征:高频词分析(邮件正文)

#machine learing for heckers
#chapter 4

library(tm)
library(ggplot2)library(plyr)easyham.path <- "ML_for_Hackers/03-Classification/data/easy_ham/"

  

###############################################

#抽取特征集合
###############################################
#输入邮件路径,返回特征信息
#把路径作为数据的最后一列保存,可以使测试阶段的排序更容易一些(书上这么写的,没看懂为什么)

parse.email <- function(path){full.msg <- msg.full(path)date <- get.date(full.msg)from <- get.from(full.msg)subj <- get.subject(full.msg)msg <- get.msg(full.msg)return(c(date, from, subj, msg, path))
}

  

#辅助函数的编写
#读取内容
#按行读取,每一行的内容对应向量中的一个元素

msg.full <- function(path){con <- file(path, open = "rt", encoding = "latin1")msg <- readLines(con)close(con)return(msg)
}

  

#正则表达式抽取信息
#抽取发件地址:
#两种格式:From: JM<j@xx.com>; From: j@xx.com(JM)
#grepl()以"From: "作为匹配条件,返回是(1)否(0)匹配;匹配的一行存入from中
#方括号创建字符集:冒号、尖括号、空格,作为拆分文本的标志,存入列表中的第一个元素
#将空元素过滤掉
#查找包含"@"字符的元素并返回

get.from <- function(msg.vec){from <- msg.vec[grepl("From: ", msg.vec)]from <- strsplit(from, '[":<> ]')[[1]]from <- from[which(from != "" & from != " ")]return(from[grepl("@", from)][1])
}

  

#抽取正文

get.msg <- function(msg.vec){msg <- msg.vec[seq(which(msg.vec == "")[1]+1, length(msg.vec), 1)]return(paste(msg, collapse = "\n"))
}

  

#抽取主题
#正则匹配主题特征(有的邮件没有主题)
#如果长度大于0,返回第2个元素(第1个元素是"Subject")否则返回空字符
#如果不设条件,在grepl()一类函数得不到匹配时,会返回一个指定的值,
#如integer(0)或character(0)

get.subject <- function(msg.vec){subj <- msg.vec[grepl("Subject: ", msg.vec)]if(length(subj) > 0){return(strsplit(subj, "Subject: ")[[1]][2])}else{return("")}
}

  

#抽取日期
#需要解决的问题:
#1. 邮件头中会有许多行与"Date: "匹配,但是真正需要的是只有字符串首部出现的
#利用这一点,要求正则表达式只匹配在字符串首部的"Date: ",使用脱字符"^Date: "
#2. 有可能在正文中也匹配上,所以只需要保存第一次匹配成功的字符串即可
#3. 处理文本:拆分字符:加号或减号或冒号
#4. 将首部或者尾部的空白字符替换掉
#5. 只返回符合格式的部分,除掉25字符以后的内容

get.date <- function(msg.vec){date.grep <- grepl("^Date: ", msg.vec)date.grep <- which(date.grep == TRUE)date <- msg.vec[date.grep[1]]date <- strsplit(date, "\\+|\\-|: ")[[1]][2]date <- gsub("^\\s+|\\s+$", "", date)return(strtrim(date, 25))
}

  

#处理邮件

easyham.docs <- dir(easyham.path)
easyham.docs <- easyham.docs[which(easyham.docs != "cmds")]
easyham.parse <- lapply(easyham.docs, function(p) parse.email(paste(easyham.path, p, sep = "")))
ehparse.matrix <- do.call(rbind, easyham.parse)
allparse.df <- data.frame(ehparse.matrix, stringsAsFactors = FALSE)
names(allparse.df) <- c("Date", "From.EMail", "Subject", "Message", "Path")

  

#时间格式不统一,需要进一步处理,转换为R中的POSIX对象,以便于排序

#将形如(Wed, 04 Dec 2002 11:36:32)和(04 Dec 2002 11:49:23)格式的字符串转换成POSIX格式

date.converter <- function(dates, pattern1, pattern2){pattern1.convert <- strptime(dates, pattern1)pattern2.convert <- strptime(dates, pattern2)pattern1.convert[is.na(pattern1.convert)] <- pattern2.convert[is.na(pattern2.convert)]return(pattern1.convert)
}

  

#指明需要转换的格式

pattern1 <- "%a, %d %b %Y %H:%M:%S"
pattern2 <- "%d %b %Y %H:%M:%S"

  

#系统区域设置,"LC_TIME"的设置会对as.POSIXlt()和strptime()的表现造成影响
#如果不进行设置,会使后面的处理后结果Date列全部缺失

Sys.setlocale("LC_TIME", "C")
allparse.df$Date <- date.converter(allparse.df$Date, pattern1, pattern2)

  

#转换成小写,统一格式

allparse.df$Subject <- tolower(allparse.df$Subject)
allparse.df$From.EMail <- tolower(allparse.df$From.EMail)

  

#根据时间排序 注意排序方法,使用with( , order()),虽然不直观,但是会经常用到,默认升序排列
#产生训练集:前一半作为训练集

priority.df <- allparse.df[with(allparse.df, order(Date)), ]
priority.train <- priority.df[1:(round(nrow(priority.df) / 2)), ]

  

######################################
#设置权重策略
######################################
#用ddply()在数据框上执行操作,对象是数据组From.EMail,summarise()创建了Freq保存频次信息
#日期一列会影响ddply()的操作,因此需要将该列删除或者是转成字符串类型,否则会出错

#直接应用书上的代码会提示如下error

#这里给出两种解决方案,都没问题

priority.train.temp1 <- priority.train[, c(2,3,4,5)]
priority.train.temp2 <- priority.train
priority.train.temp2$Date <- as.character(priority.train.temp2$Date)
from.weight <- ddply(priority.train.temp2, .(From.EMail), summarise, Freq = length(Subject))

  

#另外文件源码里给出另一种处理方式,得到的结果是一样的

library(reshape2)
from.weight <- melt(with(priority.train, table(From.EMail)), value.name="Freq")

  

#结果可视化
#排序、过滤频数小于6的观测、绘制图形

from.weight <- from.weight[with(from.weight, order(Freq)), ]
from.ex <- subset(from.weight, Freq > 6)
from.scales <- ggplot(from.ex) +geom_rect(aes(xmin = 1:nrow(from.ex) - 0.5,xmax = 1:nrow(from.ex) + 0.5,ymin = 0,ymax = Freq,fill = "lightgrey",color = "darkblue")) +scale_x_continuous(breaks = 1:nrow(from.ex), labels = from.ex$From.EMail) +coord_flip() +scale_fill_manual(values = c("lightgrey" = "lightgrey"), guide = "none") +scale_color_manual(values = c("darkblue" = "darkblue"), guide = "none") +ylab("Number of Emails Received (truncated at 6)") +xlab("Sender Address") +theme_bw() +theme(axis.text.y = element_text(size = 5, hjust = 1))
print(from.scales)

  

#根据条形图,只有几个人的数量很大,属于特殊情况,会导致权重偏移
#解决方案:尺度变换:不会因为极个别情况影响整体的阈值计算
#对数变换:观察绝对值权重、自然对数变换权重及常用对数变换权重

from.weight <- transform(from.weight,Weight = log(Freq + 1),log10Weight = log10(Freq + 1))
from.rescaled <- ggplot(from.weight, aes(x = 1:nrow(from.weight))) +geom_line(aes(y = Weight, linetype = "ln")) +geom_line(aes(y = log10Weight, linetype = "log10")) +geom_line(aes(y = Freq, linetype = "Absolute")) +scale_linetype_manual(values = c("ln" = 1,"log10" = 2,"Absolute" = 3),name = "Scaling") +xlab("") +ylab("Number of emails Receieved") +theme_bw() +theme(axis.text.y = element_blank(), axis.text.x = element_blank())
print(from.rescaled)

  

#绝对值权重过于陡峭,常用对数变换差异太弱,选择自然对数变换更加合理

#来自书中的警告:不允许特征集合中出现值为0的观测记录,否则log()会返回-Inf(负无穷),破坏整个结果

#线程活跃度的权重计算
#针对"re: "回复操作,可以找到相关线程
#返回所有带有"re: "的邮件的发件人和初始线程的主题

find.threads <- function(email.df){response.threads <- strsplit(email.df$Subject, "re: ")is.thread <- sapply(response.threads, function(subj) ifelse(subj[1] == "", TRUE, FALSE))threads <- response.threads[is.thread]senders <- email.df$From.EMail[is.thread]threads <- sapply(threads, function(t) paste(t[2:length(t)], collapse = "re: "))return(cbind(senders, threads))
}
threads.matrix <- find.threads(priority.train)

  

#根据线程中活跃的发件人赋予权重,增加在发件人上,但是只关注出现在threads.matrix中的发件人
#仍然采用自然对数变换的权重

email.thread <- function(threads.matrix){senders <- threads.matrix[, 1]senders.freq <- table(senders)senders.matrix <- cbind(names(senders.freq), senders.freq, log(senders.freq + 1))senders.df <- data.frame(senders.matrix, stringsAsFactors = FALSE)row.names(senders.df) <- 1:nrow(senders.df)names(senders.df) <- c("From.EMail", "Freq", "Weight")senders.df$Freq <- as.numeric(senders.df$Freq)senders.df$Weight <- as.numeric(senders.df$Weight)return(senders.df)
}
senders.df <- email.thread(threads.matrix)

  

#基于已知的活跃线程,追加权重:假设这个线程已知,用户会觉得这些更活跃的线程更重要
#unique()得到所有线程名称,thread.counts里存放了所有线程对应的活跃度及权重
#最后合并,返回线程名、频数、间隔时间和权重

get.threads <- function(threads.matrix, email.df){threads <- unique(threads.matrix[, 2])thread.counts <- lapply(threads,function(t) thread.counts(t, email.df))thread.matrix <- do.call(rbind, thread.counts)return(cbind(threads, thread.matrix))
}

  

#输入线程主题和训练数据,通过所有邮件日期和时间戳,计算训练数据中这个线程接收了多少邮件
#thread.times找到了线程的时间戳,其向量长度就是该线程接收邮件的频数
#time.span是线程在训练数据中存在的时间:为了计算活跃度
#log.trans.weight是常用对数的权重
#一个线程中只有一条邮件记录的情况:训练数据开始收集数据时线程结束或训练数据结束收集时线程开始
#要剔除这种情况,返回缺失值
#实际情况中,频数小而间隔大,意味着trans.weight是远小于1的值,对它进行对数变换时,结果为负
#为了将权重计算不引入负值,进行仿射变换,即加10

thread.counts <- function(thread, email.df){thread.times <- email.df$Date[which(email.df$Subject == thread |email.df$Subject == paste("re:", thread))]freq <- length(thread.times)min.time <- min(thread.times)max.time <- max(thread.times)time.span <- as.numeric(difftime(max.time, min.time, units = "secs"))if(freq < 2){return(c(NA, NA, NA))}else{trans.weight <- freq / time.spanlog.trans.weight <- 10 + log(trans.weight, base = 10)return(c(freq, time.span, log.trans.weight))}
}

  

#生成权重数据,并做一定处理,最后用subset剔除缺失行

thread.weights <- get.threads(threads.matrix, priority.train)
thread.weights <- data.frame(thread.weights, stringsAsFactors = FALSE)
names(thread.weights) <- c("Thread", "Freq", "Response", "Weight")
thread.weights$Freq <- as.numeric(thread.weights$Freq)
thread.weights$Response <- as.numeric(thread.weights$Response)
thread.weights$Weight <- as.numeric(thread.weights$Weight)
thread.weights <- subset(thread.weights, is.na(thread.weights$Freq) == FALSE)

  

图中可以看到,即使有相同频数,因为响应时间不同,赋予的权重也不同。虽然对于一些人来说可能不会用这种方式排序,但是作为一种通用解决方案,需要这种量化的手段

#线程中的高频词权重:假设出现在活跃线程邮件主题中的高频词比低频词和出现在不活跃线程中的词项重要
#term.counts():输入词项向量和TDM选项列表,返回词项的TDM并抽取所有线程中的词项频次

term.counts <- function(term.vec, control){vec.corpus <- Corpus(VectorSource(term.vec))vec.tdm <- TermDocumentMatrix(vec.corpus, control = control)return(rowSums(as.matrix(vec.tdm)))
}

  

#计算词频,并只留下词项
#对词项进行赋予权重,该权重=该词项所在所有线程权重的平均值
#将向量转数据框,词项提取为名称,行名改为行号

thread.terms <- term.counts(thread.weights$Thread, control = list(stopwords = stopwords()))
thread.terms <- names(thread.terms)
term.weights <- sapply(thread.terms, function(t) mean(thread.weights$Weight[grepl(t, thread.weights$Thread, fixed = TRUE)]))
term.weights <- data.frame(list(Term = names(term.weights), Weight = term.weights), stringsAsFactors = FALSE, row.names = 1:length(term.weights))

  

#邮件词项权重:假设与已读邮件相似的邮件比完全陌生的邮件更重要
#计算出现在邮件中的词频,取对数变换值作为权重
#将负值权重剔除

msg.terms <- term.counts(priority.train$Message, control = list(stopwords = stopwords(), removePunctuation = TRUE, removeNumbers = TRUE))
msg.weights <- data.frame(list(Term = names(msg.terms), Weight = log(msg.terms, base = 10)), stringsAsFactors = FALSE, row.names = 1:length(msg.terms))
msg.weights <- subset(msg.weights, Weight > 0)

  

#################
#总结:共有5项权重数据框:
#from.weight(社交特征)
#senders.df(发件人在线程内的活跃度)
#thread.weights(线程的活跃度)
#term.weights(活跃线程的词项)
#msg.weights(所有邮件共有词项)
##############################################################

####################################
#训练和测试排序算法
####################################
#思路是给每封邮件都产生一个优先等级,这就要将前面提到的各个权重相乘
#因此需要对每封邮件都进行解析、抽取特征、匹配权重数据框并查找权重值
#用这些权重值的乘积作为排序的依据
####################
#首先执行权重查找,即主题和正文词项
#输入三个参数:待查找词项(字符串)、查找对象(权重数据框)、查找类型(T为词项,F为线程)
#返回权重值
#查找失败的情况:
#1.检查输入get.weights()的待查找词项长度是否大于0,如果输入无效则返回1不影响乘积运算
#2.match()对于没有匹配上的元素返回了NA,要将NA替换为1,通过判断match.weights为0即可

get.weights <- function(search.term, weight.df, term = TRUE){if(length(search.term) > 0 ){if(term){term.match <- match(names(search.term), weight.df$Term)}else{term.match <- match(search.term, weight.df$Thread)}match.weights <- weight.df$Weight[which(!is.na(term.match))]#书上的代码有误,但是表述正确if(length(match.weights) < 1){return(1)}else{return(mean(match.weights))}}else{return(1)}
}

  

#输入邮件路径,返回排序权重(rank)

rank.message <- function(path){#抽取四个特征:#msg[]1日期2发信人3主题4正文5路径msg <- parse.email(path)# Weighting based on message author# First is just on the total frequency#查找发件人地址权重,未匹配的返回1from <- ifelse(length(which(from.weight$From.EMail == msg[2])) > 0,from.weight$Weight[which(from.weight$From.EMail == msg[2])], 1)# Second is based on senders in threads, and threads themselves#查找发件人活跃度权重,未匹配返回1thread.from <- ifelse(length(which(senders.df$From.EMail == msg[2])) > 0,senders.df$Weight[which(senders.df$From.EMail == msg[2])], 1)#解析主题是否在线程内subj <- strsplit(tolower(msg[3]), "re: ")is.thread <- ifelse(subj[[1]][1] == "", TRUE, FALSE)#线程活跃度查找并匹配if(is.thread){activity <- get.weights(subj[[1]][2], thread.weights, term = FALSE)}else{activity <- 1}# Next, weight based on terms    # Weight based on terms in threads#活跃线程词项权重匹配thread.terms <- term.counts(msg[3], control = list(stopwords = TRUE))thread.terms.weights <- get.weights(thread.terms, term.weights)# Weight based terms in all messages#正文词项权重匹配msg.terms <- term.counts(msg[4], control = list(stopwords = TRUE,  removePunctuation = TRUE,removeNumbers = TRUE))msg.weights <- get.weights(msg.terms, msg.weights)# Calculate rank by interacting all weights#排序依据是所有查找到的权重的乘积rank <- prod(from, thread.from, activity, thread.terms.weights, msg.weights)#返回日期、发件人、主题、排序权重积return(c(msg[1], msg[2], msg[3], rank))
}

  

#启动排序算法
#按时间分为训练数据和测试数据,注意round()在处理.5的时候,返回的值是最靠近的偶数

train.paths <- priority.df$Path[1:(round(nrow(priority.df) / 2))]
test.paths <- priority.df$Path[((round(nrow(priority.df) / 2)) + 1):nrow(priority.df)]

  

#对训练数据进行处理,返回排序值
#警告可以忽略,应用suppressWarning()函数即可

train.ranks <- lapply(train.paths, rank.message)
train.ranks.matrix <- do.call(rbind, train.ranks)
train.ranks.matrix <- cbind(train.paths, train.ranks.matrix, "TRAINING")
train.ranks.df <- data.frame(train.ranks.matrix, stringsAsFactors = FALSE)
names(train.ranks.df) <- c("Message", "Date", "From", "Subj", "Rank", "Type")
train.ranks.df$Rank <- as.numeric(train.ranks.df$Rank)

  

#计算优先邮件的阈值(取中位数)新建列设置是否推荐

priority.threshold <- median(train.ranks.df$Rank)
train.ranks.df$Priority <- ifelse(train.ranks.df$Rank >= priority.threshold, 1, 0)

  

#将设定阈值的结果可视化:

threshold.plot <- ggplot(train.ranks.df, aes(x = Rank)) + stat_density(aes(fill="darkred")) + geom_vline(xintercept = priority.threshold, linetype = 2) + scale_fill_manual(values = c("darkred" = "darkred"), guide = "none") + theme_bw()
print(threshold.plot)

  

从图中可以看到阈值约为24,排序结果是明显的重尾分布,说明排序算法在训练集上表现不错

书中提到与标准差作为阈值,此时阈值为90. 中位数的做法有较大包容性,而标准差的方式将大部分邮件排除在外了

#测试集测试效果

test.ranks <- suppressWarnings(lapply(test.paths,rank.message))
test.ranks.matrix <- do.call(rbind, test.ranks)
test.ranks.matrix <- cbind(test.paths, test.ranks.matrix, "TESTING")
test.ranks.df <- data.frame(test.ranks.matrix, stringsAsFactors = FALSE)
names(test.ranks.df) <- c("Message","Date","From","Subj","Rank","Type")
test.ranks.df$Rank <- as.numeric(test.ranks.df$Rank)
test.ranks.df$Priority <- ifelse(test.ranks.df$Rank >= priority.threshold, 1, 0)

  

#合并训练集和测试集

final.df <- rbind(train.ranks.df, test.ranks.df)
Sys.setlocale("LC_TIME", "C")
final.df$Date <- date.converter(final.df$Date, pattern1, pattern2)
final.df <- final.df[rev(with(final.df, order(Date))), ]

  

在上图的基础上叠加测试数据的排序密度

testing.plot <- ggplot(subset(final.df, Type == "TRAINING"), aes(x = Rank)) +stat_density(aes(fill = Type, alpha = 0.65)) +stat_density(data = subset(final.df, Type == "TESTING"),aes(fill = Type, alpha = 0.65)) +geom_vline(xintercept = priority.threshold, linetype = 2) +scale_alpha(guide = "none") +scale_fill_manual(values = c("TRAINING" = "darkred", "TESTING" = "darkblue")) +theme_bw()
print(testing.plot)

  

图表分析:

测试数据的分布尾部密度更高,说明更多邮件的优先级排序值不高;

密度估计不平滑,说明测试数据中包含了较多训练数据中没有出现的特征。

测试数据中排序在前面的邮件:

可以看到,该排序算法在线程上的表现较好,即相同线程的各个邮件大致在同一组;相同主题不同发件人也有不同的优先级。

转载于:https://www.cnblogs.com/gyjerry/p/5565665.html

[读书笔记]机器学习:实用案例解析(4)相关推荐

  1. 《用于物联网的Arduino项目开发:实用案例解析》—— 3.4 小结

    本节书摘来自华章出版社<用于物联网的Arduino项目开发:实用案例解析>一 书中的第3章,第3.4节,作者[美]安德尔·杰韦德(Adeel Javed),更多章节内容可以访问云栖社区&q ...

  2. 《统计学习方法》读书笔记——机器学习常用评价指标

    传送门 <统计学习方法>读书笔记--机器学习常用评价指标 <统计学习方法>读书笔记--感知机(原理+代码实现) <统计学习方法>读书笔记--K近邻法(原理+代码实现 ...

  3. 《Speech and Language Processing》读书笔记——语法规则及其解析

    <Speech and Language Processing>读书笔记--语法规则及其解析 1.摘要 本章主要介绍了: 上下文无关语法(CFG) 语法解析算法(CKY.Earley) 2 ...

  4. 《机器学习:实用案例解析》,读书笔记

    第1章:数据处理技巧 案例1: 数据:6,0000条不明飞行物(UFO)的目击纪录和报道.主要目击纪录发生在美国. 问题:面对这份数据的时空维度,我们可能会有以下疑问:UFO的出现是否有周期性规律?美 ...

  5. [读书笔记]机器学习:实用案例解析(6)

    第6章  正则化:文本回归 线性回归的非线性扩展:广义加性模型(GAM, Generalized Additive Model)R语言中可用gam()函数实现 多项式回归:degree值不能无限增大, ...

  6. 《机器学习:实用案例解析》第三章 (3)

    代码: library(tm) library(ggplot2) spam.path<-"data/spam/" spam2.path<-"data/spam ...

  7. 【读书笔记】iOS-网络-解析响应负载

    Web Service可以通过多种格式返回结构化数据, 不过大多数时候使用的是XML与JSON.也可以让应用只接收HTML结构的数据.实现了这些Web Service或是接收HTML文档的应用必须能解 ...

  8. 木马核心技术剖析读书笔记之木马实例解析

    模块化的木马系统架构 被控端是整个木马的核心,它包含 Dropper 与常驻模块两部分 Dropper Dropper 是木马在植入过程中执行的组件,只在目标机器上执行一次,然后自行删除.Droppe ...

  9. R语言学习笔记-机器学习1-3章

    在折腾完爬虫还有一些感兴趣的内容后,我最近在看用R语言进行简单机器学习的知识,主要参考了<机器学习-实用案例解析>这本书. 这本书是目前市面少有的,纯粹以R语言为基础讲解的机器学习知识,书 ...

  10. 《统计学习方法》读书笔记——朴素贝叶斯法(公式推导+代码实现)

    传送门 <统计学习方法>读书笔记--机器学习常用评价指标 <统计学习方法>读书笔记--感知机(原理+代码实现) <统计学习方法>读书笔记--K近邻法(原理+代码实现 ...

最新文章

  1. oracle导出建表主键,oracle主键自动生成 配合hibernate的生成策略详解
  2. 顶级Javaer,常用的 14 个类库
  3. python自动化入门操作文档,写得如此清晰明了,忍不住让人点赞
  4. python地址簿3.7版本_用python编写地址簿程序的问题
  5. Android Studio 技巧
  6. 架构实战项目心得(十四):spring-boot结合Swagger2构建RESTful API测试体系
  7. 第4章 基本TCP套接口编程
  8. jmail 发送html,jmail发送html格式的邮件
  9. LQR 的直观推导及简单应用
  10. 抓包中情局特工后,是时候聊聊勒索黑客了!
  11. 判断是否为回文——判断用户输入的字符串是否为回文。回文是指正反拼写形式都是一样的词,譬如“racecar”。
  12. 【瑞吉外卖】学习笔记-day1:项目介绍及后台初识
  13. 【Golang源码阅读】strings/builder.go
  14. 3分钟配置zabbix 监控mysql
  15. Caffeine教程
  16. 打印机服务器未响应,打印机不能打印,点击打印后没反应
  17. CAD图纸打印文字显示空心怎么办?
  18. 微信小程序开发实战 ②①(网路请求Promise化)
  19. sku展示php,前端如何展示商品属性:SKU多维属性状态判断算法的应用
  20. 服务器架设游戏还可以挂网站吗,游戏服务器架设中需要注意的几个要素

热门文章

  1. 二十一天学通C++之异常概述
  2. 演说时代之艺术创造始于初心
  3. 二分法02:寻找第一个和最后一个的满足条件的位置
  4. fiddler实现将http请求的js转为本地的js文件
  5. html表格 溢出,html – 如何创建在溢出时滚动的表格单元格
  6. java判断读到末尾_Java 中的运算符和流程控制
  7. zookeeper 客户端常用命令_看完这篇文章,还说自己不会使用Zookeeper命令吗?
  8. Flutter高级第6篇:事件广播 、事件监听
  9. 系统提示 由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作。
  10. linux下的5个查找命令