add test for factor

Add a test for `factor`.

This commit also pulls factor's Sieve implementation into its own module
so that the factor test can use it.

Finally, slight refactoring for clarity in gen_table.rs.
This commit is contained in:
kwantam 2015-04-30 14:23:12 -04:00
parent cab4f8d570
commit 9a806346a9
5 changed files with 670 additions and 572 deletions

View file

@ -161,6 +161,7 @@ TEST_PROGS := \
cat \
cp \
env \
factor \
mkdir \
mv \
nl \

View file

@ -13,8 +13,8 @@
//! 2 has no multiplicative inverse mode 2^64 because 2 | 2^64,
//! and in any case divisibility by two is trivial by checking the LSB.
use sieve::Sieve;
use std::env::args;
use std::iter::repeat;
use std::num::Wrapping;
use std::u64::MAX as MAX_U64;
@ -24,48 +24,7 @@ use numeric::is_prime;
#[cfg(test)]
mod numeric;
// A lazy Sieve of Eratosthenes
// Not particularly efficient, but fine for generating a few thousand primes.
struct Sieve {
inner: Box<Iterator<Item=u64>>,
filts: Vec<u64>,
}
impl Iterator for Sieve {
type Item = u64;
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
#[inline]
fn next(&mut self) -> Option<u64> {
while let Some(n) = self.inner.next() {
if self.filts.iter().all(|&x| n % x != 0) {
self.filts.push(n);
return Some(n);
}
}
None
}
}
impl Sieve {
#[inline]
pub fn new() -> Sieve {
fn next(s: &mut u64, t: u64) -> Option<u64> {
let ret = Some(*s);
*s = *s + t;
ret
}
let next = next;
let odds_by_3 = Box::new(repeat(2).scan(3, next)) as Box<Iterator<Item=u64>>;
Sieve { inner: odds_by_3, filts: Vec::new() }
}
}
mod sieve;
// extended Euclid algorithm
// precondition: a does not divide 2^64
@ -105,16 +64,20 @@ fn inv_mod_u64(a: u64) -> Option<u64> {
#[cfg_attr(test, allow(dead_code))]
fn main() {
// By default, we print the multiplicative inverses mod 2^64 of the first 10k primes
let n = args().skip(1).next().unwrap_or("10000".to_string()).parse::<usize>().ok().unwrap_or(10000);
// By default, we print the multiplicative inverses mod 2^64 of the first 1k primes
let n = args().skip(1).next().unwrap_or("1000".to_string()).parse::<usize>().ok().unwrap_or(1000);
print!("{}", PREAMBLE);
let mut cols = 3;
let m = n;
Sieve::new()
.scan((0, 3), move |st, x| {
let (count, mut cols) = *st;
if count < m {
// we want a total of n + 1 values
let mut primes = Sieve::new().take(n + 1);
// in each iteration of the for loop, we use the value yielded
// by the previous iteration. This leaves one value left at the
// end, which we call NEXT_PRIME.
let mut x = primes.next().unwrap();
for next in primes {
// format the table
let outstr = format!("({}, {}, {}),", x, inv_mod_u64(x).unwrap(), MAX_U64 / x);
if cols + outstr.len() > MAX_WIDTH {
@ -125,18 +88,10 @@ fn main() {
cols += 1 + outstr.len();
}
*st = (count + 1, cols);
Some(1)
} else if count == m {
// now we're done formatting the table, print NEXT_PRIME
print!("\n];\n\npub const NEXT_PRIME: u64 = {};\n", x);
*st = (count + 1, cols);
Some(1)
} else {
None
x = next;
}
}).take(m + 1).count();
print!("\n];\n\npub const NEXT_PRIME: u64 = {};\n", x);
}
#[test]
@ -150,7 +105,7 @@ fn test_generator_and_inverter() {
}));
}
const MAX_WIDTH: usize = 100;
const MAX_WIDTH: usize = 102;
const PREAMBLE: &'static str =
r##"/*
* This file is part of the uutils coreutils package.

53
src/factor/sieve.rs Normal file
View file

@ -0,0 +1,53 @@
/*
* This file is part of the uutils coreutils package.
*
* (c) kwantam <kwantam@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
use std::iter::repeat;
// A lazy Sieve of Eratosthenes
// Not particularly efficient, but fine for generating a few thousand primes.
pub struct Sieve {
inner: Box<Iterator<Item=u64>>,
filts: Vec<u64>,
}
impl Iterator for Sieve {
type Item = u64;
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
#[inline]
fn next(&mut self) -> Option<u64> {
while let Some(n) = self.inner.next() {
if self.filts.iter().all(|&x| n % x != 0) {
self.filts.push(n);
return Some(n);
}
}
None
}
}
impl Sieve {
#[inline]
pub fn new() -> Sieve {
fn next(s: &mut u64, t: u64) -> Option<u64> {
let ret = Some(*s);
*s = *s + t;
ret
}
let next = next;
let odds_by_3 = Box::new(repeat(2).scan(3, next)) as Box<Iterator<Item=u64>>;
Sieve { inner: odds_by_3, filts: Vec::new() }
}
}

89
test/factor.rs Normal file
View file

@ -0,0 +1,89 @@
/*
* This file is part of the uutils coreutils package.
*
* (c) kwantam <kwantam@gmail.com>
*
* For the full copyright and license information, please view the LICENSE file
* that was distributed with this source code.
*/
extern crate libc;
extern crate rand;
use rand::{weak_rng, Rng};
use sieve::Sieve;
use std::io::Write;
use std::process::{Command, Stdio};
#[path="../src/factor/sieve.rs"]
mod sieve;
const NUM_PRIMES: usize = 10000;
const LOG_PRIMES: f64 = 14.0; // ceil(log2(NUM_PRIMES))
const NUM_TESTS: usize = 1000;
const PROGNAME: &'static str = "./factor";
#[test]
fn test_random() {
let mut primes = Sieve::new().take(NUM_PRIMES - 1).collect::<Vec<u64>>();
primes.push(2);
let primes = primes;
let mut rng = weak_rng();
let mut rand_gt = move |min: u64| {
let mut product = 1u64;
let mut factors = Vec::new();
while product < min {
// log distribution---higher probability for lower numbers
let mut factor;
loop {
let next = rng.gen_range(0f64, LOG_PRIMES).exp2().floor() as usize;
if next < NUM_PRIMES {
factor = primes[next];
break;
}
}
let factor = factor;
match product.checked_mul(factor) {
Some(p) => {
product = p;
factors.push(factor);
},
None => break,
};
}
factors.sort();
(product, factors)
};
// build an input and expected output string from factor
let mut instring = String::new();
let mut outstring = String::new();
for _ in 0..NUM_TESTS {
let (product, factors) = rand_gt(1 << 63);
instring.push_str(&(format!("{} ", product))[..]);
outstring.push_str(&(format!("{}:", product))[..]);
for factor in factors.iter() {
outstring.push_str(&(format!(" {}", factor))[..]);
}
outstring.push_str("\n");
}
// now run factor
let mut process = Command::new(PROGNAME)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else(|e| panic!("{}", e));
process.stdin.take().unwrap_or_else(|| panic!("Could not take child process stdin"))
.write_all(instring.as_bytes()).unwrap_or_else(|e| panic!("{}", e));
let output = process.wait_with_output().unwrap_or_else(|e| panic!("{}", e));
assert_eq!(&output.stdout[..], outstring.as_bytes());
}