From d4ef30b3908fffb6925fdc19aeceb93ed5cdb619 Mon Sep 17 00:00:00 2001 From: Akshay Dixit Date: Thu, 12 May 2022 15:35:53 -0400 Subject: [PATCH] Initial commit for rust_intro --- .gitignore | 4 + Cargo.toml | 16 ++++ src/bin/http.rs | 56 ++++++++++++++ src/bin/main.rs | 22 ++++++ src/bin/mandel-threaded.rs | 42 ++++++++++ src/bin/mandel.rs | 22 ++++++ src/bin/quickreplace.rs | 84 ++++++++++++++++++++ src/gcd.rs | 33 ++++++++ src/lib.rs | 2 + src/mandel.rs | 152 +++++++++++++++++++++++++++++++++++++ 10 files changed, 433 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/bin/http.rs create mode 100644 src/bin/main.rs create mode 100644 src/bin/mandel-threaded.rs create mode 100644 src/bin/mandel.rs create mode 100644 src/bin/quickreplace.rs create mode 100644 src/gcd.rs create mode 100644 src/lib.rs create mode 100644 src/mandel.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b8a0887 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +/target +/output +/Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7fcde46 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/src/bin/http.rs b/src/bin/http.rs new file mode 100644 index 0000000..6c27a81 --- /dev/null +++ b/src/bin/http.rs @@ -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#" + GCD Calculator +
+ + + +
+ + "#, + ) +} + +fn post_gcd(form: web::Form) -> 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 {}\n", + form.n, + form.m, + gcd(form.n, form.m) + ); + + HttpResponse::Ok().content_type("text/html").body(reponse) +} diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..7e58014 --- /dev/null +++ b/src/bin/main.rs @@ -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); +} diff --git a/src/bin/mandel-threaded.rs b/src/bin/mandel-threaded.rs new file mode 100644 index 0000000..204dfe5 --- /dev/null +++ b/src/bin/mandel-threaded.rs @@ -0,0 +1,42 @@ +use rust_intro::mandel::*; +use std::env; + +fn main() { + let args: Vec = 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"); +} diff --git a/src/bin/mandel.rs b/src/bin/mandel.rs new file mode 100644 index 0000000..4527088 --- /dev/null +++ b/src/bin/mandel.rs @@ -0,0 +1,22 @@ +use rust_intro::mandel::*; +use std::env; + +fn main() { + let args: Vec = 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"); +} diff --git a/src/bin/quickreplace.rs b/src/bin/quickreplace.rs new file mode 100644 index 0000000..4c67cd8 --- /dev/null +++ b/src/bin/quickreplace.rs @@ -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 { + 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 "); +} + +fn parse_args() -> Arguments { + let args: Vec = 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); + } + }; +} diff --git a/src/gcd.rs b/src/gcd.rs new file mode 100644 index 0000000..8499759 --- /dev/null +++ b/src/gcd.rs @@ -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); + } + +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d8537fa --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod gcd; +pub mod mandel; diff --git a/src/mandel.rs b/src/mandel.rs new file mode 100644 index 0000000..cc1edb8 --- /dev/null +++ b/src/mandel.rs @@ -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, limit: usize) -> Option { + 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 , where is the +/// character given by the `separator` argument, and and 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(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::("", ','), None); + assert_eq!(parse_pair::("10,", ','), None); + assert_eq!(parse_pair::(",10", ','), None); + assert_eq!(parse_pair::("10,20", ','), Some((10, 20))); + assert_eq!(parse_pair::("10,20xy", ','), None); + assert_eq!(parse_pair::("0.5x", 'x'), None); + assert_eq!(parse_pair::("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> { + 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, + lower_right: Complex, +) -> Complex { + 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, + lower_right: Complex, +) { + 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(()) +}