Reputation: 683
I frequently have problems that lend themselves to while loops, but come out ugly, and I'm here to ask if there is an elegant solution or if all possible solutions are ugly. Is there?
Here's a simplified example: suppose we are trying to find the minimum value of a function f <- function(x){x^2}
, as well as the location at which it is found. Suppose we elect to find the minimum by making an initial guess x
and evaluating f(x)
. Then we evaluate f(x-0.1)
, and f(x+0.1)
. If either of these values is lower than f(x)
, our new guess is the argmin. We repeat until such shifts no longer decrease the value.
The best solution I have come up with is to run part of the first iteration of the algorithm outside of the loop. But this requires me to duplicate code from the loop, namely the section of code enclosed with !!!!!!!
.
# function to minimize
f <- function(x){x^2}
# initial guess
x.current <- 1
f.current <- f(x.current)
# !!!!!!!!!!!!
# part of first iteration
x.guess <- c(x.current - 0.1, x.current + 0.1)
f.guess <- sapply(x.guess, f)
best.ind <- which.min(f.guess)
x.new <- x.guess[best.ind]
f.new <- f.guess[best.ind]
# !!!!!!!!!!!!
# part of first iteration and later iterations
while (f.new < f.current){
x.current <- x.new
f.current <- f.new
x.guess <- c(x.current - 0.1, x.current + 0.1)
f.guess <- sapply(x.guess, f)
best.ind <- which.min(f.guess)
x.new <- x.guess[best.ind]
f.new <- f.guess[best.ind]
}
print("best guess = ")
print(x.current)
Is there a "nicer" way of doing this?
Upvotes: 2
Views: 685
Reputation: 31454
There are a variety of ways to deal with this situation. Whether one is 'ugly' or 'pretty' is a matter of opinion, and therefore off topic for StackOverflow. Nonetheless, we can make some generalisations about some different options:
A common rule of thumb is that one should avoid repeating code segments. Whenever you see a sequence of lines repeated at various places in your program, one should strongly consider placing those lines into their own function and calling this function repeatedly.
This aids in the readability of the overall code by making it more succinct, and requiring a maintainer to only read through and understand that section once.
Perhaps more importantly, it also aids in maintainability of the code because any changes to that snippet will automatically propagate through the whole program. Having to hunt down and alter every instance of a repeated code snippet is not only frustrating when it comes to editing code, but it is also a potentially error-prone procedure.
Here's one way you might apply this principle here, using an additional trick of placing the function call inside the loop condition expression, so that we only need to call it once here (although the code inside a while
loop is not guaranteeed to execute, the code in its condition must always be executed at least once:
# initial guess
x <- 1
fx <- f(x)
find.new = function(x){
x.new <- c(x - 0.1, x + 0.1)
f.new <- sapply(x.new, f)
best.ind <- which.min(f.new)
x.new <- x.new[best.ind]
f.new <- f.new[best.ind]
return(list(x=x.new, fx=f.new))
}
while ((new <- find.new(x))$fx < fx){
x <- new$x
fx <- new$fx
}
If, as in this case, there is some code inside the loop that we would always want to execute at least onse, then consider using a repeat loop instead of while. We can then test for the exit condition to either update values or to break from the loop. If the repeated code snippet in your original does not need to execute anywhere else in your program, this can be more concise than wrapping it in its own function
repeat {
x.new <- c(x - 0.1, x + 0.1)
f.new <- sapply(x.new, f)
best.ind <- which.min(f.new)
x.new <- x.new[best.ind]
f.new <- f.new[best.ind]
if (f.new < fx) {
x <- x.new
fx <- f.new
} else {
break
}
}
Upvotes: 1
Reputation: 42592
As pointed out by dww there are several options. There is a third one which initializes the variables which are referenced in the first iteration of the loop and in the test condition of the loop appropriately:
# function to minimize
f <- function(x){x^2}
# initialize values
x.new <- 1
f.new <- f(x.new)
f.current <- f.new + 0.1 # just to fulfill test condition
# part of first iteration and later iterations
while (f.new < f.current){
x.current <- x.new
f.current <- f.new
x.guess <- c(x.current - 0.1, x.current + 0.1)
f.guess <- sapply(x.guess, f)
best.ind <- which.min(f.guess)
x.new <- x.guess[best.ind]
f.new <- f.guess[best.ind]
}
print("best guess = ")
print(x.current)
Upvotes: 1