Some cleanliness cleanups
This commit is contained in:
parent
45ab4ed9e0
commit
e26b528464
@ -3,13 +3,14 @@
|
|||||||
## Highest priority:
|
## Highest priority:
|
||||||
|
|
||||||
- If my `closure_try2` branch seems to be working: start converting
|
- If my `closure_try2` branch seems to be working: start converting
|
||||||
other things and cleaning everything up. (`twist` is still ugly.)
|
other things and cleaning everything up. (`twist` is still ugly.
|
||||||
|
Look at all my TODOs in it.)
|
||||||
- See `automata_scratch/examples.py` and implement some of the tougher
|
- See `automata_scratch/examples.py` and implement some of the tougher
|
||||||
examples.
|
examples.
|
||||||
- `spiral_nested_2` & `spiral_nested_3` (how to compose
|
- `spiral_nested_2` & `spiral_nested_3` (how to compose
|
||||||
efficiently?)
|
efficiently?)
|
||||||
- `twisty_torus`
|
- `twisty_torus`
|
||||||
- `ram_horn_branch` - how do I pass depth in order to do this right?
|
- `ram_horn_branch` - Can I pass depth via a closure?
|
||||||
|
|
||||||
## Important but less critical:
|
## Important but less critical:
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,10 @@ use std::rc::Rc;
|
|||||||
use nalgebra::*;
|
use nalgebra::*;
|
||||||
//pub mod examples;
|
//pub mod examples;
|
||||||
|
|
||||||
use crate::openmesh::{OpenMesh, Tag, Mat4, Vertex, vertex, transform};
|
use crate::openmesh::{OpenMesh, Mat4, vertex, transform};
|
||||||
use crate::rule::{Rule, RuleFn, RuleEval, Child};
|
use crate::rule::{Rule, RuleFn, RuleEval, Child};
|
||||||
use crate::prim;
|
use crate::prim;
|
||||||
use crate::util;
|
use crate::util;
|
||||||
use crate::scratch;
|
|
||||||
|
|
||||||
fn cube_thing() -> Rule {
|
fn cube_thing() -> Rule {
|
||||||
|
|
||||||
@ -369,12 +368,14 @@ fn twist(f: f32, subdiv: usize) -> Rule {
|
|||||||
// TODO: Clean this code up. It was a very naive conversion from
|
// TODO: Clean this code up. It was a very naive conversion from
|
||||||
// the non-closure version.
|
// the non-closure version.
|
||||||
let xf = geometry::Rotation3::from_axis_angle(&Vector3::x_axis(), -0.7).to_homogeneous();
|
let xf = geometry::Rotation3::from_axis_angle(&Vector3::x_axis(), -0.7).to_homogeneous();
|
||||||
let seed = transform(&vec![
|
let seed = {
|
||||||
vertex(-0.5, 0.0, -0.5),
|
let s = vec![vertex(-0.5, 0.0, -0.5),
|
||||||
vertex( 0.5, 0.0, -0.5),
|
vertex( 0.5, 0.0, -0.5),
|
||||||
vertex( 0.5, 0.0, 0.5),
|
vertex( 0.5, 0.0, 0.5),
|
||||||
vertex(-0.5, 0.0, 0.5),
|
vertex(-0.5, 0.0, 0.5)];
|
||||||
], &xf);
|
util::subdivide_cycle(&transform(&s, &xf), subdiv)
|
||||||
|
};
|
||||||
|
let n = seed.len();
|
||||||
let dx0: f32 = 1.5;
|
let dx0: f32 = 1.5;
|
||||||
let dy: f32 = 0.1/f;
|
let dy: f32 = 0.1/f;
|
||||||
let ang: f32 = 0.05/f;
|
let ang: f32 = 0.05/f;
|
||||||
@ -390,23 +391,24 @@ fn twist(f: f32, subdiv: usize) -> Rule {
|
|||||||
let incr_outer = geometry::Translation3::new(-dx0*2.0, 0.0, 0.0).to_homogeneous() *
|
let incr_outer = geometry::Translation3::new(-dx0*2.0, 0.0, 0.0).to_homogeneous() *
|
||||||
geometry::Rotation3::from_axis_angle(&y, ang/2.0).to_homogeneous() *
|
geometry::Rotation3::from_axis_angle(&y, ang/2.0).to_homogeneous() *
|
||||||
geometry::Translation3::new(dx0*2.0, dy, 0.0).to_homogeneous();
|
geometry::Translation3::new(dx0*2.0, dy, 0.0).to_homogeneous();
|
||||||
|
// TODO: Cleanliness fix - transforms?
|
||||||
|
|
||||||
let seed2 = seed.clone();
|
let seed2 = seed.clone();
|
||||||
// TODO: Why do I need the above?
|
// TODO: Why do I need the above?
|
||||||
let recur = move |incr: Mat4| -> RuleFn {
|
let recur = move |incr: Mat4| -> RuleFn {
|
||||||
|
|
||||||
let seed_orig = transform(&seed2, &incr);
|
let seed_next = transform(&seed2, &incr);
|
||||||
let seed_sub = util::subdivide_cycle(&seed_orig, subdiv);
|
|
||||||
let n = seed_sub.len();
|
|
||||||
|
|
||||||
|
// TODO: Cleanliness fix - utility function to make a zigzag mesh?
|
||||||
let geom = OpenMesh {
|
let geom = OpenMesh {
|
||||||
verts: seed_sub.clone(),
|
verts: seed_next.clone(),
|
||||||
faces: util::parallel_zigzag_faces(n),
|
faces: util::parallel_zigzag_faces(n),
|
||||||
};
|
};
|
||||||
let (vc, faces) = util::connect_convex(&seed_sub, true);
|
// TODO: Cleanliness fix - why not just make these return meshes?
|
||||||
|
let (vc, faces) = util::connect_convex(&seed_next, true);
|
||||||
let final_geom = OpenMesh {
|
let final_geom = OpenMesh {
|
||||||
verts: vec![vc],
|
verts: vec![vc],
|
||||||
faces: faces.clone(),
|
faces: faces,
|
||||||
};
|
};
|
||||||
|
|
||||||
let c = move |self_: Rc<Rule>| -> RuleEval {
|
let c = move |self_: Rc<Rule>| -> RuleEval {
|
||||||
@ -427,30 +429,30 @@ fn twist(f: f32, subdiv: usize) -> Rule {
|
|||||||
};
|
};
|
||||||
Box::new(c)
|
Box::new(c)
|
||||||
};
|
};
|
||||||
|
// TODO: Can a macro do anything to clean up some of the
|
||||||
|
// repetition with HOFs & closures?
|
||||||
|
|
||||||
// TODO: so there's incr_inner & incr_outer that I wanted to
|
// TODO: so there's incr_inner & incr_outer that I wanted to
|
||||||
// parametrize over. why is it so ugly to do so?
|
// parametrize over. why is it so ugly to do so?
|
||||||
|
|
||||||
let start = move |self_: Rc<Rule>| -> RuleEval {
|
let start = move |_| -> RuleEval {
|
||||||
|
|
||||||
let xform = |dx, i, ang0, div| -> Mat4 {
|
let xform = |dx, i, ang0, div| -> Mat4 {
|
||||||
(geometry::Rotation3::from_axis_angle(&y, ang0 + (qtr / div * (i as f32))).to_homogeneous() *
|
(geometry::Rotation3::from_axis_angle(&y, ang0 + (qtr / div * (i as f32))).to_homogeneous() *
|
||||||
geometry::Translation3::new(dx, 0.0, 0.0).to_homogeneous())
|
geometry::Translation3::new(dx, 0.0, 0.0).to_homogeneous())
|
||||||
};
|
};
|
||||||
|
// TODO: Cleanliness fix - transforms?
|
||||||
|
|
||||||
let make_child = |i, incr, xform| -> (OpenMesh, Child) {
|
let make_child = |incr, xform| -> (OpenMesh, Child) {
|
||||||
|
|
||||||
let seed_orig = transform(&seed, &incr);
|
|
||||||
let seed_sub = util::subdivide_cycle(&seed_orig, subdiv);
|
|
||||||
let n = seed_sub.len();
|
|
||||||
|
|
||||||
let c = Child {
|
let c = Child {
|
||||||
rule: Rc::new(Rule { eval: (recur.clone())(incr) }),
|
rule: Rc::new(Rule { eval: (recur.clone())(incr) }),
|
||||||
|
// TODO: Cleanliness fix - can macros clean up above?
|
||||||
xf: xform,
|
xf: xform,
|
||||||
vmap: (0..(n+1)).collect(),
|
vmap: (0..(n+1)).collect(),
|
||||||
// N.B. n+1, not n. the +1 is for the centroid below
|
// N.B. n+1, not n. the +1 is for the centroid below.
|
||||||
};
|
};
|
||||||
let mut vs = transform(&seed_sub, &xform);
|
let mut vs = transform(&seed, &xform);
|
||||||
// and in the process, generate faces for these seeds:
|
// and in the process, generate faces for these seeds:
|
||||||
let (centroid, f) = util::connect_convex(&vs, false);
|
let (centroid, f) = util::connect_convex(&vs, false);
|
||||||
vs.push(centroid);
|
vs.push(centroid);
|
||||||
@ -458,8 +460,8 @@ fn twist(f: f32, subdiv: usize) -> Rule {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Generate 'count' children, shifted/rotated differently:
|
// Generate 'count' children, shifted/rotated differently:
|
||||||
let children_inner = (0..count).map(|i| make_child(i, incr_inner, xform(dx0, i, 0.0, 1.0)));
|
let children_inner = (0..count).map(|i| make_child(incr_inner, xform(dx0, i, 0.0, 1.0)));
|
||||||
let children_outer = (0..count).map(|i| make_child(i, incr_outer, xform(dx0*2.0, i, qtr/2.0, 2.0)));
|
let children_outer = (0..count).map(|i| make_child(incr_outer, xform(dx0*2.0, i, qtr/2.0, 2.0)));
|
||||||
|
|
||||||
RuleEval::from_pairs(
|
RuleEval::from_pairs(
|
||||||
children_inner.chain(children_outer), prim::empty_mesh())
|
children_inner.chain(children_outer), prim::empty_mesh())
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
pub mod scratch;
|
|
||||||
pub mod examples;
|
pub mod examples;
|
||||||
pub mod openmesh;
|
pub mod openmesh;
|
||||||
pub mod rule;
|
pub mod rule;
|
||||||
|
|||||||
297
src/scratch.rs
297
src/scratch.rs
@ -1,297 +0,0 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
/*
|
|
||||||
struct R<'a> {
|
|
||||||
b: &'a dyn Fn() -> R<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct Foo {}
|
|
||||||
impl<'a> Foo {
|
|
||||||
// These are valid, but not especially useful (if I am
|
|
||||||
// transferring ownership then I cannot have any branching):
|
|
||||||
fn fn1(self) -> R<'a> {
|
|
||||||
R { b: & move || self.fn1() }
|
|
||||||
}
|
|
||||||
fn fn2(self) -> R<'a> {
|
|
||||||
R { b: &|| self.fn2() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Below (using box instead of a trait object) follows similar rules:
|
|
||||||
struct S<'a> {
|
|
||||||
b: Box<dyn Fn() -> S<'a>>,
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct Foo2 {}
|
|
||||||
impl<'a> Foo2 {
|
|
||||||
fn fn1(self) -> S<'a> {
|
|
||||||
S { b: Box::new(move || self.fn1()) }
|
|
||||||
}
|
|
||||||
// Not valid (error[E0373]: closure may outlive the current
|
|
||||||
// function, but it borrows `self`, which is owned by the current
|
|
||||||
// function):
|
|
||||||
//fn fn2(self) -> S<'a> {
|
|
||||||
// S { b: Box::new(|| self.fn2()) }
|
|
||||||
//}
|
|
||||||
// Not valid:
|
|
||||||
//fn fn3(&self) -> S<'a> {
|
|
||||||
// S { b: Box::new(move || self.fn3()) }
|
|
||||||
//}
|
|
||||||
// Not valid:
|
|
||||||
//fn fn4(&self) -> S<'a> {
|
|
||||||
// S { b: Box::new(|| self.fn4()) }
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct T<'a> {
|
|
||||||
b: Rc<dyn Fn() -> T<'a> + 'a>,
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct Foo3 {}
|
|
||||||
impl<'a> Foo3 {
|
|
||||||
fn fn1(self) -> T<'a> {
|
|
||||||
T { b: Rc::new(move || self.fn1()) }
|
|
||||||
}
|
|
||||||
// Not valid (E0373):
|
|
||||||
//fn fn2(self) -> T<'a> {
|
|
||||||
// T { b: Rc::new(|| self.fn2()) }
|
|
||||||
//}
|
|
||||||
// Not valid:
|
|
||||||
//fn fn3(&self) -> T<'a> {
|
|
||||||
// T { b: Rc::new(move || self.fn3()) }
|
|
||||||
//}
|
|
||||||
// Not valid:
|
|
||||||
//fn fn4(&self) -> T<'a> {
|
|
||||||
// T { b: Rc::new(|| self.fn4()) }
|
|
||||||
//}
|
|
||||||
// But this is now valid because T can be cloned:
|
|
||||||
fn fn5(self) -> (T<'a>, T<'a>) {
|
|
||||||
let p = Rc::new(move || self.fn1());
|
|
||||||
let p2 = p.clone();
|
|
||||||
(T { b: p }, T { b: p2 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Further, this is now valid too (lifetimes removed):
|
|
||||||
struct U {
|
|
||||||
b: Rc<dyn Fn() -> U>,
|
|
||||||
}
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
struct Foo4 {}
|
|
||||||
impl Foo4 {
|
|
||||||
fn fn1(self) -> U {
|
|
||||||
U { b: Rc::new(move || self.fn1()) }
|
|
||||||
}
|
|
||||||
fn fn5(self) -> (U, U) {
|
|
||||||
let p = Rc::new(move || self.fn1());
|
|
||||||
let p2 = p.clone();
|
|
||||||
(U { b: p }, U { b: p2 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// I can get rid of Copy/Clone if I use FnOnce:
|
|
||||||
struct V {
|
|
||||||
b: Rc<dyn FnOnce() -> V>,
|
|
||||||
}
|
|
||||||
struct Foo5 {}
|
|
||||||
impl Foo5 {
|
|
||||||
fn fn1(self) -> V {
|
|
||||||
V { b: Rc::new(move || self.fn1()) }
|
|
||||||
}
|
|
||||||
fn fn2(self) -> (V, V) {
|
|
||||||
let p = Rc::new(move || self.fn1());
|
|
||||||
let p2 = p.clone();
|
|
||||||
(V { b: p }, V { b: p2 })
|
|
||||||
}
|
|
||||||
// and then either kind is fine:
|
|
||||||
fn fn3(self) -> V {
|
|
||||||
V { b: Rc::new(|| self.fn3()) }
|
|
||||||
}
|
|
||||||
fn fn4(self) -> (V, V) {
|
|
||||||
let p = Rc::new(|| self.fn3());
|
|
||||||
let p2 = p.clone();
|
|
||||||
(V { b: p }, V { b: p2 })
|
|
||||||
// but this confuses me a bit. doesn't this then let me call
|
|
||||||
// an FnOnce... more than once?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is valid and I can recurse:
|
|
||||||
struct W {
|
|
||||||
b: Box<dyn Fn() -> W>,
|
|
||||||
}
|
|
||||||
struct Foo6 {}
|
|
||||||
impl Foo6 {
|
|
||||||
fn fn1(s: &Rc<Self>) -> W {
|
|
||||||
let s2 = Rc::clone(&s);
|
|
||||||
W { b: Box::new(move || Self::fn1(&s2)) }
|
|
||||||
}
|
|
||||||
fn fn2(s: &Rc<Self>) -> (W, W) {
|
|
||||||
let s2 = Rc::clone(&s);
|
|
||||||
let w2 = W { b: Box::new(move || Self::fn1(&s2)) };
|
|
||||||
let s3 = Rc::clone(&s);
|
|
||||||
let w3 = W { b: Box::new(move || Self::fn1(&s3)) };
|
|
||||||
(w2, w3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foo6() {
|
|
||||||
|
|
||||||
// Whatever (note that it doesn't automatically do Copy):
|
|
||||||
struct State {
|
|
||||||
v: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purposely put state somewhere it goes out of scope:
|
|
||||||
let s = {
|
|
||||||
let s_orig = State {
|
|
||||||
v: 105,
|
|
||||||
};
|
|
||||||
Rc::new(s_orig)
|
|
||||||
};
|
|
||||||
/*
|
|
||||||
let fn1 = |f: &dyn Fn(&dyn Fn() -> W) -> (&dyn Fn() -> W)| -> (&dyn Fn() -> W) {
|
|
||||||
&(|| -> W {
|
|
||||||
let s2 = Rc::clone(&s);
|
|
||||||
W { b: Box::new(move || f(f)) }
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let f2 = fn1(fn1);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foo7(t: impl Clone) -> impl Clone {
|
|
||||||
t.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foo7b<T: Clone>(t: T) -> T {
|
|
||||||
t.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn foo7c<T>(t: T) -> T where T: Clone {
|
|
||||||
t.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A simple implementation of the Y Combinator
|
|
||||||
// λf.(λx.xx)(λx.f(xx))
|
|
||||||
// <=> λf.(λx.f(xx))(λx.f(xx))
|
|
||||||
|
|
||||||
// CREDITS: A better version of the previous code that was posted here, with detailed explanation.
|
|
||||||
// See <y> and also <y_apply>.
|
|
||||||
|
|
||||||
// A function type that takes its own type as an input is an infinite recursive type.
|
|
||||||
// We introduce a trait that will allow us to have an input with the same type as self, and break the recursion.
|
|
||||||
// The input is going to be a trait object that implements the desired function in the interface.
|
|
||||||
// NOTE: We will be coercing a reference to a closure into this trait object.
|
|
||||||
|
|
||||||
trait Apply<T, R> {
|
|
||||||
fn apply(&self, f: &dyn Apply<T, R>, t: T) -> R;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In Rust, closures fall into three kinds: FnOnce, FnMut and Fn.
|
|
||||||
// FnOnce assumed to be able to be called just once if it is not Clone. It is impossible to
|
|
||||||
// write recursive FnOnce that is not Clone.
|
|
||||||
// All FnMut are also FnOnce, although you can call them multiple times, they are not allow to
|
|
||||||
// have a reference to themselves. So it is also not possible to write recursive FnMut closures
|
|
||||||
// that is not Clone.
|
|
||||||
// All Fn are also FnMut, and all closures of Fn are also Clone. However, programmers can create
|
|
||||||
// Fn objects that are not Clone
|
|
||||||
|
|
||||||
// This will work for all Fn objects, not just closures
|
|
||||||
// And it is a little bit more efficient for Fn closures as it do not clone itself.
|
|
||||||
impl<T, R, F> Apply<T, R> for F where F:
|
|
||||||
Fn(&dyn Apply<T, R>, T) -> R
|
|
||||||
{
|
|
||||||
fn apply(&self, f: &dyn Apply<T, R>, t: T) -> R {
|
|
||||||
self(f, t)
|
|
||||||
|
|
||||||
// NOTE: Each letter is an individual symbol.
|
|
||||||
// (λx.(λy.xxy))(λx.(λy.f(λz.xxz)y))t
|
|
||||||
// => (λx.xx)(λx.f(xx))t
|
|
||||||
// => (Yf)t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This works for all closures that is Clone, and those are Fn.
|
|
||||||
// impl<T, R, F> Apply<T, R> for F where F: FnOnce( &Apply<T, R>, T ) -> R + Clone {
|
|
||||||
// fn apply( &self, f: &Apply<T, R>, t: T ) -> R {
|
|
||||||
// (self.clone())( f, t )
|
|
||||||
|
|
||||||
// // If we were to pass in self as f, we get -
|
|
||||||
// // NOTE: Each letter is an individual symbol.
|
|
||||||
// // λf.λt.sft
|
|
||||||
// // => λs.λt.sst [s/f]
|
|
||||||
// // => λs.ss
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Before 1.26 we have some limitations and so we need some workarounds. But now impl Trait is stable and we can
|
|
||||||
// write the following:
|
|
||||||
|
|
||||||
fn y<T,R>(f:impl Fn(&dyn Fn(T) -> R, T) -> R) -> impl Fn(T) -> R {
|
|
||||||
move |t| (
|
|
||||||
|x: &dyn Apply<T,R>, y| x.apply(x, y)
|
|
||||||
) (
|
|
||||||
&|x: &dyn Apply<T,R>, y| f(
|
|
||||||
&|z| x.apply(x,z),
|
|
||||||
y
|
|
||||||
),
|
|
||||||
t
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn y<T,R>(f:impl FnOnce(&Fn(T) -> R, T) -> R + Clone) -> impl FnOnce(T) -> R {
|
|
||||||
// |t| (|x: &Apply<T,R>,y| x.apply(x,y))
|
|
||||||
// (&move |x:&Apply<T,R>,y| f(&|z| x.apply(x,z), y), t)
|
|
||||||
|
|
||||||
// // NOTE: Each letter is an individual symbol.
|
|
||||||
// // (λx.(λy.xxy))(λx.(λy.f(λz.xxz)y))t
|
|
||||||
// // => (λx.xx)(λx.f(xx))t
|
|
||||||
// // => (Yf)t
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Previous version removed as they are just hacks when impl Trait is not available.
|
|
||||||
|
|
||||||
fn fac(n: usize) -> usize {
|
|
||||||
let almost_fac = |f: &dyn Fn(usize) -> usize, x|
|
|
||||||
if x == 0 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
x * f(x - 1)
|
|
||||||
}
|
|
||||||
;
|
|
||||||
let fac = y( almost_fac );
|
|
||||||
fac(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fib( n: usize ) -> usize {
|
|
||||||
let almost_fib = |f: &dyn Fn(usize) -> usize, x|
|
|
||||||
if x < 2 {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
f(x - 2) + f(x - 1)
|
|
||||||
};
|
|
||||||
let fib = y(almost_fib);
|
|
||||||
fib(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn optimal_fib( n: usize ) -> usize {
|
|
||||||
let almost_fib = |f: &dyn Fn((usize,usize,usize)) -> usize, (i0,i1,x)|
|
|
||||||
match x {
|
|
||||||
0 => i0,
|
|
||||||
1 => i1,
|
|
||||||
x => f((i1,i0+i1, x-1))
|
|
||||||
}
|
|
||||||
;
|
|
||||||
let fib = |x| y(almost_fib)((1,1,x));
|
|
||||||
fib(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_y() {
|
|
||||||
println!("{}", fac(10));
|
|
||||||
println!("{}", fib(10));
|
|
||||||
println!("{}", optimal_fib(10));
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user