From 40638bdcc95e67ea1a9532510237224d38b42999 Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sat, 19 Jul 2014 21:50:11 +0900 Subject: [PATCH 01/10] Add tsort Signed-off-by: Akira Hayakawa --- Cargo.toml | 4 ++ Makefile | 1 + tsort/tsort.rs | 187 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 tsort/tsort.rs diff --git a/Cargo.toml b/Cargo.toml index 85254dd8205..76d3eb79f12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -203,6 +203,10 @@ path = "true/true.rs" name = "truncate" path = "truncate/truncate.rs" +[[bin]] +name = "tsort" +path = "tsort/tsort.rs" + [[bin]] name = "tty" path = "tty/tty.rs" diff --git a/Makefile b/Makefile index 439b13765ef..97996c6fa9b 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ PROGS := \ tr \ true \ truncate \ + tsort \ unlink \ uniq \ wc \ diff --git a/tsort/tsort.rs b/tsort/tsort.rs new file mode 100644 index 00000000000..1a514a9174e --- /dev/null +++ b/tsort/tsort.rs @@ -0,0 +1,187 @@ +#![crate_name = "tsort"] + +/* + * This file is part of the uutils coreutils package. + * + * (c) Ben Eggers + * (c) Akira Hayakawa + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#![feature(macro_rules)] + +extern crate getopts; +extern crate libc; + +use std::io; +use std::collections::{HashSet, HashMap}; + +#[path = "../common/util.rs"] +mod util; + +static NAME: &'static str = "tsort"; +static VERSION: &'static str = "1.0.0"; + +pub fn uumain(args: Vec) -> int { + let opts = [ + getopts::optflag("h", "help", "display this help and exit"), + getopts::optflag("V", "version", "output version information and exit"), + ]; + + let matches = match getopts::getopts(args.tail(), opts) { + Ok(m) => m, + Err(f) => crash!(1, "{}", f) + }; + + if matches.opt_present("h") { + println!("{} v{}", NAME, VERSION); + println!(""); + println!("Usage:"); + println!(" {} [OPTIONS] FILE", NAME); + println!(""); + io::print(getopts::usage("Topological sort the strings in FILE. Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). If FILE is not passed in, stdin is used instead.", opts).as_slice()); + return 0; + } + + if matches.opt_present("V") { + println!("{} v{}", NAME, VERSION); + return 0; + } + + let files = matches.free.clone(); + let input = if files.len() > 1 { + crash!(1, "{}, extra operand '{}'", NAME, matches.free[1]); + } else if files.is_empty() { + "-".to_string() + } else { + files[0].to_string() + }; + + let mut reader = io::BufferedReader::new( + if input.as_slice() == "-" { + box io::stdio::stdin_raw() as Box + } else { + box match io::File::open(&Path::new(input.clone())) { + Ok(a) => a, + _ => crash!(1, "{}: No such file or directory", input) + } as Box + } + ); + + let mut g = Graph::new(); + loop { + match reader.read_line() { + Ok(line) => { + let ab: Vec<&str> = line.as_slice().trim_right_chars('\n').split(' ').collect(); + if ab.len() > 2 { + crash!(1, "{}: input contains an odd number of tokens", input); + } + g.add_edge(&ab[0].to_string(), &ab[1].to_string()); + }, + _ => break + } + } + + g.run_tsort(); + + if !g.is_acyclic() { + crash!(1, "{}, input contains a loop:", input); + } + + let mut writer = io::BufferedWriter::new(box io::stdio::stdout_raw() as Box); + for x in g.result.iter() { + crash_if_err!(1, writer.write_line(x.as_slice())); + } + + return 0 +} + +// We use String as a representatio of node here +// but using integer may improve performance. +struct Graph { + in_edges: HashMap>, + out_edges: HashMap>, + result: Vec +} + +impl Graph { + fn new() -> Graph { + Graph { + in_edges: HashMap::new(), + out_edges: HashMap::new(), + result: vec!(), + } + } + + fn has_node(&self, n: &String) -> bool { + self.in_edges.contains_key(n) + } + + fn has_edge(&self, from: &String, to: &String) -> bool { + self.in_edges.find(to).unwrap().contains(from) + } + + fn init_node(&mut self, n: &String) { + self.in_edges.insert(n.clone(), HashSet::new()); + self.out_edges.insert(n.clone(), vec!()); + } + + fn add_edge(&mut self, from: &String, to: &String) { + if !self.has_node(to) { + self.init_node(to); + } + + if !self.has_node(from) { + self.init_node(from); + } + + if !self.has_edge(from, to) { + self.in_edges.find_mut(to).unwrap().insert(from.clone()); + self.out_edges.find_mut(from).unwrap().push(to.clone()); + } + } + + // Kahn's algorithm + // O(|V|+|E|) + fn run_tsort(&mut self) { + let mut start_nodes = vec!(); + for (n, edges) in self.in_edges.iter() { + if edges.is_empty() { + start_nodes.push(n.clone()); + } + } + + while !start_nodes.is_empty() { + let n = start_nodes[0].clone(); + start_nodes.remove(0); + + self.result.push(n.clone()); + + let n_out_edges = self.out_edges.find_mut(&n).unwrap(); + while !n_out_edges.is_empty() { + // n -> m + let m = (*n_out_edges)[0].clone(); + n_out_edges.remove(0); + + let m_in_edges = self.in_edges.find_mut(&m).unwrap(); + m_in_edges.remove(&n); + + // If m doesn't have other in-coming edges add it to start_nodes + if m_in_edges.is_empty() { + start_nodes.push(m); + } + } + } + } + + fn is_acyclic(&self) -> bool { + for (_, edges) in self.out_edges.iter() { + if !edges.is_empty() { + return false + } + } + true + } +} From 0e57ea5150bfbca5a44a3958cd1000ed60825ae4 Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 11:18:08 +0900 Subject: [PATCH 02/10] fix (pop start_nodes) Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index 1a514a9174e..8b64af27b4d 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -154,8 +154,7 @@ impl Graph { } while !start_nodes.is_empty() { - let n = start_nodes[0].clone(); - start_nodes.remove(0); + let n = start_nodes.shift().unwrap(); self.result.push(n.clone()); From 2982a58b36502b8311850fd871644a62e017425b Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 11:49:33 +0900 Subject: [PATCH 03/10] fix (use println! instead of explicit bufferedwriter) Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index 8b64af27b4d..a3572b87082 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -90,9 +90,8 @@ pub fn uumain(args: Vec) -> int { crash!(1, "{}, input contains a loop:", input); } - let mut writer = io::BufferedWriter::new(box io::stdio::stdout_raw() as Box); for x in g.result.iter() { - crash_if_err!(1, writer.write_line(x.as_slice())); + println!("{}", x); } return 0 From 79f57be53712224cbe333fee30401b5cec20dcd5 Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 11:52:40 +0900 Subject: [PATCH 04/10] fix spacing Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index a3572b87082..d70eefa336e 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -94,7 +94,7 @@ pub fn uumain(args: Vec) -> int { println!("{}", x); } - return 0 + return 0 } // We use String as a representatio of node here From e93aa89f4aab3018fae9b8bfeecc877562395943 Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 11:53:18 +0900 Subject: [PATCH 05/10] fix spacing Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index d70eefa336e..a95665652ba 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -42,7 +42,7 @@ pub fn uumain(args: Vec) -> int { println!(" {} [OPTIONS] FILE", NAME); println!(""); io::print(getopts::usage("Topological sort the strings in FILE. Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). If FILE is not passed in, stdin is used instead.", opts).as_slice()); - return 0; + return 0; } if matches.opt_present("V") { From 00e5519d0b0147dd3cce4a59a8004213addef79f Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 11:54:31 +0900 Subject: [PATCH 06/10] fix spacing Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index a95665652ba..40e166b2086 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -26,7 +26,7 @@ static VERSION: &'static str = "1.0.0"; pub fn uumain(args: Vec) -> int { let opts = [ - getopts::optflag("h", "help", "display this help and exit"), + getopts::optflag("h", "help", "display this help and exit"), getopts::optflag("V", "version", "output version information and exit"), ]; From 15994ed32c806448ae416acb1c61fe3683d08073 Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 11:55:31 +0900 Subject: [PATCH 07/10] fix spacing Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index 40e166b2086..446d4aa72da 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -25,10 +25,10 @@ static NAME: &'static str = "tsort"; static VERSION: &'static str = "1.0.0"; pub fn uumain(args: Vec) -> int { - let opts = [ + let opts = [ getopts::optflag("h", "help", "display this help and exit"), getopts::optflag("V", "version", "output version information and exit"), - ]; + ]; let matches = match getopts::getopts(args.tail(), opts) { Ok(m) => m, @@ -39,7 +39,7 @@ pub fn uumain(args: Vec) -> int { println!("{} v{}", NAME, VERSION); println!(""); println!("Usage:"); - println!(" {} [OPTIONS] FILE", NAME); + println!(" {} [OPTIONS] FILE", NAME); println!(""); io::print(getopts::usage("Topological sort the strings in FILE. Strings are defined as any sequence of tokens separated by whitespace (tab, space, or newline). If FILE is not passed in, stdin is used instead.", opts).as_slice()); return 0; From 619b74536cd1678f8467baf5b2f68a57f5accc17 Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 11:56:16 +0900 Subject: [PATCH 08/10] fix typo Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index 446d4aa72da..6b0ae3ed726 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -97,7 +97,7 @@ pub fn uumain(args: Vec) -> int { return 0 } -// We use String as a representatio of node here +// We use String as a representation of node here // but using integer may improve performance. struct Graph { in_edges: HashMap>, From 8c0a63fae0b72603bd391f24bdf3d8554a92f2c5 Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 12:08:41 +0900 Subject: [PATCH 09/10] fix buffered reader Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index 6b0ae3ed726..54d9fc7a668 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -59,14 +59,21 @@ pub fn uumain(args: Vec) -> int { files[0].to_string() }; + let mut stdin_buf; + let mut file_buf; let mut reader = io::BufferedReader::new( if input.as_slice() == "-" { - box io::stdio::stdin_raw() as Box + stdin_buf = io::stdio::stdin_raw(); + &mut stdin_buf as &mut Reader } else { - box match io::File::open(&Path::new(input.clone())) { + file_buf = match io::File::open(&Path::new(input.as_slice())) { Ok(a) => a, - _ => crash!(1, "{}: No such file or directory", input) - } as Box + _ => { + show_error!("{}: No such file or directory", input); + return 1; + } + }; + &mut file_buf as &mut Reader } ); From a7304e94453c32ff38b393a092c984275ef5569c Mon Sep 17 00:00:00 2001 From: Akira Hayakawa Date: Sun, 20 Jul 2014 12:12:50 +0900 Subject: [PATCH 10/10] refactor looping Signed-off-by: Akira Hayakawa --- tsort/tsort.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tsort/tsort.rs b/tsort/tsort.rs index 54d9fc7a668..fe77e0f716f 100644 --- a/tsort/tsort.rs +++ b/tsort/tsort.rs @@ -165,19 +165,16 @@ impl Graph { self.result.push(n.clone()); let n_out_edges = self.out_edges.find_mut(&n).unwrap(); - while !n_out_edges.is_empty() { - // n -> m - let m = (*n_out_edges)[0].clone(); - n_out_edges.remove(0); - - let m_in_edges = self.in_edges.find_mut(&m).unwrap(); + for m in n_out_edges.iter() { + let m_in_edges = self.in_edges.find_mut(m).unwrap(); m_in_edges.remove(&n); // If m doesn't have other in-coming edges add it to start_nodes if m_in_edges.is_empty() { - start_nodes.push(m); + start_nodes.push(m.clone()); } } + n_out_edges.clear(); } }