From 89a9ce0cc6ecd8c1369bede6916ef9e27bcc0ee8 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Thu, 14 Nov 2013 09:11:33 +0100 Subject: [PATCH 1/3] Add shootout meteor contest benchmark. This implementation of the meteor contest implements: - insertion check with bit trick; - pregenetation of every feasible placement of the pieces on the board; - filtering of placement that implies unfeasible board - central symetry breaking --- src/test/bench/shootout-meteor.rs | 285 ++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 src/test/bench/shootout-meteor.rs diff --git a/src/test/bench/shootout-meteor.rs b/src/test/bench/shootout-meteor.rs new file mode 100644 index 0000000000000..3f90411620c5f --- /dev/null +++ b/src/test/bench/shootout-meteor.rs @@ -0,0 +1,285 @@ +// Copyright 2013 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// +// Utilities. +// + +// returns an infinite iterator of repeated applications of f to x, +// i.e. [x, f(x), f(f(x)), ...], as haskell iterate function. +fn iterate<'a, T>(f: &'a fn(&T) -> T, x: T) -> Iterate<'a, T> { + Iterate::new(f, x) +} +struct Iterate<'self, T> { + priv f: &'self fn(&T) -> T, + priv next: T +} +impl<'self, T> Iterate<'self, T> { + fn new<'a>(f: &'a fn(&T) -> T, x: T) -> Iterate<'a, T> { + Iterate {f: f, next: x} + } +} +impl<'self, T> Iterator for Iterate<'self, T> { + fn next(&mut self) -> Option { + let mut res = (self.f)(&self.next); + std::util::swap(&mut res, &mut self.next); + Some(res) + } +} + +// a linked list using borrowed next. +enum List<'self, T> { + Nil, + Cons(T, &'self List<'self, T>) +} +struct ListIterator<'self, T> { + priv cur: &'self List<'self, T> +} +impl<'self, T> List<'self, T> { + fn iter(&'self self) -> ListIterator<'self, T> { + ListIterator{cur: self} + } +} +impl<'self, T> Iterator<&'self T> for ListIterator<'self, T> { + fn next(&mut self) -> Option<&'self T> { + match *self.cur { + Nil => None, + Cons(ref elt, next) => { + self.cur = next; + Some(elt) + } + } + } +} + +// +// preprocess +// + +// Takes a pieces p on the form [(y1, x1), (y2, x2), ...] and returns +// every possible transformations (the 6 rotations with their +// corresponding mirrored piece), with, as minimum coordinates, (0, +// 0). If all is false, only generate half of the possibilities (used +// to break the symetry of the board). +fn transform(p: ~[(int, int)], all: bool) -> ~[~[(int, int)]] { + let mut res = + // rotations + iterate(|p| p.iter().map(|&(y, x)| (x + y, -y)).collect(), p) + .take(if all {6} else {3}) + // mirror + .flat_map(|p| { + iterate(|p| p.iter().map(|&(y, x)| (x, y)).collect(), p).take(2) + }).to_owned_vec(); + + // translating to (0, 0) as minimum coordinates. + for p in res.mut_iter() { + let (dy, dx) = *p.iter().min_by(|e| *e).unwrap(); + for &(ref mut y, ref mut x) in p.mut_iter() { + *y -= dy; *x -= dx; + } + } + + res +} + +// A mask is a piece somewere on the board. It is represented as a +// u64: for i in the first 50 bits, m[i] = 1 if the cell at (i/5, i%5) +// is occuped. m[50 + id] = 1 if the identifier of the piece is id. + +// Takes a piece with minimum coordinate (0, 0) (as generated by +// transform). Returns the corresponding mask if p translated by (dy, +// dx) is on the board. +fn mask(dy: int, dx: int, id: uint, p: &[(int, int)]) -> Option { + let mut m = 1 << (50 + id); + for &(y, x) in p.iter() { + let x = x + dx + (y + (dy % 2)) / 2; + if x < 0 || x > 4 {return None;} + let y = y + dy; + if y < 0 || y > 9 {return None;} + m |= 1 << (y * 5 + x); + } + Some(m) +} + +// Makes every possible masks. masks[id][i] correspond to every +// possible masks for piece with identifier id with minimum coordinate +// (i/5, i%5). +fn make_masks() -> ~[~[~[u64]]] { + let pieces = ~[ + ~[(0,0),(0,1),(0,2),(0,3),(1,3)], + ~[(0,0),(0,2),(0,3),(1,0),(1,1)], + ~[(0,0),(0,1),(0,2),(1,2),(2,1)], + ~[(0,0),(0,1),(0,2),(1,1),(2,1)], + ~[(0,0),(0,2),(1,0),(1,1),(2,1)], + ~[(0,0),(0,1),(0,2),(1,1),(1,2)], + ~[(0,0),(0,1),(1,1),(1,2),(2,1)], + ~[(0,0),(0,1),(0,2),(1,0),(1,2)], + ~[(0,0),(0,1),(0,2),(1,2),(1,3)], + ~[(0,0),(0,1),(0,2),(0,3),(1,2)]]; + let mut res = ~[]; + for (id, p) in pieces.move_iter().enumerate() { + // To break the central symetry of the problem, every + // transformation must be taken except for one piece (piece 3 + // here). + let trans = transform(p, id != 3); + let mut cur_piece = ~[]; + for dy in range(0, 10) { + for dx in range(0, 5) { + let masks = + trans.iter() + .filter_map(|t| mask(dy, dx, id, *t)) + .collect(); + cur_piece.push(masks); + } + } + res.push(cur_piece); + } + res +} + +// Check if all coordinates can be covered by an unused piece and that +// all unused piece can be placed on the board. +fn is_board_unfeasible(board: u64, masks: &[~[~[u64]]]) -> bool { + let mut coverable = board; + for i in range(0, 50).filter(|&i| board & 1 << i == 0) { + for (cur_id, pos_masks) in masks.iter().enumerate() { + if board & 1 << (50 + cur_id) != 0 {continue;} + for &cur_m in pos_masks[i].iter() { + if cur_m & board == 0 {coverable |= cur_m;} + } + } + if coverable & (1 << i) == 0 {return true;} + } + // check if every coordinates can be covered and every piece can + // be used. + coverable != (1 << 60) - 1 +} + +// Filter the masks that we can prove to result to unfeasible board. +fn filter_masks(masks: &[~[~[u64]]]) -> ~[~[~[u64]]] { + masks.iter().map( + |p| p.iter().map( + |p| p.iter() + .map(|&m| m) + .filter(|&m| !is_board_unfeasible(m, masks)) + .collect()) + .collect()) + .collect() +} + +// Gets the identifier of a mask. +fn get_id(m: u64) -> u8 { + for id in range(0, 10) { + if m & (1 << (id + 50)) != 0 {return id as u8;} + } + fail!("{:016x} does not have a valid identifier", m); +} + +// Converts a list of mask to a ~str. +fn to_utf8(raw_sol: &List) -> ~str { + let mut sol: ~[u8] = std::vec::from_elem(50, '.' as u8); + for &m in raw_sol.iter() { + let id = get_id(m); + for i in range(0, 50) { + if m & 1 << i != 0 {sol[i] = '0' as u8 + id;} + } + } + std::str::from_utf8_owned(sol) +} + +// Prints a solution in ~str form. +fn print_sol(sol: &str) { + for (i, c) in sol.iter().enumerate() { + if (i) % 5 == 0 {println("");} + if (i + 5) % 10 == 0 {print(" ");} + print!("{} ", c); + } + println(""); +} + +// The data managed during the search +struct Data { + // If more than stop_after is found, stop the search. + stop_after: int, + // Number of solution found. + nb: int, + // Lexicographically minimal solution found. + min: ~str, + // Lexicographically maximal solution found. + max: ~str +} + +// Records a new found solution. Returns false if the search must be +// stopped. +fn handle_sol(raw_sol: &List, data: &mut Data) -> bool { + // because we break the symetry, 2 solutions correspond to a call + // to this method: the normal solution, and the same solution in + // reverse order, i.e. the board rotated by half a turn. + data.nb += 2; + let sol1 = to_utf8(raw_sol); + let sol2: ~str = sol1.iter().invert().collect(); + + if data.nb == 2 { + data.min = sol1.clone(); + data.max = sol1.clone(); + } + + if sol1 < data.min {data.min = sol1.clone();} + if sol2 < data.min {data.min = sol2.clone();} + if sol1 > data.max {data.max = sol1;} + if sol2 > data.max {data.max = sol2;} + data.nb < data.stop_after +} + +// Search for every solutions. Returns false if the search was +// stopped before the end. +fn search( + masks: &[~[~[u64]]], + board: u64, + mut i: int, + cur: List, + data: &mut Data) + -> bool +{ + // Search for the lesser empty coordinate. + while board & (1 << i) != 0 && i < 50 {i += 1;} + // the board is full: a solution is found. + if i >= 50 {return handle_sol(&cur, data);} + + // for every unused piece + for id in range(0, 10).filter(|id| board & (1 << (id + 50)) == 0) { + // for each mask that fits on the board + for &m in masks[id][i].iter().filter(|&m| board & *m == 0) { + // This check is too costy. + //if is_board_unfeasible(board | m, masks) {continue;} + if !search(masks, board | m, i + 1, Cons(m, &cur), data) { + return false; + } + } + } + return true; +} + +fn main () { + let args = std::os::args(); + let stop_after = if args.len() <= 1 { + 2098 + } else { + from_str(args[1]).unwrap() + }; + let masks = make_masks(); + let masks = filter_masks(masks); + let mut data = Data {stop_after: stop_after, nb: 0, min: ~"", max: ~""}; + search(masks, 0, 0, Nil, &mut data); + println!("{} solutions found", data.nb); + print_sol(data.min); + print_sol(data.max); + println(""); +} From 74d27311a7ecb50b4783b6adc4a977a6e51759b2 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Thu, 14 Nov 2013 23:05:33 +0100 Subject: [PATCH 2/3] remove useless Iterate impl, and permute the order of the argument of iterate Writing iterate(x, f) is more logical as rust iterator pipeline is left to right. --- src/test/bench/shootout-meteor.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/test/bench/shootout-meteor.rs b/src/test/bench/shootout-meteor.rs index 3f90411620c5f..2c72e8f30f2e2 100644 --- a/src/test/bench/shootout-meteor.rs +++ b/src/test/bench/shootout-meteor.rs @@ -14,18 +14,13 @@ // returns an infinite iterator of repeated applications of f to x, // i.e. [x, f(x), f(f(x)), ...], as haskell iterate function. -fn iterate<'a, T>(f: &'a fn(&T) -> T, x: T) -> Iterate<'a, T> { - Iterate::new(f, x) +fn iterate<'a, T>(x: T, f: &'a fn(&T) -> T) -> Iterate<'a, T> { + Iterate {f: f, next: x} } struct Iterate<'self, T> { priv f: &'self fn(&T) -> T, priv next: T } -impl<'self, T> Iterate<'self, T> { - fn new<'a>(f: &'a fn(&T) -> T, x: T) -> Iterate<'a, T> { - Iterate {f: f, next: x} - } -} impl<'self, T> Iterator for Iterate<'self, T> { fn next(&mut self) -> Option { let mut res = (self.f)(&self.next); @@ -71,11 +66,11 @@ impl<'self, T> Iterator<&'self T> for ListIterator<'self, T> { fn transform(p: ~[(int, int)], all: bool) -> ~[~[(int, int)]] { let mut res = // rotations - iterate(|p| p.iter().map(|&(y, x)| (x + y, -y)).collect(), p) + iterate(p, |p| p.iter().map(|&(y, x)| (x + y, -y)).collect()) .take(if all {6} else {3}) // mirror .flat_map(|p| { - iterate(|p| p.iter().map(|&(y, x)| (x, y)).collect(), p).take(2) + iterate(p, |p| p.iter().map(|&(y, x)| (x, y)).collect()).take(2) }).to_owned_vec(); // translating to (0, 0) as minimum coordinates. From d2bcc7b621b791f4abe7000a99816209a8ea7a45 Mon Sep 17 00:00:00 2001 From: Guillaume Pinot Date: Thu, 14 Nov 2013 23:22:44 +0100 Subject: [PATCH 3/3] Improve variable naming. --- src/test/bench/shootout-meteor.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/test/bench/shootout-meteor.rs b/src/test/bench/shootout-meteor.rs index 2c72e8f30f2e2..78b31b335c93d 100644 --- a/src/test/bench/shootout-meteor.rs +++ b/src/test/bench/shootout-meteor.rs @@ -63,20 +63,21 @@ impl<'self, T> Iterator<&'self T> for ListIterator<'self, T> { // corresponding mirrored piece), with, as minimum coordinates, (0, // 0). If all is false, only generate half of the possibilities (used // to break the symetry of the board). -fn transform(p: ~[(int, int)], all: bool) -> ~[~[(int, int)]] { +fn transform(piece: ~[(int, int)], all: bool) -> ~[~[(int, int)]] { let mut res = // rotations - iterate(p, |p| p.iter().map(|&(y, x)| (x + y, -y)).collect()) + iterate(piece, |rot| rot.iter().map(|&(y, x)| (x + y, -y)).collect()) .take(if all {6} else {3}) // mirror - .flat_map(|p| { - iterate(p, |p| p.iter().map(|&(y, x)| (x, y)).collect()).take(2) + .flat_map(|cur_piece| { + iterate(cur_piece, |mir| mir.iter().map(|&(y, x)| (x, y)).collect()) + .take(2) }).to_owned_vec(); // translating to (0, 0) as minimum coordinates. - for p in res.mut_iter() { - let (dy, dx) = *p.iter().min_by(|e| *e).unwrap(); - for &(ref mut y, ref mut x) in p.mut_iter() { + for cur_piece in res.mut_iter() { + let (dy, dx) = *cur_piece.iter().min_by(|e| *e).unwrap(); + for &(ref mut y, ref mut x) in cur_piece.mut_iter() { *y -= dy; *x -= dx; } }