Skip to content
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

SkipList doesn't have the same semantics as a Vec #5

Open
coriolinus opened this issue Dec 17, 2017 · 3 comments
Open

SkipList doesn't have the same semantics as a Vec #5

coriolinus opened this issue Dec 17, 2017 · 3 comments

Comments

@coriolinus
Copy link

coriolinus commented Dec 17, 2017

Given the following library code:

extern crate skiplist;
use skiplist::SkipList;

pub const TWENTY_SEVENTEEN: usize = 2017;
pub const FIFTY_MILLION: usize = 50_000_000;

type List<T> = Vec<T>;

pub struct Spinner {
    index: usize,
    items: List<usize>,
    next_insert: usize,
}

impl Spinner {
    pub fn new() -> Spinner {
        let mut items = List::with_capacity(FIFTY_MILLION);
        items.insert(0, 0);
        Spinner {
            index: 0,
            items: items,
            next_insert: 1,
        }
    }

    fn spin_insert(&mut self, steps: usize, item: usize) {
        self.index = ((self.index + steps) % self.items.len()) + 1;
        self.items.insert(self.index, item);
    }

    pub fn insert_until(&mut self, until: usize, steps: usize) {
        for item in self.next_insert..(until + 1) {
            self.spin_insert(steps, item);
        }
        self.next_insert = until + 1;
    }

    pub fn get_index(&self) -> usize {
        self.index
    }

    pub fn get_items(&self) -> &List<usize> {
        &self.items
    }
}

then running the following produces a value of 640:

fn main() {
    let mut spinner = Spinner::new();
    spinner.insert_until(TWENTY_SEVENTEEN, INPUT);
    println!(
        "Item after 2017 inserts: {}",
        spinner.get_items()[spinner.get_index() + 1]
    );
}

However, if you replace type List<T> = Vec<T>; with type List<T> = SkipList<T>; in the library code, running the same code produces a value of 494. (No playground link because skiplist is not available in the playground; sorry.) In other words, though we haven't changed the algorithm at all, we get different results.

This is obviously not a minimal example, and for that I apologize. However, this case seems to prove that something is broken in the SkipList implementation.

@coriolinus
Copy link
Author

In Cargo.toml:

[dependencies]
skiplist = "0.2.10"

coriolinus added a commit to coriolinus/adventofcode-2017 that referenced this issue Dec 17, 2017
This was an interesting one. It took a while because for the first
while, I thought of it as a data structures problem: if you have a
structure for which inserting at an arbitrary index is O(log n) and
retrieval is also O(log n), you can actually just run the entire
simulation. After some searching, I discovered that skip lists
actually do work that way.

Unfortunately, the standard Rust implementation of skip lists
are broken in some way, and I didn't have time to figure out
exactly what was wrong with them. I filed an issue, but that's
as far as I got with that:

JP-Ellis/rust-skiplist#5

I actually considered giving up and looking at some other peoples'
solutions, but decided not to. I did look up a generalized hint,
which simply said that the twist in part 2 actually made it easier
to abstract without simulation than part 1's problem; from there,
I figured it out on my own.

Resultant performance isn't great, but isn't terrible either:

$ cargo build --release && time target/release/day17
    Finished release [optimized] target(s) in 0.0 secs
Item after 2017 inserts: 640
Item after 0 after 50 million inserts: 47949463

real    0m0.631s
user    0m0.578s
sys     0m0.000s
@carlomilanesi
Copy link

The cause is that the signature of alloc::vec::Vec insert is pub fn insert(&mut self, index: usize, element: T), and the signature of skiplist::skiplist::SkipList insert is pub fn insert(&mut self, value: T, index: usize).
Using the former, insert(1, 'x') inserts an 'x' in position 1; using the latter, insert('y', 2) inserts a 'y' in position 2. If your items have type `usize', you can wrongly swap the item with the position.

@coriolinus
Copy link
Author

Thanks for solving this! It's been a long while since I encountered this problem. Given that this is user error, one could plausibly just close this issue.

On the other hand, there's no obvious reason why this library shouldn't use the same insert signature that Vec does. Would a PR which normalizes the interface be welcomed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants