commit
d4ef30b390
10 changed files with 433 additions and 0 deletions
@ -0,0 +1,16 @@ |
|||||||
|
[package] |
||||||
|
name = "rust_intro" |
||||||
|
version = "0.1.0" |
||||||
|
edition = "2021" |
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||||
|
|
||||||
|
[dependencies] |
||||||
|
actix-web = "1.0.8" |
||||||
|
serde = { version = "1.0", features = ["derive"] } |
||||||
|
num = "0.4" |
||||||
|
image = "0.13.0" |
||||||
|
crossbeam = "0.8" |
||||||
|
text-colorizer = "1" |
||||||
|
regex = "1" |
||||||
|
num_cpus = "1.13.1" |
||||||
@ -0,0 +1,56 @@ |
|||||||
|
use actix_web::{web, App, HttpResponse, HttpServer}; |
||||||
|
use rust_intro::gcd::gcd; |
||||||
|
use serde::Deserialize; |
||||||
|
|
||||||
|
#[derive(Deserialize)] |
||||||
|
struct GcdParameters { |
||||||
|
n: u64, |
||||||
|
m: u64, |
||||||
|
} |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let server = HttpServer::new(|| { |
||||||
|
App::new() |
||||||
|
.route("/", web::get().to(get_index)) |
||||||
|
.route("/gcd", web::post().to(post_gcd)) |
||||||
|
}); |
||||||
|
|
||||||
|
println!("Serving on http://localhost:8123"); |
||||||
|
server |
||||||
|
.bind("127.0.0.1:8123") |
||||||
|
.expect("error binding server to address") |
||||||
|
.run() |
||||||
|
.expect("error running server"); |
||||||
|
} |
||||||
|
|
||||||
|
fn get_index() -> HttpResponse { |
||||||
|
HttpResponse::Ok().content_type("text/html").body( |
||||||
|
r#" |
||||||
|
<title>GCD Calculator</title> |
||||||
|
<form action="/gcd" method="post"> |
||||||
|
<input type="text" name="n"/> |
||||||
|
<input type="text" name="m"/> |
||||||
|
<button type="submit">Compute GCD</button> |
||||||
|
</form> |
||||||
|
|
||||||
|
"#, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
fn post_gcd(form: web::Form<GcdParameters>) -> HttpResponse { |
||||||
|
if form.n == 0 || form.m == 0 { |
||||||
|
return HttpResponse::BadRequest() |
||||||
|
.content_type("text/html") |
||||||
|
.body("Computing GCD with zero is boring"); |
||||||
|
} |
||||||
|
|
||||||
|
let reponse = format!( |
||||||
|
"The greatest common divisor of the numbers {} and {} \ |
||||||
|
is <b>{}</b>\n", |
||||||
|
form.n, |
||||||
|
form.m, |
||||||
|
gcd(form.n, form.m) |
||||||
|
); |
||||||
|
|
||||||
|
HttpResponse::Ok().content_type("text/html").body(reponse) |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
use rust_intro::gcd::gcd; |
||||||
|
use std::env; |
||||||
|
use std::str::FromStr; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let mut numbers = Vec::new(); |
||||||
|
for arg in env::args().skip(1) { |
||||||
|
numbers.push(u64::from_str(&arg).expect("error parsing argument")); |
||||||
|
} |
||||||
|
|
||||||
|
if numbers.len() == 0 { |
||||||
|
eprintln!("Usage: gcd NUMBER ..."); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
let mut d = numbers[0]; |
||||||
|
for m in &numbers[1..] { |
||||||
|
d = gcd(d, *m); |
||||||
|
} |
||||||
|
|
||||||
|
println!("The greatest common divisor of {:?} is {}", numbers, d); |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
use rust_intro::mandel::*; |
||||||
|
use std::env; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let args: Vec<String> = env::args().collect(); |
||||||
|
if args.len() != 5 { |
||||||
|
eprintln!("Usage: {} FILE PIXELS UPPERLEFT LOWERRIGHT", args[0]); |
||||||
|
eprintln!( |
||||||
|
"Example: {} mandel.png 1000x750 -1.20,0.35 -1,0.20", |
||||||
|
args[0] |
||||||
|
); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
let bounds = parse_pair(&args[2], 'x').expect("error parsing image dimensions"); |
||||||
|
let upper_left = parse_complex(&args[3]).expect("error parsing upper left corner point"); |
||||||
|
let lower_right = parse_complex(&args[4]).expect("error parsing lower right corner point"); |
||||||
|
|
||||||
|
let mut pixels = vec![0; bounds.0 * bounds.1]; |
||||||
|
let threads = num_cpus::get(); |
||||||
|
let rows_per_band = bounds.1 / threads + 1; |
||||||
|
|
||||||
|
{ |
||||||
|
let bands: Vec<&mut [u8]> = pixels.chunks_mut(rows_per_band * bounds.0).collect(); |
||||||
|
crossbeam::scope(|spawner| { |
||||||
|
for (i, band) in bands.into_iter().enumerate() { |
||||||
|
let top = rows_per_band * i; |
||||||
|
let height = band.len() / bounds.0; |
||||||
|
let band_bounds = (bounds.0, height); |
||||||
|
let band_upper_left = pixel_to_point(bounds, (0, top), upper_left, lower_right); |
||||||
|
let band_lower_right = |
||||||
|
pixel_to_point(bounds, (bounds.0, top + height), upper_left, lower_right); |
||||||
|
spawner.spawn(move |_| { |
||||||
|
render(band, band_bounds, band_upper_left, band_lower_right); |
||||||
|
}); |
||||||
|
} |
||||||
|
}) |
||||||
|
.unwrap(); |
||||||
|
} |
||||||
|
|
||||||
|
write_image(&args[1], &pixels, bounds).expect("error writing PNG file"); |
||||||
|
} |
||||||
@ -0,0 +1,22 @@ |
|||||||
|
use rust_intro::mandel::*; |
||||||
|
use std::env; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let args: Vec<String> = env::args().collect(); |
||||||
|
if args.len() != 5 { |
||||||
|
eprintln!("Usage: {} FILE PIXELS UPPERLEFT LOWERRIGHT", args[0]); |
||||||
|
eprintln!( |
||||||
|
"Example: {} mandel.png 1000x750 -1.20,0.35 -1,0.20", |
||||||
|
args[0] |
||||||
|
); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
let bounds = parse_pair(&args[2], 'x').expect("error parsing image dimensions"); |
||||||
|
let upper_left = parse_complex(&args[3]).expect("error parsing upper left corner point"); |
||||||
|
let lower_right = parse_complex(&args[4]).expect("error parsing lower right corner point"); |
||||||
|
|
||||||
|
let mut pixels = vec![0; bounds.0 * bounds.1]; |
||||||
|
render(&mut pixels, bounds, upper_left, lower_right); |
||||||
|
write_image(&args[1], &pixels, bounds).expect("error writing PNG file"); |
||||||
|
} |
||||||
@ -0,0 +1,84 @@ |
|||||||
|
use regex::Regex; |
||||||
|
use std::env; |
||||||
|
use std::fs; |
||||||
|
use text_colorizer::*; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
struct Arguments { |
||||||
|
target: String, |
||||||
|
replacement: String, |
||||||
|
filename: String, |
||||||
|
output: String, |
||||||
|
} |
||||||
|
|
||||||
|
fn replace(target: &str, replacement: &str, text: &str) -> Result<String, regex::Error> { |
||||||
|
let regex = Regex::new(target)?; |
||||||
|
Ok(regex.replace_all(text, replacement).to_string()) |
||||||
|
} |
||||||
|
|
||||||
|
fn print_usage() { |
||||||
|
eprintln!( |
||||||
|
"{} - change occurrences of one string to another", |
||||||
|
"quickreplace".green() |
||||||
|
); |
||||||
|
eprintln!("Usage: quickreplace <target> <replacement> <INPUT> <OUTPUT>"); |
||||||
|
} |
||||||
|
|
||||||
|
fn parse_args() -> Arguments { |
||||||
|
let args: Vec<String> = env::args().skip(1).collect(); |
||||||
|
|
||||||
|
if args.len() != 4 { |
||||||
|
print_usage(); |
||||||
|
eprintln!( |
||||||
|
"{} wrong number of arguments: expected 4, got {}.", |
||||||
|
"Error:".red().bold(), |
||||||
|
args.len() |
||||||
|
); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
Arguments { |
||||||
|
target: args[0].clone(), |
||||||
|
replacement: args[1].clone(), |
||||||
|
filename: args[2].clone(), |
||||||
|
output: args[3].clone(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let args = parse_args(); |
||||||
|
//println!("{:?}", args);
|
||||||
|
|
||||||
|
let data = match fs::read_to_string(&args.filename) { |
||||||
|
Ok(v) => v, |
||||||
|
Err(e) => { |
||||||
|
eprintln!( |
||||||
|
"{} failed to read from file '{}': {:?}", |
||||||
|
"Error".red().bold(), |
||||||
|
args.filename, |
||||||
|
e |
||||||
|
); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let replaced_data = match replace(&args.target, &args.replacement, &data) { |
||||||
|
Ok(v) => v, |
||||||
|
Err(e) => { |
||||||
|
eprintln!("{} failed to replace text: {:?}", "Error".red().bold(), e); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
match fs::write(&args.output, replaced_data) { |
||||||
|
Ok(_) => {} |
||||||
|
Err(e) => { |
||||||
|
eprintln!( |
||||||
|
"{} failed to write to file '{}': {:?}", |
||||||
|
"Error".red().bold(), |
||||||
|
args.output, |
||||||
|
e |
||||||
|
); |
||||||
|
std::process::exit(1); |
||||||
|
} |
||||||
|
}; |
||||||
|
} |
||||||
@ -0,0 +1,33 @@ |
|||||||
|
pub fn gcd(mut n: u64, mut m: u64) -> u64 { |
||||||
|
assert!(n != 0 && m != 0); |
||||||
|
while m != 0 { |
||||||
|
if m < n { |
||||||
|
let t = m; |
||||||
|
m = n; |
||||||
|
n = t; |
||||||
|
} |
||||||
|
m = m % n; |
||||||
|
} |
||||||
|
n |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
#[test] |
||||||
|
fn it_works() { |
||||||
|
let result = 2 + 2; |
||||||
|
assert_eq!(result, 4); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn basic_gcd() { |
||||||
|
let result = gcd(100, 30); |
||||||
|
assert_eq!(result, 10); |
||||||
|
|
||||||
|
assert_eq!(gcd(14, 15),1); |
||||||
|
|
||||||
|
assert_eq!(gcd(2*3*5*11*17, 3*7*11*13*19), 3*11); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,152 @@ |
|||||||
|
use image::png::PNGEncoder; |
||||||
|
use image::ColorType; |
||||||
|
use num::Complex; |
||||||
|
use std::fs::File; |
||||||
|
use std::str::FromStr; |
||||||
|
|
||||||
|
/// Try to determine if `c` is in the Mandelbrot set, using at most `limit`
|
||||||
|
/// iterations to decide.
|
||||||
|
///
|
||||||
|
/// If 'c' is not a member, return `Some(i)`, where `i` is the number of iterations
|
||||||
|
/// it took for `c` to leave the circle of radius 2 centered on the origin. If `c`
|
||||||
|
/// seems to be a member (more precisely, if we reached the iteration limit without being
|
||||||
|
/// able to prove the `c` is not a member), return `None`.
|
||||||
|
pub fn escape_time(c: Complex<f64>, limit: usize) -> Option<usize> { |
||||||
|
let mut z = Complex { re: 0.0, im: 0.0 }; |
||||||
|
for i in 0..limit { |
||||||
|
if z.norm_sqr() > 4.0 { |
||||||
|
return Some(i); |
||||||
|
} |
||||||
|
z = z * z + c; |
||||||
|
} |
||||||
|
None |
||||||
|
} |
||||||
|
|
||||||
|
/// Parse the string `s` as a coordinate pair, like `"400x600"` or `"1.0,0.5"`.
|
||||||
|
///
|
||||||
|
/// Specifically, `s` should have the form <left><sep><right>, where <sep> is the
|
||||||
|
/// character given by the `separator` argument, and <left> and <right> are both
|
||||||
|
/// strings and can be parsed by `T::from_str`. `separator` must be an ASCII
|
||||||
|
/// character.
|
||||||
|
///
|
||||||
|
/// If `s` has the proper form, return `Some<(x,y)>`. If it doesn't parse correctly,
|
||||||
|
/// return `None`.
|
||||||
|
pub fn parse_pair<T: FromStr>(s: &str, separator: char) -> Option<(T, T)> { |
||||||
|
match s.find(separator) { |
||||||
|
None => None, |
||||||
|
Some(index) => match (T::from_str(&s[..index]), T::from_str(&s[index + 1..])) { |
||||||
|
(Ok(l), Ok(r)) => Some((l, r)), |
||||||
|
_ => None, |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_parse_pair() { |
||||||
|
assert_eq!(parse_pair::<i32>("", ','), None); |
||||||
|
assert_eq!(parse_pair::<i32>("10,", ','), None); |
||||||
|
assert_eq!(parse_pair::<i32>(",10", ','), None); |
||||||
|
assert_eq!(parse_pair::<i32>("10,20", ','), Some((10, 20))); |
||||||
|
assert_eq!(parse_pair::<i32>("10,20xy", ','), None); |
||||||
|
assert_eq!(parse_pair::<f64>("0.5x", 'x'), None); |
||||||
|
assert_eq!(parse_pair::<f64>("0.5x1.5", 'x'), Some((0.5, 1.5))); |
||||||
|
} |
||||||
|
|
||||||
|
/// Parse a pair of floating point numbers separated by a comma as a complex number
|
||||||
|
pub fn parse_complex(s: &str) -> Option<Complex<f64>> { |
||||||
|
match parse_pair(s, ',') { |
||||||
|
Some((re, im)) => Some(Complex { re, im }), |
||||||
|
None => None, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_parse_complex() { |
||||||
|
assert_eq!(parse_complex("3.0,4.0"), Some(Complex::new(3.0, 4.0))); |
||||||
|
assert_eq!( |
||||||
|
parse_complex("1.25,-0.0625"), |
||||||
|
Some(Complex { |
||||||
|
re: 1.25, |
||||||
|
im: -0.0625 |
||||||
|
}) |
||||||
|
); |
||||||
|
assert_eq!(parse_complex(",-0.0625"), None); |
||||||
|
} |
||||||
|
|
||||||
|
/// Given the row and column of a pixel in the output image, return the corresponding point
|
||||||
|
/// on the complex plane.
|
||||||
|
///
|
||||||
|
/// `bounds` is a pair giving the width and the height of the image in pixels.
|
||||||
|
/// `pixel` is a (column, row) pair indicating a particular pixel in that image.
|
||||||
|
/// The `upper_left` and `lower_right` parameters are points on the complex plane
|
||||||
|
/// designating the area our image covers.
|
||||||
|
pub fn pixel_to_point( |
||||||
|
bounds: (usize, usize), |
||||||
|
pixel: (usize, usize), |
||||||
|
upper_left: Complex<f64>, |
||||||
|
lower_right: Complex<f64>, |
||||||
|
) -> Complex<f64> { |
||||||
|
let (width, height) = ( |
||||||
|
lower_right.re - upper_left.re, |
||||||
|
upper_left.im - lower_right.im, |
||||||
|
); |
||||||
|
Complex { |
||||||
|
re: upper_left.re + pixel.0 as f64 * width / bounds.0 as f64, |
||||||
|
im: upper_left.im - pixel.1 as f64 * height / bounds.1 as f64, // Why subtraction here ? pixel.1 increases as we go down,
|
||||||
|
// but the imaginary component increases as we go up.
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn test_pixel_to_point() { |
||||||
|
assert_eq!( |
||||||
|
pixel_to_point( |
||||||
|
(100, 200), |
||||||
|
(25, 175), |
||||||
|
Complex { re: -1.0, im: 1.0 }, |
||||||
|
Complex { re: 1.0, im: -1.0 } |
||||||
|
), |
||||||
|
Complex { |
||||||
|
re: -0.5, |
||||||
|
im: -0.75 |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
/// Render a rectangle of the Mandelbrot set into a buffer of pixels.
|
||||||
|
///
|
||||||
|
/// The `bounds` argument gives the width and height of the buffer `pixels`,
|
||||||
|
/// which holds one grayscale pixel per byte. The `upper_left` and `lower_right`
|
||||||
|
/// arguments specify points on the complex plane corresponding to the upper-left
|
||||||
|
/// and lower-right corners of the pixel buffer.
|
||||||
|
pub fn render( |
||||||
|
pixels: &mut [u8], |
||||||
|
bounds: (usize, usize), |
||||||
|
upper_left: Complex<f64>, |
||||||
|
lower_right: Complex<f64>, |
||||||
|
) { |
||||||
|
assert!(pixels.len() == bounds.0 * bounds.1); |
||||||
|
|
||||||
|
for row in 0..bounds.1 { |
||||||
|
for column in 0..bounds.0 { |
||||||
|
let point = pixel_to_point(bounds, (column, row), upper_left, lower_right); |
||||||
|
pixels[row * bounds.0 + column] = match escape_time(point, 255) { |
||||||
|
None => 0, |
||||||
|
Some(count) => 255 - count as u8, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Write the buffer `pixels`, whose dimensions are given by `bounds`, to the
|
||||||
|
/// file named `filename`.
|
||||||
|
pub fn write_image( |
||||||
|
filename: &str, |
||||||
|
pixels: &[u8], |
||||||
|
bounds: (usize, usize), |
||||||
|
) -> Result<(), std::io::Error> { |
||||||
|
let output = File::create(filename)?; |
||||||
|
let encoder = PNGEncoder::new(output); |
||||||
|
encoder.encode(pixels, bounds.0 as u32, bounds.1 as u32, ColorType::Gray(8))?; |
||||||
|
Ok(()) |
||||||
|
} |
||||||
Loading…
Reference in new issue