右转弯

13

我有一个问题,我有一堆长度,想从原点开始(假装我正朝着y轴正方向),向右转并沿着x轴正方向移动长度为length_i。此时我再右转,走过长度为length_i,重复n次。我可以这样做,但我认为有更有效率的方法来做,而我缺乏数学背景:

## Fake Data
set.seed(11)
dat <- data.frame(id = LETTERS[1:6], lens=sample(2:9, 6), 
    x1=NA, y1=NA, x2=NA, y2=NA)

##   id lens x1 y1 x2 y2
## 1  A    4 NA NA NA NA
## 2  B    2 NA NA NA NA
## 3  C    5 NA NA NA NA
## 4  D    8 NA NA NA NA
## 5  E    6 NA NA NA NA
## 6  F    9 NA NA NA NA

## Add a cycle of 4 column    
dat[, "cycle"] <- rep(1:4, ceiling(nrow(dat)/4))[1:nrow(dat)]

##For loop to use the information from cycle column
for(i in 1:nrow(dat)) {

    ## set x1, y1
    if (i == 1) {
       dat[1, c("x1", "y1")] <- 0
    } else {
       dat[i, c("x1", "y1")] <- dat[(i - 1), c("x2", "y2")]
    }

    col1 <- ifelse(dat[i, "cycle"] %% 2 == 0, "x1", "y1")
    col2 <- ifelse(dat[i, "cycle"] %% 2 == 0, "x2", "y2")
    dat[i, col2] <- dat[i, col1]

    col3 <- ifelse(dat[i, "cycle"] %% 2 != 0, "x2", "y2")
    col4 <- ifelse(dat[i, "cycle"] %% 2 != 0, "x1", "y1")
    mag <- ifelse(dat[i, "cycle"] %in% c(1, 4), 1, -1)
    dat[i, col3] <- dat[i, col4] + (dat[i, "lens"] * mag)

}

这将得到期望的结果:

> dat

  id lens x1 y1 x2 y2 cycle
1  A    4  0  0  4  0     1
2  B    2  4  0  4 -2     2
3  C    5  4 -2 -1 -2     3
4  D    8 -1 -2 -1  6     4
5  E    6 -1  6  5  6     1
6  F    9  5  6  5 -3     2

这是它的绘图形式:

library(ggplot2); library(grid)
ggplot(dat, aes(x = x1, y = y1, xend = x2, yend = y2)) + 
    geom_segment(aes(color=id), size=3, arrow = arrow(length = unit(0.5, "cm"))) + 
    ylim(c(-10, 10)) + xlim(c(-10, 10))

这似乎很慢、笨重。我猜测除了在for循环中执行的操作之外,还有更好的方式来进行编程权利的处理。有什么更高效的方法吗?

输入图像描述


1
你只是在添加二维向量。虽然R中没有二维向量类,但矩阵或复数可以为您完成此操作。请尝试在x和y坐标上使用Reduce(cumsum,...,accumulate=TRUE)。 - IRTFM
5个回答

10

(正如@DWin所建议的)这是一个使用复数的解决方案,可以适应任何类型的转向,而不仅仅是90度(-pi / 2弧度)右角。所有内容都是矢量化的:

<code>set.seed(11)
dat <- data.frame(id = LETTERS[1:6], lens = sample(2:9, 6),
                                     turn = -pi/2)

dat <- within(dat, { facing   <- pi/2 + cumsum(turn)
                     move     <- lens * exp(1i * facing)
                     position <- cumsum(move)
                     x2       <- Re(position)
                     y2       <- Im(position)
                     x1       <- c(0, head(x2, -1))
                     y1       <- c(0, head(y2, -1))
                   })

dat[c("id", "lens", "x1", "y1", "x2", "y2")]
#   id lens x1 y1 x2 y2
# 1  A    4  0  0  4  0
# 2  B    2  4  0  4 -2
# 3  C    5  4 -2 -1 -2
# 4  D    8 -1 -2 -1  6
# 5  E    6 -1  6  5  6
# 6  F    9  5  6  5 -3
</code>

turn变量应该与lens一起被视为输入。目前所有的旋转都是 -pi/2 弧度,但您可以将每个旋转设置为任何您想要的。所有其他变量都是输出。


现在稍微有点乐趣:

trace.path <- function(lens, turn) {
  facing   <- pi/2 + cumsum(turn)
  move     <- lens * exp(1i * facing)
  position <- cumsum(move)
  x        <- c(0, Re(position))
  y        <- c(0, Im(position))

  plot.new()
  plot.window(range(x), range(y))
  lines(x, y)
}

trace.path(lens = seq(0, 1,  length.out = 200),
           turn = rep(pi/2 * (-1 + 1/200), 200))

在此输入图像描述

