This problem is taken from the book Introduction to Algorithms, Third Edition By Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein:
We define the Josephus problem as follows. Suppose that n people form a circle and that we are given a positive integer m <= n. Beginning with a designated first person, we proceed around the circle, removing every mth person. After each person is removed, counting continues around the circle that remains. This process continues until we have removed all n people. The order in which the people are removed from the circle defines the (n, m)-Josephus permutation of the integers 1,2,...,n. For example, the (7, 3)-Josephus permutation is (3,6,2,7,5,1,4)
Suppose that m is not a constant. Describe an O(nlg(n))-time algorithm that, given integers n and m, outputs the (n, m)-Josephus permutation.
My solution was to use an augmented tree for getting the rank of any element in \$O(log(n))\$time. But instead of implementing a balance tree, I build the tree given a range from 1
to n
in \$O(n)\$ time. I would prefer a review on how to make my code more idiomatic and elegant but any kind of review is welcome.
Josephus permutation (lib.rs)
extern crate itertools;
use itertools::Unfold;
mod util;
use util::*;
pub fn permutation(size: u32, m: u32) -> Box<Iterator<Item = u32>> {
let mut tree = Tree::new(1, size);
Box::new(Unfold::new(1, move |a| {
*a = (*a + m - 2) % tree.len() + 1;
Some(tree.pop_rank(*a))
}).take(size as usize))
}
Tree (util.rs)
use std::cmp::Ordering;
#[derive(Debug)]
struct Node {
data: u32,
left: Option<Box<Node>>,
rigth: Option<Box<Node>>,
size: u32,
}
impl Node {
fn new(data: u32) -> Node {
Node {
data: data,
left: None,
rigth: None,
size: 0,
}
}
}
#[derive(Debug)]
pub struct Tree {
root: Option<Box<Node>>,
}
impl Tree {
fn get_size(node: &Option<Box<Node>>) -> u32 {
node.as_ref().map_or(0, |x| x.size)
}
fn build_from_range(from: u32, to: u32) -> Option<Box<Node>> {
if from > to {
None
} else {
let mid = from + (to - from) / 2;
let mut node = Node::new(mid);
node.left = Tree::build_from_range(from, mid - 1);
node.rigth = Tree::build_from_range(mid + 1, to);
node.size = 1 + Tree::get_size(&node.left)
+ Tree::get_size(&node.rigth);
Some(Box::new(node))
}
}
pub fn new(from: u32, to: u32) -> Tree {
Tree { root: Tree::build_from_range(from, to) }
}
fn find_rank(node: &mut Option<Box<Node>>, rank: u32) -> &mut Option<Box<Node>> {
let r = Tree::get_size(&node.as_mut()
.expect("rank out of range")
.left) + 1;
Tree::get_mut(node).size -= 1;
let (d, r) = match rank.cmp(&r) {
Ordering::Equal => return node,
Ordering::Less => (&mut Tree::get_mut(node).left, rank),
Ordering::Greater => (&mut Tree::get_mut(node).rigth, rank - r),
};
Tree::find_rank(d, r)
}
fn get_mut(node: &mut Option<Box<Node>>) -> &mut Box<Node> {
node.as_mut().unwrap()
}
pub fn pop_rank(&mut self, rank: u32) -> u32 {
let ranked = Tree::find_rank(&mut self.root, rank);
let data = ranked.as_ref()
.unwrap()
.data;
Tree::delete_node(ranked);
data
}
fn delete_node(node: &mut Option<Box<Node>>) {
*node = node.take()
.map(|mut x| {
if x.left.is_none() {
x.rigth
} else if x.rigth.is_none() {
x.left
} else {
x.data = Tree::pop_min(&mut x.rigth).unwrap();
Some(x)
}
})
.expect("Cant't delete none");
}
fn pop_min(node: &mut Option<Box<Node>>) -> Option<u32> {
node.take()
.and_then(|mut x| {
if x.left.is_none() {
let data = x.data;
*node = x.rigth;
Some(data)
} else {
x.size -= 1;
let result = Tree::pop_min(&mut x.left);
*node = Some(x);
result
}
})
}
pub fn len(&self) -> u32 {
Tree::get_size(&self.root)
}
}
Client and tests (main.rs)
extern crate josephus;
#[test]
fn test1() {
let x: Vec<u32> = josephus::permutation(7,3).collect();
assert_eq!(x, vec![3, 6, 2, 7, 5, 1, 4 ]);
}
#[test]
fn test2() {
let x: Vec<u32> = josephus::permutation(14, 6).collect();
assert_eq!(x, vec![6, 12, 4, 11, 5, 14, 9, 7, 3 ,8, 13, 10, 2, 1 ]);
}
fn main() {
let x: Vec<u32> = josephus::permutation(143455,1534).collect();
println!("{:?}", x.len());
}
util
? If it's not included, it will be hard to give effective review as we can't compile to ensure it works. To that end, some tests or expected outputs is always useful. \$\endgroup\$