在R语言的函数调用中,变量的赋值是如何工作的



我试图用仿射变换和迭代函数系统(IFS(模拟R中的Sierpinski三角形。希望我能进一步练习如何模拟巴恩斯利蕨类植物。对于那些懂中文的人来说,这段视频是我这次练习的起点。

以下是模拟过程的简短介绍:

  1. 创建一个等边三角形,将顶点命名为A、B、C
  2. 创建一个位于三角形ABC内的随机初始点
  3. 机会均等的样本A、B、C
  4. 如果结果为A,则将初始点移动到A及其自身的中点
  5. 重复步骤3,将最后一个点移动到结果点及其本身的中点。通过反复这样做,我们应该看到点的路径看起来像一个Sierpinski三角形

我想知道变量的赋值是如何在自定义函数中工作的。我想创建一个对象(矩阵或数据帧(来存储模拟点的路径,并不断更新对象以跟踪点的移动方式。

以下是我当前的代码:

# create the triangle
triangle <- matrix(c(A = c(-1,0), 
B = c(1, 0), 
C = c(0, sqrt(3))),
byrow = TRUE, nrow = 3, ncol = 2)
colnames(triangle) <- c("X", "Y") # axis name
rownames(triangle) <- c("A", "B", "C")
# sample an initial point inside the triangle ABC
sampleInit <- function(){
X <- runif(1, min = -1, max = 1)
Y <- runif(1, min = 0, max = sqrt(3))
if( (Y >= 0) && (Y <= (sqrt(3)*X + sqrt(3))) && (Y <= -sqrt(3)*X+sqrt(3)) ){
return(cbind(X, Y))
} else {
sampleInit()
}
}
### graph: plot the triangle and the initial point together
graphics.off()  
plot(triangle, xlim = c(-1, 1), ylim = c(0, sqrt(3)))
par(new = TRUE)
plot(sampleInit(), xlim = c(-1, 1), ylim = c(0, sqrt(3)), col = "red")
### a three-sided dice: determine the direction to move along
diceRoll <- function(){
return(sample(c("A", "B", "C"), size = 1, prob = c(1/3, 1/3, 1/3)))
}
## path
stepTrace <- as.data.frame(sampleInit())
move <- function(diceOutCome, stepTrace){
lastStep <- tail(stepTrace, 1)
if(diceOutCome == "A"){
X <- (-1 + lastStep[,1])/2
Y <- (0 + lastStep[,2])/2
} else if(diceOutCome == "B"){
X <- (1 + lastStep[,1])/2
Y <- (0 + lastStep[,2])/2
} else if(diceOutCome == "C"){
X <- (0 + lastStep[,1])/2
Y <- (sqrt(3) + lastStep[,2])/2
}
lastStep <- cbind(X, Y)
stepTrace <- rbind(stepTrace, lastStep)
}
move(diceRoll(), stepTrace)
View(stepTrace)

很抱歉故事太长,没有直接跳到关键问题。我的问题是stepTrace(我想存储路径的对象(在执行最后两行时没有得到更新。

我想象的是move()中的分配过程会更新数据帧stepTrace,但事实并非如此。我在调试器中检查了我的代码,发现stepTrace确实在函数调用内部得到了更新,但它没有在函数调用外部传递新的赋值。这就是为什么我想问R中的赋值过程是如何工作的。这种过程和其他通用语言(如Java(之间有什么区别?(我想用Java做这个练习不会遇到这种分配问题。如果我错了,请纠正我,因为我还是Java新手(

当我试图在循环中分配变量时,类似的问题困扰着我。我知道有一个基本函数assign可以帮助解决这个问题,但我不知道它背后的机制是什么

我试着用谷歌搜索我的问题,但我不确定我应该使用哪个关键词,我也没有找到问题的直接答案。感谢对文档的任何评论、关键字或外部资源!

简而言之,move函数可以执行您想要的操作,但这样编写它是不可取的。在当前形式中,stepTrace在函数的本地环境中更新,但不在stepTrace所在的全局环境中更新。它们不是同一个stepTrace。要修复它,您可以运行stepTrace <- move(diceRoll(), stepTrace),但要注意第二个圆圈。对于更干净的方法,请从move中删除最后一个stepTrace赋值。

?return:如果在没有调用return的情况下到达函数的末尾,则返回最后一个求值表达式的值

考虑以下示例:

x <- 5
a <- b <- c <- d <- 1
f1 <- function(x) x + 1
f2 <- function(x) return(x + 1)
f3 <- function(x) x <- x + 1 
f4 <- function(x) x <<- x + 1 
f1(1)
f2(1)
f3(1) # your problem
f4(1) # x gets replaced with x in f4, 2 in global environment.
a <- b <- c <- d <- 1
a <- f1(1)
b <- f2(1)
c <- f3(1)
d <- f4(1)

f3f4通常被认为是不良做法,因为它们有副作用,即它们(可以(修改非局部变量,f2可能会引发讨论。f3见结果

c(f3(1))
#> [1] 2

考虑到我们自己调用f3(1)的实验,我们预计会有一个长度为0(?(的向量。考虑将任何赋值作为函数中的最后一个操作删除,并避免将函数参数命名为与要更改的对象相同的名称。

@DonaldSeinen在回答中解释了如何修复代码。我将尝试向您介绍文档以了解更多详细信息。

首先,您不需要查看外部文档R简介R语言定义手册包含在R发行版中。引言在第10.7节中详细描述了正在发生的事情;范围";。在第3.5节的语言定义中有一个不同的描述;变量范围";。

有些人觉得这些手册中的语言过于技术性。一个更容易阅读的外部参考是Wickham的高级R,在线阅读https://adv-r.hadley.nz/.第6章和第7章讨论了范围界定,特别是第6.4节和第7.2节。

最新更新