diff --git a/src/cmd/sort.rs b/src/cmd/sort.rs index ecbcb3aa..af83c8f3 100644 --- a/src/cmd/sort.rs +++ b/src/cmd/sort.rs @@ -1,9 +1,11 @@ use std::cmp; use CliResult; +use byteorder::{ByteOrder, LittleEndian}; use config::{Config, Delimiter}; use select::SelectColumns; use util; +use rand::{thread_rng, Rng, SeedableRng, StdRng}; use std::str::from_utf8; use self::Number::{Float, Int}; @@ -21,6 +23,8 @@ sort options: See 'xsv select --help' for the format details. -N, --numeric Compare according to string numerical value -R, --reverse Reverse order + --random Random order + --seed RNG seed Common options: -h, --help Display this message @@ -39,6 +43,8 @@ struct Args { flag_select: SelectColumns, flag_numeric: bool, flag_reverse: bool, + flag_random: bool, + flag_seed: Option, flag_output: Option, flag_no_headers: bool, flag_delimiter: Option, @@ -48,6 +54,7 @@ pub fn run(argv: &[&str]) -> CliResult<()> { let args: Args = util::get_args(USAGE, argv)?; let numeric = args.flag_numeric; let reverse = args.flag_reverse; + let random = args.flag_random; let rconfig = Config::new(&args.arg_input) .delimiter(args.flag_delimiter) .no_headers(args.flag_no_headers) @@ -58,27 +65,42 @@ pub fn run(argv: &[&str]) -> CliResult<()> { let headers = rdr.byte_headers()?.clone(); let sel = rconfig.selection(&headers)?; + // Seeding rng + let seed = args.flag_seed; + let mut rng: StdRng = match seed { + None => { + StdRng::from_rng(thread_rng()).unwrap() + } + Some(seed) => { + let mut buf = [0u8; 32]; + LittleEndian::write_u64(&mut buf, seed as u64); + SeedableRng::from_seed(buf) + } + }; + let mut all = rdr.byte_records().collect::, _>>()?; - match (numeric, reverse) { - (false, false) => + match (numeric, reverse, random) { + (_, _, true) => + rng.shuffle(&mut all), + (false, false, false) => all.sort_by(|r1, r2| { let a = sel.select(r1); let b = sel.select(r2); iter_cmp(a, b) }), - (true, false) => + (true, false, false) => all.sort_by(|r1, r2| { let a = sel.select(r1); let b = sel.select(r2); iter_cmp_num(a, b) }), - (false, true) => + (false, true, false) => all.sort_by(|r1, r2| { let a = sel.select(r1); let b = sel.select(r2); iter_cmp(b, a) }), - (true, true) => + (true, true, false) => all.sort_by(|r1, r2| { let a = sel.select(r1); let b = sel.select(r2); diff --git a/tests/test_sort.rs b/tests/test_sort.rs index 7ece56c6..72a763a7 100644 --- a/tests/test_sort.rs +++ b/tests/test_sort.rs @@ -128,6 +128,27 @@ fn sort_reverse() { assert_eq!(got, expected); } +#[test] +fn sort_random() { + let wrk = Workdir::new("sort_random"); + wrk.create("in.csv", vec![ + svec!["R", "S"], + svec!["1", "b"], + svec!["2", "a"], + ]); + + let mut cmd = wrk.command("sort"); + cmd.arg("--random").args(&["--seed", "0"]).arg("in.csv"); + + let got: Vec> = wrk.read_stdout(&mut cmd); + let expected = vec![ + svec!["R", "S"], + svec!["2", "a"], + svec!["1", "b"], + ]; + assert_eq!(got, expected); +} + /// Order `a` and `b` lexicographically using `Ord` pub fn iter_cmp(mut a: L, mut b: R) -> cmp::Ordering where A: Ord, L: Iterator, R: Iterator {