繁体   English   中英

替代for循环R

[英]Alternative to for loop R

我编写了一个函数,该函数将比较IP地址的相似性,并让用户选择八位位组中的详细程度。 例如,在地址255.255.255.0255.255.255.1 ,用户可以指定他们只想比较第一个,第一个和第二个,第一个,第二个,第三个,第三个等八位位组。

功能如下:

did.change.ip=function(vec, detail){
  counter=2
  result.vec=FALSE
  r.list=strsplit(vec, '.', fixed=TRUE)

  for(i in vec){
    if(counter>length(vec)){
      break
    }

    first=as.numeric(r.list[[counter-1]][1:detail])
    second=as.numeric(r.list[[counter]][1:detail])

    if(sum(first==second)==detail){
      result.vec=append(result.vec,FALSE)
    }
    else{
      result.vec=append(result.vec,TRUE)
    }
    counter=counter+1
  }
  return(result.vec)
}

一旦数据开始变大,它真的很慢。 对于500,000行的数据集, system.time()结果为:

user  system elapsed 
 208.36    0.59  209.84

是否有R级超级用户了解如何更有效地编写此代码? 我知道lapply()是在向量/数据帧上循环的首选方法,但是我lapply()如何访问向量中的前一个元素。 我试图快速勾勒出一些东西,但返回语法错误:

test=function(vec, detail){
  rlist=strsplit(vec, '.', fixed=TRUE)
  r.value=vapply(rlist, function(x,detail) ifelse(x[1:detail]==x[1:detail] TRUE, FALSE))
}

我已经创建了一些示例数据用于测试,如下所示:

stack.data=structure(list(V1 = c("247.116.209.66", "195.121.47.105", "182.136.49.12", 
"237.123.100.50", "120.30.174.18", "29.85.72.70", "18.186.76.177", 
"33.248.142.26", "109.97.92.50", "217.138.155.145", "20.203.156.2", 
"71.1.51.190", "31.225.208.60", "55.25.129.73", "211.204.249.244", 
"198.137.15.53", "234.106.102.196", "244.3.87.9", "205.242.10.22", 
"243.61.212.19", "32.165.79.86", "190.207.159.147", "157.153.136.100", 
"36.151.152.15", "2.254.210.246", "3.42.1.208", "30.11.229.18", 
"72.187.36.103", "98.114.189.34", "67.93.180.224")), .Names = "V1", class = "data.frame", row.names = c(NA, 
-30L))

这是仅使用基数R的另一种解决方案。

did.change.ip <- function(vec, detail=4){
    ipv <- scan(text=paste(vec, collapse="\n"), 
        what=c(replicate(detail, integer()), replicate(4-detail,NULL)), 
        sep=".", quiet=TRUE)
    c(FALSE, rowSums(vapply(ipv[!sapply(ipv, is.null)], 
        diff, integer(length(vec)-1))!=0)>0)
}

在这里,我们使用scan()将IP地址分解为数字。 然后,我们使用diff查看每个八位位组的diff 看来这比最初的提议要快,但比@josilber的严格解决方案要慢一些(使用具有3000个ip地址的microbenchmark)

Unit: milliseconds
   expr       min        lq    median        uq       max neval
   orig 35.251886 35.716921 36.019354 36.700550 90.159992   100
   scan  2.062189  2.116391  2.170110  2.236658  3.563771   100
 strngr  2.027232  2.075018  2.136114  2.200096  3.535227   100

我想到的最简单的方法是构建一个仅包含所需IP部分的转换向量。 然后,检查每个元素是否等于它之前的元素是一种一线:

library(stringr)
did.change.josilber <- function(vec, detail) {
  s <- str_extract(vec, paste0("^(\\d+\\.){", detail, "}"))
  return(s != c(s[1], s[1:(length(s)-1)]))
}

对于500,000行,这似乎相当有效:

set.seed(144)
big.vec <- sample(stack.data[,1], 500000, replace=T)
system.time(did.change.josilber(big.vec, 3))
#    user  system elapsed 
#   0.527   0.030   0.554 

代码的最大问题是每次迭代都调用append ,这需要将向量重新分配500,000次。 您可以在R inferno第二圈中阅读更多有关此内容的信息。

不确定是否只需要计数,但这可能是一种解决方案:

library(dplyr)
library(tidyr)

# split ip addresses into "octets"
octets <- stack.data %>%
  separate(V1,c("first","second","third","fourth"))

# how many shared both their first and second octets?
octets %>%
  group_by(first,second) %>%
  summarize(n = n())

   first second n
1    109     97 1
2    120     30 1
3    157    153 1
4     18    186 1
5    182    136 1
6    190    207 1
7    195    121 1
8    198    137 1
9      2    254 1
10    20    203 1
11   205    242 1
12   211    204 1
13   217    138 1
14   234    106 1
15   237    123 1
16   243     61 1
17   244      3 1
18   247    116 1
19    29     85 1
20     3     42 1
21    30     11 1
22    31    225 1
23    32    165 1
24    33    248 1
25    36    151 1
26    55     25 1
27    67     93 1
28    71      1 1
29    72    187 1
30    98    114 1

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM