使用for循环重复向量中的元素

9

我希望能够在R中从3到50制作一个向量,看起来像这样:

3 4 4 5 6 6 7 8 8 .. 50 50

我想使用一个for循环内嵌另一个for循环,但它并没有产生我想要的效果。

f <- c()
for (i in 3:50) {
  for(j in 1:2) {
    f = c(f, i)
  }
}

这是什么问题?

3
像循环中那样增长向量是一个不好的想法。你正在用一个非意图二次算法来解决线性问题。使用模算术直接构造向量。 - John Coleman
2
由于您坚持在我的回答下面使用嵌套for循环,因此您当前代码中的错误在于您使用了 for(j in 1:2),无论 i 是奇数(那么 j 应该为1)还是偶数(那么 j 应该循环1和2)。因此,在外部for循环中,您需要为 j 设置最大值,我们称之为 a。然后,内部循环需要像这样看起来: for( j in 1:a)。通过使用模运算符(见维基百科“模运算”),您可以检查 i 是否为奇数:if( i %% 2 ) ... - akraf
1
我建议你尝试把这些部分组合起来,然后自己发布答案。如果你坚持的话,我可以发布正确的答案,但那样做之后你就不会成为一个更好的程序员了 ;) - akraf
该死,打错了:您可以使用 if( i %% 2 == 1) 来检查 i 是否为奇数... - akraf
8个回答

16

另一个选择是使用内嵌的rep

rep(3:50, rep(1:2, 24))

这将导致:

 [1]  3  4  4  5  6  6  7  8  8  9 10 10 11 12 12 13 14 14 15 16 16 17 18 18 19 20 20
[28] 21 22 22 23 24 24 25 26 26 27 28 28 29 30 30 31 32 32 33 34 34 35 36 36 37 38 38
[55] 39 40 40 41 42 42 43 44 44 45 46 46 47 48 48 49 50 50

这利用了 rep 函数的 times 参数也可以是一个整数向量,其长度等于 x 参数的长度。

您可以将其推广到:

s <- 3
e <- 50
v <- 1:2

rep(s:e, rep(v, (e-s+1)/2))

甚至可以使用reprep_len的组合选项:

v <- 3:50
rep(v, rep_len(1:2, length(v)))

2
这个概念在概念上非常简单(尽管您必须稍微思考一下才能看出它的工作原理),并且非常快速(在我的机器上平均只需1微秒)。 - John Coleman

9
基于的解决方案。
as.vector(sapply(0:23 * 2 + 2, function(x)  x + c(1, 2, 2)))

# [1]  3  4  4  5  6  6  7  8  8  9 10 10 11 12 12 13 14 14 15 16 16 17 18 18 19 20 20 21 22 22 23 24 24 25 26 26
# [37] 27 28 28 29 30 30 31 32 32 33 34 34 35 36 36 37 38 38 39 40 40 41 42 42 43 44 44 45 46 46 47 48 48 49 50 50

基准测试

以下是所有当前答案的性能比较结果。结果显示cumsum(rep(c(1, 1, 0), 24)) + 2L (m8)最快,而rep(3:50, rep(1:2, 24)) (m1) 与 m8几乎一样快。

library(microbenchmark)
library(ggplot2)

perf <- microbenchmark(
  m1 = {rep(3:50, rep(1:2, 24))},
  m2 = {rep(3:50, each = 2)[c(TRUE, FALSE, TRUE, TRUE)]},
  m3 = {v <- 3:50; sort(c(v,v[v %% 2 == 0]))},
  m4 = {as.vector(t(cbind(seq(3,49,2),seq(4,50,2),seq(4,50,2))))},
  m5 = {as.vector(sapply(0:23 * 2 + 2, function(x)  x + c(1, 2, 2)))},
  m6 = {sort(c(3:50, seq(4, 50, 2)))},
  m7 = {rep(seq(3, 50, 2), each=3) + c(0, 1, 1)},
  m8 = {cumsum(rep(c(1, 1, 0), 24)) + 2L},
  times = 10000L
)

perf
# Unit: nanoseconds
# expr   min    lq      mean median    uq     max neval
#   m1   514  1028  1344.980   1029  1542  190200 10000
#   m2  1542  2570  3083.716   3084  3085  191229 10000
#   m3 26217 30329 35593.596  31871 34442 5843267 10000
#   m4 43180 48321 56988.386  50891 55518 6626173 10000
#   m5 30843 35984 42077.543  37526 40611 6557289 10000
#   m6 40611 44209 50092.131  46779 50891  446714 10000
#   m7 13879 16449 19314.547  17478 19020 6309001 10000
#   m8     0  1028  1256.715   1028  1542   71454 10000

@JohnColeman 谢谢。不同答案之间的时间差是纳秒级别的。除非 OP 真的关心这些微小的时间差异,否则我认为所有答案都是一个很好的选择。 - www
1
感谢进行基准测试。看到不同的答案和它们的执行方式真是太好了。 - kangaroo_cliff
1
@headpoint 不用谢。我在考虑如何解释这个模式。看起来使用 seq 的解决方案比仅使用 rep 的解决方案要慢一些。此外,添加 sort 也可能会增加一些时间。但是,时间差异只有纳秒级别。它们都是好的答案。 - www
1
我也在想这个问题。当我从m6中删除了sort后,它的平均时间约为15k。但仍然远远不及m1m2。因此,rep必须比seq快得多。 - kangaroo_cliff
1
很棒的工作。非常有信息量。我对 sort 所花费的时间并不感到惊讶。如果我从我的解决方案中删除 sort,那么它将接近顶级表现者,但是 sort 不能被去除。 - MKR
1
为了好玩,我对你的微基准语句进行了微基准测试。在我的机器上,每个8种方法被评估10000次,收集时间结果到一个包含80000个观测值的数据框中,并计算相关摘要统计信息,平均需要不到2秒钟来完成microbenchmark的评估。R有时候可以出奇地快。 - John Coleman

