Rectangles

1. Readme

矩形

计算 ASCII 图中的矩形的数目,如下所示.

   +--+
  ++  |
+-++--+
|  |  |
+--+--+

上面的图表包含 6 个矩形:

+-----+
|     |
+-----+
   +--+
   |  |
   |  |
   |  |
   +--+
   +--+
   |  |
   +--+
   +--+
   |  |
   +--+
+--+
|  |
+--+
  ++
  ++

你可以假设输入总是一个适当的矩形(即每行的长度,等于第一行的长度)。

2. 开始你的表演

pub fn count(lines: &[&str]) -> u32 {
   unimplemented!("\nDetermine the count of rectangles in the ASCII diagram represented by the following lines:\n{:#?}\n.", lines);
}

3. 测试代码查看


# #![allow(unused_variables)]
#fn main() {
#[test]
fn test_zero_area_1() {
   let lines = &[];
   assert_eq!(0, count(lines))
}

#[test]
//#[ignore]
fn test_zero_area_2() {
   let lines = &[""];
   assert_eq!(0, count(lines))
}

#[test]
//#[ignore]
fn test_empty_area() {
   let lines = &[" "];
   assert_eq!(0, count(lines))
}

#[test]
//#[ignore]
fn test_one_rectangle() {
   let lines = &["+-+", "| |", "+-+"];
   assert_eq!(1, count(lines))
}

#[test]
//#[ignore]
fn test_two_rectangles_no_shared_parts() {
   let lines = &["  +-+", "  | |", "+-+-+", "| |  ", "+-+  "];
   assert_eq!(2, count(lines))
}

#[test]
//#[ignore]
fn test_five_rectangles_three_regions() {
   let lines = &["  +-+", "  | |", "+-+-+", "| | |", "+-+-+"];
   assert_eq!(5, count(lines))
}

#[test]
//#[ignore]
fn rectangle_of_height_1() {
   let lines = &["+--+", "+--+"];
   assert_eq!(1, count(lines))
}

#[test]
//#[ignore]
fn rectangle_of_width_1() {
   let lines = &["++", "||", "++"];
   assert_eq!(1, count(lines))
}

#[test]
//#[ignore]
fn unit_square() {
   let lines = &["++", "++"];
   assert_eq!(1, count(lines))
}

#[test]
//#[ignore]
fn test_incomplete_rectangles() {
   let lines = &["  +-+", "    |", "+-+-+", "| | -", "+-+-+"];
   assert_eq!(1, count(lines))
}

#[test]
//#[ignore]
fn test_complicated() {
   let lines = &[
       "+------+----+",
       "|      |    |",
       "+---+--+    |",
       "|   |       |",
       "+---+-------+",
   ];
   assert_eq!(3, count(lines))
}

#[test]
//#[ignore]
fn test_not_so_complicated() {
   let lines = &[
       "+------+----+",
       "|      |    |",
       "+------+    |",
       "|   |       |",
       "+---+-------+",
   ];
   assert_eq!(2, count(lines))
}

#[test]
//#[ignore]
fn test_large_input_with_many_rectangles() {
   let lines = &[
       "+---+--+----+",
       "|   +--+----+",
       "+---+--+    |",
       "|   +--+----+",
       "+---+--+--+-+",
       "+---+--+--+-+",
       "+------+  | |",
       "          +-+",
   ];
   assert_eq!(60, count(lines))
}

#}

4. 答案


# #![allow(unused_variables)]
#fn main() {
use std::collections::{HashMap, HashSet};

// We first look for corners that are connected by a straight uninterrupted line.
// At the same time we make a note for each corner in which direction it's connected.
//
// Later we simply scan over the corners, pairing each top-left with each right-bottom corner
// (provided it's to the right and below the top-left corner) and look if all four lines
// of the rectangle exist.
//
// To simplify scanning for lines and points we run the horizontal lines algorithm on a
// seemingly transposed version of the input and later transpose the results again.

// Note: values of these constants are used in transposition, they're not random.
const CONN_LEFT: u8 = 0x1;
const CONN_RIGHT: u8 = 0x2;
const CONN_UP: u8 = 0x4;
const CONN_DOWN: u8 = 0x8;

#[derive(PartialEq, Eq, Hash)]
struct Point {
   x: usize,
   y: usize
}

#[derive(PartialEq, Eq, Hash)]
struct Line {
   x1: usize,
   y1: usize,
   x2: usize,
   y2: usize
}

