なんでつくったか
例えば x, y, z
がある以下のような構造体があって、
struct Sample { x: i64, y: i64, z: i64, }
v: Vec<Sample>
をソートしたい。ただし、並び替える順番は利用箇所によって違う。
こんなときに、いつも、 sort_by
を使って、以下のように書いていた*1。
// x昇順→y昇順→z昇順 v.sort_by(|l, r| { match l.x.cmp(&r.x) { std::cmp::Ordering::Equal => { match l.y.cmp(&r.y) { std::cmp::Ordering::Equal => { l.z.cmp(&r.z) }, other => other } }, other => other } });
これは書くの面倒だし、やりたいことの割に長くて目を引く。
ところでSQLでは order by x, y, z
と書けるな、とふと思って、
そんな感じで簡単に書けるようにするマクロが欲しくなった。ので作った。
使い方
以下のように使う。
let mut v = vec![ Sample {x: 10, y: 200, z:3000}, Sample {x: 10, y: 100, z:1000}, // ... ]; // x昇順→y昇順→z昇順 v.sort_by(order_by!(x, y, z)); // x昇順→y降順 v.sort_by(order_by!(x asc, y desc)); // z昇順→x昇順 v.sort_by(order_by!(z asc, x));
さらに、タプルでもいける。
let mut v2: Vec<(i64, i64, i64)> = vec![ (20, 100, 1000), (10, 300, 1000), (10, 300, 2000), (30, 300, 2000), (30, 300, 3000), // ... ]; v2.sort_by(order_by!(0, 1, 2)); v2.sort_by(order_by!(0 desc, 1, 2)); v2.sort_by(order_by!(2, 0, 1));
マクロコード
macro_rules! order_by { ($($x:tt)*) => { |l, r| { order_by_inner!(l, r, $($x)*) } } } macro_rules! order_by_inner { () => {}; ($l:ident) => {std::cmp::Ordering::Equal}; ($l:ident , ) => {std::cmp::Ordering::Equal}; ($l:ident , $r:ident) => {std::cmp::Ordering::Equal}; ($l:ident , $r:ident , ) => {std::cmp::Ordering::Equal}; // last ($l:ident , $r:ident , $x:tt asc) => { $l.$x.cmp(&$r.$x) }; ($l:ident , $r:ident , $x:tt desc) => { $l.$x.cmp(&$r.$x).reverse() }; ($l:ident , $r:ident , $x:tt) => { order_by_inner!($l, $r, $x asc) }; // mid ($l:ident , $r:ident , $x:tt asc , $($p:tt)+) => { match $l.$x.cmp(&$r.$x) { std::cmp::Ordering::Equal => { order_by_inner!($l, $r, $($p)+) }, other => other } }; ($l:ident , $r:ident , $x:tt desc , $($p:tt)+) => { match $l.$x.cmp(&$r.$x).reverse() { std::cmp::Ordering::Equal => { order_by_inner!($l, $r, $($p)+) }, other => other } }; ($l:ident , $r:ident , $x:tt , $($p:tt)+) => { order_by_inner!($l, $r, $x asc, $($p)+) }; }
*1:もっといいやり方があったら教えてほしい