-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add tsort #359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add tsort #359
Changes from all commits
40638bd
0e57ea5
2982a58
79f57be
e93aa89
00e5519
15994ed
619b745
8c0a63f
a7304e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,7 @@ PROGS := \ | |
| tr \ | ||
| true \ | ||
| truncate \ | ||
| tsort \ | ||
| unlink \ | ||
| uniq \ | ||
| wc \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| #![crate_name = "tsort"] | ||
|
|
||
| /* | ||
| * This file is part of the uutils coreutils package. | ||
| * | ||
| * (c) Ben Eggers <ben.eggers36@gmail.com> | ||
| * (c) Akira Hayakawa <ruby.wktk@gmail.com> | ||
| * | ||
| * 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<String>) -> 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be written as print!("{name} v{version}
Usage:
{name} [OPTIONS] FILE
{usage}",
name = NAME,
vers = VERSION,
usage = getopts::usage(...));
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll probably refactor all of the utilities to use this at some point. |
||
| 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 stdin_buf; | ||
| let mut file_buf; | ||
| let mut reader = io::BufferedReader::new( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer that this be done like: let mut stdin_buf;
let mut file_buf;
let mut reader = io::BufferedReader::new(
if input.as_slice() == "-" {
stdin_buf = io::stdio::stdin_raw();
&mut stdin_buf as &mut Reader
} else {
file_buf = match io::File::open(&Path::new(input.as_slice())) {
Ok(a) => a,
_ => {
show_error!("{}: No such file or directory", input);
return 1
}
};
&mut file_buf as &mut Reader
}
); |
||
| if input.as_slice() == "-" { | ||
| stdin_buf = io::stdio::stdin_raw(); | ||
| &mut stdin_buf as &mut Reader | ||
| } else { | ||
| file_buf = match io::File::open(&Path::new(input.as_slice())) { | ||
| Ok(a) => a, | ||
| _ => { | ||
| show_error!("{}: No such file or directory", input); | ||
| return 1; | ||
| } | ||
| }; | ||
| &mut file_buf as &mut Reader | ||
| } | ||
| ); | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| for x in g.result.iter() { | ||
| println!("{}", x); | ||
| } | ||
|
|
||
| return 0 | ||
| } | ||
|
|
||
| // We use String as a representation of node here | ||
| // but using integer may improve performance. | ||
| struct Graph { | ||
| in_edges: HashMap<String, HashSet<String>>, | ||
| out_edges: HashMap<String, Vec<String>>, | ||
| result: Vec<String> | ||
| } | ||
|
|
||
| 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.shift().unwrap(); | ||
|
|
||
| self.result.push(n.clone()); | ||
|
|
||
| let n_out_edges = self.out_edges.find_mut(&n).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.clone()); | ||
| } | ||
| } | ||
| n_out_edges.clear(); | ||
| } | ||
| } | ||
|
|
||
| fn is_acyclic(&self) -> bool { | ||
| for (_, edges) in self.out_edges.iter() { | ||
| if !edges.is_empty() { | ||
| return false | ||
| } | ||
| } | ||
| true | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spacing.