(我尝试在这里复制图表:http://en.wikipedia.org/wiki/Turtle_graphics)

我还让你尝试这些:

trace.path(lens = seq(1, 10, length.out = 1000),
           turn = rep(2 * pi / 10, 1000))

trace.path(lens = seq(0, 1,  length.out = 500),
           turn = seq(0, pi, length.out = 500))

trace.path(lens = seq(0, 1,  length.out = 600) * c(1, -1),
           turn = seq(0, 8*pi, length.out = 600) * seq(-1, 1, length.out = 200))

欢迎您添加自己的内容!


1
@DWin -- 我举手了 ;) 一开始看,我以为Tyler正在用R实现Logo - Josh O'Brien
我得到了比我预期更多的回复。所有的解决方案都有效,应该被未来的搜索者考虑。对于我的需求来说,这个解决方案是最好的方法。谢谢大家。 - Tyler Rinker

8
这是使用复数的另一种方法。您可以通过乘以-1i来将矢量在复平面上“向右旋转”。下面的代码使第一次遍历沿着正X(Re()-al轴)进行,并且每个后续遍历都会向“右”旋转。
imVecs <- lengths*c(0-1i)^(0:3)
imVecs
# [1]  9+0i  0-5i -9+0i  0+9i  8+0i  0-5i -8+0i  0+7i  8+0i  0-1i -5+0i  0+3i  4+0i  0-7i -4+0i  0+2i
#[17]  3+0i  0-7i -5+0i  0+8i

cumsum(imVecs)
# [1] 9+0i 9-5i 0-5i 0+4i 8+4i 8-1i 0-1i 0+6i 8+6i 8+5i 3+5i 3+8i 7+8i 7+1i 3+1i 3+3i 6+3i 6-4i 1-4i
#[20] 1+4i
plot(cumsum(imVecs))
lines(cumsum(imVecs))

这是使用复平面旋转进行向右45度旋转的方法:
> sqrt(-1i)
[1] 0.7071068-0.7071068i
> imVecs <- lengths*sqrt(0-1i)^(0:7)
Warning message:
In lengths * sqrt(0 - (0+1i))^(0:7) :
  longer object length is not a multiple of shorter object length
> plot(cumsum(imVecs))
> lines(cumsum(imVecs))

而且情节是:

enter image description here


5

这个图表并不美观,但我包含它是为了展示这种“向量化”的坐标计算能够产生正确的结果,并且很容易适应您的需求:

xx <- c(1,0,-1,0)
yy <- c(0,-1,0,1)

coords <- suppressWarnings(cbind(x = cumsum(c(0,xx*dat$lens)), 
                                 y = cumsum(c(0,yy*dat$lens))))
plot(coords, type="l", xlim=c(-10,10), ylim=c(-10,10))

enter image description here


3

也许以距离和方位角来考虑这个问题会更有用。距离由 dat$lens 给出,而方位角是相对于某个任意参考线(比如 x 轴)的移动角度。然后,在每一步中,

x.new = x.old + distance * cos(bearing)
y.new = y.old + distance * sin(bearing)
bearing = bearing + increment

在这里,因为我们从原点开始并向+x方向移动,所以(x,y)=(0,0),方位角从0度开始。向右转只是将方位角增加-90度(-pi/2弧度)。所以,在R代码中,使用您定义的dat

x <-0
y <- 0
bearing <- 0
for (i in 1:nrow(dat)){
  dat[i,c(3,4)] <- c(x,y)
  length <- dat[i,2]
  x <- x + length * cos(bearing)
  y <- y + length * sin(bearing)
  dat[i,c(5,6)] <- c(x,y)
  bearing <- bearing - pi/2
}

这样做可以得到你想要的结果,并且有一个优点,那就是你可以很简单地更新它,使其左转、45度转弯或其他。你甚至可以在 dat 中添加一个 bearing.increment 列以创建随机行走。


+1;这与我建议的复数方法完全类似。要更改方位参数,您只需要使向量系列将长度向量相乘的所有向量长度都为一即可。 - IRTFM
是的,但这也可以扩展到3维(或n维)。我不认为复数方法具有这种灵活性。 - jlhoward
啊,说得好。在三维空间中旋转非常复杂。你可能需要这样来模拟战斗机游戏物理学。我想知道是否有一个三维解决方案(可能不是在R中),在其他的stackexchange论坛中? - IRTFM

1
非常类似于Josh的解决方案:

lengths <- sample(1:10, 20, repl=TRUE)
x=cumsum(lengths*c(1,0,-1,0))
y=cumsum(lengths*c(0,1,0,-1))
cbind(x,y)
      x  y
 [1,] 9  0
 [2,] 9  5
 [3,] 0  5
 [4,] 0 -4
 [5,] 8 -4
 [6,] 8  1
 [7,] 0  1
 [8,] 0 -6
 [9,] 8 -6
[10,] 8 -5
[11,] 3 -5
[12,] 3 -8
[13,] 7 -8
[14,] 7 -1
[15,] 3 -1
[16,] 3 -3
[17,] 6 -3
[18,] 6  4
[19,] 1  4
[20,] 1 -4

基础图形:

plot(cbind(x,y))
arrows(cbind(x,y)[-20,1],cbind(x,y)[-20,2], cbind(x,y)[-1,1], cbind(x,y)[-1,2] )

enter image description here

这表明Josh和我两个人的解决方案都是“转向错误的”,所以您需要更改我们的“转换矩阵”的符号。而且我们可能应该从(0,0)开始,但您应该没有问题来适应您的需求。

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