#[derive(PartialEq, Eq, Clone, Copy)]
enum Symbol {
   Corner, // '+'
   Connect, // '|' or '-' depending on direction
   Other // ' ', or anything really
}

// The input area.
struct RealArea {
   width: usize,
   height: usize,
   chars: Vec<Vec<char>>
}

trait Area {
   fn width(&self) -> usize;
   fn height(&self) -> usize;
   fn symbol_at(&self, x: usize, y: usize) -> Symbol;
}

// For horizontal scanning
impl Area for RealArea {
   fn width(&self) -> usize { self.width }
   fn height(&self) -> usize { self.height }
   fn symbol_at(&self, x: usize, y: usize) -> Symbol {
       match self.chars[y][x] {
           '+' => Symbol::Corner,
           '-' => Symbol::Connect,
           _ => Symbol::Other
       }
   }
}

struct TransposedArea<'a>(&'a RealArea);

// For vertical scanning
impl<'a> Area for TransposedArea<'a> {
   fn width(&self) -> usize { self.0.height }
   fn height(&self) -> usize { self.0.width }
   fn symbol_at(&self, x: usize, y: usize) -> Symbol {
       match self.0.chars[x][y] {
           '+' => Symbol::Corner,
           '|' => Symbol::Connect,
           _ => Symbol::Other
       }
   }
}

// Information about connections.
struct Connections {
   lines: HashSet<Line>,
   points: HashMap<Point, u8>
}

pub fn count(lines: &[&str]) -> usize {
   if lines.len() == 0 {
       return 0
   } else if lines[0].len() == 0 {
       return 0
   }
   let area = RealArea {
       width: lines[0].len(),
       height: lines.len(),
       chars: lines.iter().map(|line| line.chars().collect()).collect()
   };
   let area_transposed = TransposedArea(&area);
   let mut conns = scan_connected(&area);
   let conns_transposed = scan_connected(&area_transposed);

   // The transposed connections have their coordinate system wrong,
   // correct this.
   for l in conns_transposed.lines {
       conns.lines.insert(Line{x1: l.y1, y1: l.x1, x2: l.y2, y2: l.x2});
   }
   for (p, tcf) in conns_transposed.points {
       let cf = conns.points.entry(Point{x: p.y, y: p.x}).or_insert(0);
       *cf = *cf | (tcf << 2)
   }

   let mut total = 0;
   for (tl_p, tl_cf) in conns.points.iter() { // top left point and connection flags
       if tl_cf & (CONN_RIGHT|CONN_DOWN) == CONN_RIGHT|CONN_DOWN {
           for (br_p, br_cf) in conns.points.iter() {
               // left, right, top, bottom of potential rectangle
               let l = tl_p.x;
               let r = br_p.x;
               let t = tl_p.y;
               let b = br_p.y;
               let is_rect =
                   br_cf & (CONN_LEFT|CONN_UP) == CONN_LEFT|CONN_UP &&
                   r > l && b > t &&
                   conns.lines.contains(&Line{x1: l, y1: t, x2: l, y2: b}) &&
                   conns.lines.contains(&Line{x1: l, y1: t, x2: r, y2: t}) &&
                   conns.lines.contains(&Line{x1: l, y1: b, x2: r, y2: b}) &&
                   conns.lines.contains(&Line{x1: r, y1: t, x2: r, y2: b});
               if is_rect {
                   total += 1
               }
           }
       }
   }
   return total;
}

fn scan_connected(area: &Area) -> Connections {
   let mut conns = Connections{
       lines: HashSet::new(),
       points: HashMap::new()
   };
   let mut connected: Vec<usize> = vec![];
   for y in 0..area.height() {
       connected.clear();
       for x in 0..area.width() {
           let sym = area.symbol_at(x, y);
           if sym == Symbol::Corner {
               for prev in connected.iter() {
                   conns.lines.insert(Line{x1: prev.clone(), y1: y, x2: x, y2: y});
               }
               if let Some(last) = connected.last() {
                   let cf = conns.points.get_mut(&Point{x: last.clone(), y: y}).unwrap();
                   *cf = *cf | CONN_RIGHT;
               }
               let cf = conns.points.entry(Point{x: x, y: y}).or_insert(0);
               if connected.len() > 0 {
                   *cf = *cf | CONN_LEFT;
               }
               connected.push(x);
           } else if sym != Symbol::Connect {
               connected.clear(); // End of connected bit.
           }
       }
   }
   conns
}

#}



填充/相关