Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ PROGS := \
tr \
true \
truncate \
tsort \
unlink \
uniq \
wc \
Expand Down
189 changes: 189 additions & 0 deletions tsort/tsort.rs
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"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing.

];

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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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(...));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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
}
}