8
使用rep函数,结合可能使用的逻辑索引回收...[c(TRUE, FALSE, TRUE, TRUE)]
rep(3:50, each = 2)[c(TRUE, FALSE, TRUE, TRUE)]

 ## [1]  3  4  4  5  6  6  7  8  8  9 10 10 11 12 12 13 14 14 15 16 16 17 18 18 19
## [26] 20 20 21 22 22 23 24 24 25 26 26 27 28 28 29 30 30 31 32 32 33 34 34 35 36
## [51] 36 37 38 38 39 40 40 41 42 42 43 44 44 45 46 46 47 48 48 49 50 50

如果您使用逻辑向量(TRUE/FALSE)作为索引(在[]内部),则TRUE将导致选择相应的元素,FALSE将导致省略。如果逻辑索引向量(c(TRUE, FALSE, TRUE, TRUE))比索引向量(rep(3:50, each = 2) 在您的情况下)短,则索引向量被重复使用。

另外需要注意的是:每当您使用R代码时

 x = c(x, something)

或者
 x = rbind(x, something)

如果你使用类似于C语言的编程风格来写R代码,那么你会让你的代码变得不必要复杂,并且在处理大型数据集(比如200MB以上)时可能会导致性能低下和内存不足的问题。R被设计用来避免对数据结构进行底层操作。

想了解更多关于贪婪行为及其惩罚的信息,请阅读《R地狱》中的第二章:Growing Objects。


我不想要向量3、3、4、4、5、5,而是3、4、4、5、6、6、7等。因此重复一个元素1次,然后2次。 - Max
我需要使用嵌套的for循环来创建向量,这是我的学校作业。但是这个过程难以解释清楚吗? - Max
1
出于好奇,我对这3个解决方案进行了基准测试。你的解决方案是迄今为止最快的。 - John Coleman

5
我能找到的最简单方法是创建另一个仅包含even值(基于OP的意图)的向量,然后将两个向量简单地连接起来。示例如下:
v <- 3:50
sort(c(v,v[v %% 2 == 0]))

# [1]  3  4  4  5  6  6  7  8  8  9 10 10 11 12 12 13 14 14 15 16 16
#      17 18 18 19 20 20 21 22 22 23 24 24 25 26 26 27 28 28
#[40] 29 30 30 31 32 32 33 34 34 35 36 36 37 38 38 39 40 40 41 42 42
#     43 44 44 45 46 46 47 48 48 49 50 50

1
这很不错。起初我以为排序会使它变慢,但是microbenchmark显示它比我的解决方案更快。 - John Coleman

4

这里有一个不使用循环的一行解决方案:

> as.vector(t(cbind(seq(3,49,2),seq(4,50,2),seq(4,50,2))))
 [1]  3  4  4  5  6  6  7  8  8  9 10 10 11 12 12 13 14 14 15 16 16 17
[23] 18 18 19 20 20 21 22 22 23 24 24 25 26 26 27 28 28 29 30 30 31 32
[45] 32 33 34 34 35 36 36 37 38 38 39 40 40 41 42 42 43 44 44 45 46 46
[67] 47 48 48 49 50 50

它形成了一个矩阵,其第一列是范围为3:50中的奇数,第二列和第三列是该范围内的偶数,然后(通过转置)逐行读取它。
您的嵌套循环方法存在问题,因为基本模式的长度为3,重复24次(而不是重复50次的长度为2的模式)。如果您想使用嵌套循环,则外部循环可以迭代24次,内部循环3次。外部循环的第一次可以构建3,4,4。第二次通过外部循环可以构建5,6,6。等等。由于有24 * 3 = 72个元素,您可以预先分配向量(通过使用 f <- vector("numeric",74)),以便您不必每次增加1个元素时都扩展它。您目前正在使用的 f <- c(f,i)习语在每个阶段都复制所有旧元素,只创建一个仅比旧向量长1个元素的新向量。虽然这里的元素太少以至于没有什么区别,但如果您尝试以这种方式创建大型向量,则性能可能会非常差。

4
这里有一种方法,结合了其他答案的部分内容。
rep(seq(3, 50, 2), each=3) + c(0, 1, 1)
 [1]  3  4  4  5  6  6  7  8  8  9 10 10 11 12 12 13 14 14 15 16
[21] 16 17 18 18 19 20 20 21 22 22 23 24 24 25 26 26 27 28 28 29
[41] 30 30 31 32 32 33 34 34 35 36 36 37 38 38 39 40 40 41 42 42
[61] 43 44 44 45 46 46 47 48 48 49 50 50

这里是使用cumsum的第二种方法

cumsum(rep(c(1, 1, 0), 24)) + 2L

这应该很快。


1
刚刚根据你的答案更新了我的基准测试。感谢你的分享。 - www
1
@www 如果你有时间的话,考虑添加我的第二种方法。它应该会快得多。 - lmo
1
我已将你的第二个方法添加到我的基准测试中。你是对的。它的速度惊人地快。 - www

3
这也可以。
sort(c(3:50, seq(4, 50, 2)))

0

另一个想法,虽然速度不如最快的解决方案:

mat <- matrix(3:50,nrow=2)
c(rbind(mat,mat[2,]))
# [1]  3  4  4  5  6  6  7  8  8  9 10 10 11 12 12 13 14 14 15 16 16 17 18 18 19 20 20 21 22 22
# [31] 23 24 24 25 26 26 27 28 28 29 30 30 31 32 32 33 34 34 35 36 36 37 38 38 39 40 40 41 42 42
# [61] 43 44 44 45 46 46 47 48 48 49 50 50

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接