I`m learning rust and i have this piece of code here:
fn f(n: i64) -> i64 {
if n == 0 {
return 1;
}
if n > 0 {
return 2 * f(1 - n) + 3 * f(n - 1) + 2;
}
-1 * f(-1 * n)
}
fn main() {
println!("{}", f(50))
}
It seems to work very slow. I left it running for some time with no result in ~2 hours.
I figured out that there is alot of calculations of f() function with the same arguments.
In python the easy way to solve the problem is to use cache:
from functools import lru_cache
#lru_cache
def f(n):
if n == 0:
return 1
return 2 * f(1 - n) + 3 * f(n - 1) + 2 if n > 0 else -1 * f(-n)
res = 0
for s in str(f(50)):
res += int(s)
print(res)
Is there any way to make it right in rust?
Related
function findNum(a, b) {
if (!b) {
b = 0;
}
if (a < 2) {
throw new Error('wrong');
}
if (a === 2) {
return 1 / a + b;
}
return findNum(a - 1, b + 1 / (a * (a -1)));
}
Is there any way to write this func w/o recursion and without using while in it?
Suddenly discover that recursive call of suspend function takes more time then calling the same function but without suspend modifier, so please consider the code snippet below (basic Fibonacci series calculation):
suspend fun asyncFibonacci(n: Int): Long = when {
n <= -2 -> asyncFibonacci(n + 2) - asyncFibonacci(n + 1)
n == -1 -> 1
n == 0 -> 0
n == 1 -> 1
n >= 2 -> asyncFibonacci(n - 1) + asyncFibonacci(n - 2)
else -> throw IllegalArgumentException()
}
If I call this function and measure its execution time with code below:
fun main(args: Array<String>) {
val totalElapsedTime = measureTimeMillis {
val nFibonacci = 40
val deferredFirstResult: Deferred<Long> = async {
asyncProfile("fibonacci") { asyncFibonacci(nFibonacci) } as Long
}
val deferredSecondResult: Deferred<Long> = async {
asyncProfile("fibonacci") { asyncFibonacci(nFibonacci) } as Long
}
val firstResult: Long = runBlocking { deferredFirstResult.await() }
val secondResult: Long = runBlocking { deferredSecondResult.await() }
val superSum = secondResult + firstResult
println("${thread()} - Sum of two $nFibonacci'th fibonacci numbers: $superSum")
}
println("${thread()} - Total elapsed time: $totalElapsedTime millis")
}
I observe further results:
commonPool-worker-2:fibonacci - Start calculation...
commonPool-worker-1:fibonacci - Start calculation...
commonPool-worker-2:fibonacci - Finish calculation...
commonPool-worker-2:fibonacci - Elapsed time: 7704 millis
commonPool-worker-1:fibonacci - Finish calculation...
commonPool-worker-1:fibonacci - Elapsed time: 7741 millis
main - Sum of two 40'th fibonacci numbers: 204668310
main - Total elapsed time: 7816 millis
But if I remove suspend modifier from asyncFibonacci function, I'll have this result:
commonPool-worker-2:fibonacci - Start calculation...
commonPool-worker-1:fibonacci - Start calculation...
commonPool-worker-1:fibonacci - Finish calculation...
commonPool-worker-1:fibonacci - Elapsed time: 1179 millis
commonPool-worker-2:fibonacci - Finish calculation...
commonPool-worker-2:fibonacci - Elapsed time: 1201 millis
main - Sum of two 40'th fibonacci numbers: 204668310
main - Total elapsed time: 1250 millis
I know that's better to rewrite such a function with tailrec it will increase its execution time apx. almost in 100 times, but anyway, what this suspend key word does that decrease execution speed from 1 second to 8 seconds?
Is it totally stupid idea to mark recursive functions with suspend?
As an introductory comment, your testing code setup is too complex. This much simpler code achieves the same in terms of stressing suspend fun recursion:
fun main(args: Array<String>) {
launch(Unconfined) {
val nFibonacci = 37
var sum = 0L
(1..1_000).forEach {
val took = measureTimeMillis {
sum += suspendFibonacci(nFibonacci)
}
println("Sum is $sum, took $took ms")
}
}
}
suspend fun suspendFibonacci(n: Int): Long {
return when {
n >= 2 -> suspendFibonacci(n - 1) + suspendFibonacci(n - 2)
n == 0 -> 0
n == 1 -> 1
else -> throw IllegalArgumentException()
}
}
I tried to reproduce its performance by writing a plain function that approximates the kinds of things the suspend function must do to achieve suspendability:
val COROUTINE_SUSPENDED = Any()
fun fakeSuspendFibonacci(n: Int, inCont: Continuation<Unit>): Any? {
val cont = if (inCont is MyCont && inCont.label and Integer.MIN_VALUE != 0) {
inCont.label -= Integer.MIN_VALUE
inCont
} else MyCont(inCont)
val suspended = COROUTINE_SUSPENDED
loop# while (true) {
when (cont.label) {
0 -> {
when {
n >= 2 -> {
cont.n = n
cont.label = 1
val f1 = fakeSuspendFibonacci(n - 1, cont)!!
if (f1 === suspended) {
return f1
}
cont.data = f1
continue#loop
}
n == 1 || n == 0 -> return n.toLong()
else -> throw IllegalArgumentException("Negative input not allowed")
}
}
1 -> {
cont.label = 2
cont.f1 = cont.data as Long
val f2 = fakeSuspendFibonacci(cont.n - 2, cont)!!
if (f2 === suspended) {
return f2
}
cont.data = f2
continue#loop
}
2 -> {
val f2 = cont.data as Long
return cont.f1 + f2
}
else -> throw AssertionError("Invalid continuation label ${cont.label}")
}
}
}
class MyCont(val completion: Continuation<Unit>) : Continuation<Unit> {
var label = 0
var data: Any? = null
var n: Int = 0
var f1: Long = 0
override val context: CoroutineContext get() = TODO("not implemented")
override fun resumeWithException(exception: Throwable) = TODO("not implemented")
override fun resume(value: Unit) = TODO("not implemented")
}
You have to invoke this one with
sum += fakeSuspendFibonacci(nFibonacci, InitialCont()) as Long
where InitialCont is
class InitialCont : Continuation<Unit> {
override val context: CoroutineContext get() = TODO("not implemented")
override fun resumeWithException(exception: Throwable) = TODO("not implemented")
override fun resume(value: Unit) = TODO("not implemented")
}
Basically, to compile a suspend fun the compiler has to turn its body into a state machine. Each invocation must also create an object to hold the machine's state. When you resume, the state object tells which state handler to go to. The above still isn't all there is to it, the real code is even more complex.
In intepreted mode (java -Xint), I get almost the same performance as the actual suspend fun, and it is less than twice as fast than the real one with JIT enabled. By comparison, the "direct" function implementation is about 10 times as fast. That means that the code shown explains a good part of the overhead of suspendability.
The problem lies in the Java bytecode generated from the suspend function. While a non-suspend function just generates bytecode like we'd expect it:
public static final long asyncFibonacci(int n) {
long var10000;
if (n <= -2) {
var10000 = asyncFibonacci(n + 2) - asyncFibonacci(n + 1);
} else if (n == -1) {
var10000 = 1L;
} else if (n == 0) {
var10000 = 0L;
} else if (n == 1) {
var10000 = 1L;
} else {
if (n < 2) {
throw (Throwable)(new IllegalArgumentException());
}
var10000 = asyncFibonacci(n - 1) + asyncFibonacci(n - 2);
}
return var10000;
}
When you add the suspend keyword, the decompiled Java source code is 165 lines - so a lot larger. You can view the bytecode and the decompiled Java code in IntelliJ by going to Tools -> Kotlin -> Show Kotlin bytecode (and then click Decompile on top of the page). While it's not easy to tell what exactly the Kotlin compiler is doing in the function, it looks like it's doing a whole lot of coroutine status checking - which kind of makes sense given that a coroutine can be suspended at any time.
So as a conclusion I'd say that every suspend method call is a lot more heavy than a non-suspend call. This does not only apply to recursive functions, but probably has the worst result on them.
Is it totally stupid idea to mark recursive functions with suspend?
Unless you have a very good reason to do so - Yes
I am working on the third Project Euler problem:
fn main() {
println!("{}", p3());
}
fn p3() -> u64 {
let divs = divisors(1, 600851475143, vec![]);
let mut max = 0;
for x in divs {
if prime(x, 0, false) && x > max {
max = x
}
}
max
}
fn divisors(i: u64, n: u64, div: Vec<u64>) -> Vec<u64> {
let mut temp = div;
if i * i > n {
temp
} else {
if n % i == 0 {
temp.push(i);
temp.push(n / i);
}
divisors(i + 2, n, temp)
}
}
fn prime(n: u64, i: u64, skip: bool) -> bool {
if !skip {
if n == 2 || n == 3 {
true
} else if n % 3 == 0 || n % 2 == 0 {
false
} else {
prime(n, 5, true)
}
} else {
if i * i > n {
true
} else if n % i == 0 || n % (i + 2) == 0 {
false
} else {
prime(n, i + 6, true)
}
}
}
The value 600851475143 is the value that is at some point causing it to overflow. If I replace that with any value that is in the 1010 order of magnitude or less, it returns an answer. While keeping it as a recursive solution, is there any way to either:
Increase the stack size?
Optimize my code so it doesn't return a fatal runtime: stack overflow error?
I know this can be done iteratively, but I'd prefer to not do that.
A vector containing 600 * 109 u64s means you'll need 4.8 terabytes of RAM or swapspace.
I'm sure you don't need that for this problem, you're missing some knowledge of math here: scanning till the square root of the 600851475143 will be sufficient. You may also speed up the program by using the Sieve of Eratosthenes.
Project Euler is nice to sharpen your math skills, but it doesn't help you with any programming language in particular. For learning Rust I started with Exercism.
Performing some optimizations, such as going just up to the square root of the number when checking for its factors and for whether it's a prime, I've got:
fn is_prime(n: i64) -> bool {
let float_input = n as f64;
let upper_bound = float_input.sqrt() as i64;
for x in 2..upper_bound + 1 {
if n % x == 0 {
return false;
}
}
return true;
}
fn get_factors(n: i64) -> Vec<i64> {
let mut factors: Vec<i64> = Vec::new();
let float_input = n as f64;
let upper_bound = float_input.sqrt() as i64;
for x in 1..upper_bound + 1 {
if n % x == 0 {
factors.push(x);
factors.push(n / x);
}
}
factors
}
fn get_prime_factors(n: i64) -> Vec<i64> {
get_factors(n)
.into_iter()
.filter(|&x| is_prime(x))
.collect::<Vec<i64>>()
}
fn main() {
if let Some(max) = get_prime_factors(600851475143).iter().max() {
println!("{:?}", max);
}
}
On my machine, this code runs very fast with no overflow.
./problem003 0.03s user 0.00s system 90% cpu 0.037 total
If you really don't want the iterative version:
First, make sure that you compile with optimizations (rustc -O or cargo --release). Without it there's no chance for TCO in Rust. Your divisors function is tail-recursive, but it seems that moving this Vec up and down the recursion stack is confusing enough for LLVM to miss that fact. We can help the compiler a little, by using just a reference here:
fn divisors(i: u64, n: u64, mut div: Vec<u64>) -> Vec<u64> {
divisors_(i, n, &mut div);
div
}
fn divisors_(i: u64, n: u64, div: &mut Vec<u64>) {
if i * i > n {
} else {
if n % i == 0 {
div.push(i);
div.push(n / i);
}
divisors_(i + 2, n, div)
}
}
On my machine that changes make the code no longer segfault.
If you want to increase the stack size anyway, you should run your function in a separate thread with increased stack size (using std::thread::Builder::stack_size)
Rust has reserved the become keyword for guaranteed tail recursion,
so maybe in the future you'll just need to add one keyword to your code to make it work.
I want to compute sequence of numbers like this:
n*(n-1)+n*(n-1)*(n-2)+n*(n-1)*(n-2)*(n-3)+n*(n-1)*(n-2)*(n-3)*(n-4)+...+n(n-1)...(n-n)
For example n=5 and sum equals 320.
I have a function, which compute one element:
int fac(int n, int s)
{
if (n > s)
return n*fac(n - 1, s);
return 1;
}
Recomputing the factorial for each summand is quite wasteful. Instead, I'd suggest to use memoization. If you reorder
n*(n-1) + n*(n-1)*(n-2) + n*(n-1)*(n-2)*(n-3) + n*(n-1)*(n-2)*(n-3)*...*1
you get
n*(n-1)*(n-2)*(n-3)*...*1 + n*(n-1)*(n-2)*(n-3) + n*(n-1)*(n-2) + n*(n-1)
Notice how you start with the product of 1..n, then you add the product of 1..n divided by 1, then you add the product divided by 1*2 etc.
I think a much more efficient definition of your function is (in Python):
def f(n):
p = product(range(1, n+1))
sum_ = p
for i in range(1, n-1):
p /= i
sum_ += p
return sum_
A recursive version of this definition is:
def f(n):
def go(sum_, i):
if i >= n-1:
return sum_
return sum_ + go(sum_ / i, i+1)
return go(product(range(1, n+1)), 1)
Last but not least, you can also define the function without any explicit recursion by using reduce to generate the list of summands (this is a more 'functional' -- as in functional programming -- style):
def f(n):
summands, _ = reduce(lambda (lst, p), i: (lst + [p], p / i),
range(1, n),
([], product(range(1, n+1))))
return sum(summands)
This style is very concise in functional programming languages such as Haskell; Haskell has a function call scanl which simplifies generating the summands so that the definition is just:
f n = sum $ scanl (/) (product [1..n]) [1..(n-2)]
Something like this?
function fac(int n, int s)
{
if (n >= s)
return n * fac(n - 1, s);
return 1;
}
int sum = 0;
int s = 4;
n = 5;
while(s > 0)
{
sum += fac(n, s);
s--;
}
print sum; //320
Loop-free version:
int fac(int n, int s)
{
if (n >= s)
return n * fac(n - 1, s);
return 1;
}
int compute(int n, int s, int sum = 0)
{
if(s > 0)
return compute(n, s - 1, sum + fac(n, s));
return sum;
}
print compute(5, 4); //320
Ok ther is not mutch to write. I would suggest 2 methodes if you want to solve this recursiv. (Becaus of the recrusiv faculty the complexity is a mess and runtime will increase drasticaly with big numbers!)
int func(int n){
return func(n, 2);
}
int func(int n, int i){
if (i < n){
return n*(fac(n-1,n-i)+func(n, i + 1));
}else return 0;
}
int fac(int i,int a){
if(i>a){
return i*fac(i-1, a);
}else return 1;
}
I have encountered the following problem:
N is positive non-zero integer and I have to calculate the product of : N*(N-1)^2*(N-2)^3*..*1^N.
My solution so far is as follows:
N*myFact(N-1)*fact(N-1)
The thing is I'm not allowed to use any helping functions, such as 'fact()'.
EDIT: Mathematically it can be represented as follows: N!*(N-1)! (N-2)!..*1!
This function is called the superfactorial. A recursive implementation is
long superFact(n) {
if (n < 2) return 1;
long last = superFact(n-1);
long prev = superFact(n-2);
return last * last / prev * n;
}
but this is very inefficient -- it takes about 3*F(n) recursive calls to find superFact(n), where F(n) is the n-th Fibonacci number. (The work grows exponentially.)
Try:
int myFact(int n) {
return n == 1 ? 1 : myFact(n-1)*n;
}
I assume this needs to be accomplished with 1 function i.e. you're not allowed to create a fact helper function yourself.
You can use the fact that myFact(n-1) / myFact(n-2) == (n-1)!
int myFact(int n)
{
if (n == 0 || n == 1) {
return 1
} else {
// (n - 1)!
int previousFact = myFact(n - 1) / myFact(n - 2);
return myFact(n - 1) * previousFact * n;
}
}