2

I'm trying to pass a list of lists from Python to Rust using Py03. The function I'm trying to pass it to has this signature:

pub fn k_nearest_neighbours(k: usize, x: &[[f32; 2]], y: &[[f32; 3]]) -> Vec<Option<f32>> 

I'm writing bindings for a pre-existing lib, therefore I cannot alter the original code. My current way of doing things is this:

// This is example code == DOES NOT WORK
#[pyfunction] // make a new function within a new library with pyfunction macro
fn k_nearest_neighbours(k: usize, x: Vec<Vec<f32>>, y: Vec<f32>) -> Vec<Option<f32>> {
    // reformat input where necessary
    let x_slice = x.as_slice();
    // return original lib's function return
    classification::k_nearest_neighbours(k, x_slice, y)
}

The x.as_slice() function almost does what I need it to, it gives me a slice of vectors &[Vec<f32>], instead of a slice of slices &[[f32; 3]].

I want to be able to run this Python code:

from rust_code import k_nearest_neighbours as knn  # this is the Rust function compiled with PyO3

X = [[0.0, 1.0], [2.0, 3.0], [4.0, 5.0], [0.06, 7.0]]
train = [
        [0.0, 0.0, 0.0],
        [0.5, 0.5, 0.0],
        [3.0, 3.0, 1.0],
        [4.0, 3.0, 1.0],
    ]

k = 2
y_true = [0, 1, 1, 1]
y_test = knn(k, X, train)
assert(y_true == y_test)

1 Answer 1

5

Taking a look at the signature of k_nearest_neighbours shows that it expects [f32; 2] and [f32; 3] which are arrays, not slices (e.g. &[f32].

Arrays have a statically known size at compile time, whereas slices are dynamically sized. The same holds for vectors, you can't control what length the inner vectors have in your example. Therefore you end up with a fallible conversion from the input vectors to the expected arrays.

You can convert from a slice to an array using TryFrom, i.e.:

use std::convert::TryFrom;
fn main() {
    let x = vec![vec![3.5, 3.4, 3.6]];
    let x: Result<Vec<[f32; 3]>, _> = x.into_iter().map(TryFrom::try_from).collect::<Result<Vec<_>, _>>();
}

Putting this together, your function will need to return an error on incorrect input and you will need to create a new vector with arrays that you can pass to your function:

#[pyfunction] // make a new function within a new library with pyfunction macro
fn k_nearest_neighbours(k: usize, x: Vec<Vec<f32>>, y: Vec<f32>) -> PyResult<Vec<Option<f32>>> {
    let x = x.into_iter().map(TryFrom::try_from).collect::<Result<Vec<_>, _>>();
    let y = y.into_iter().map(TryFrom::try_from).collect::<Result<Vec<_>, _>>();
    // Error handling is missing here, you'll need to look into PyO3's documentation for that
    ...
    // return original lib's function return
    Ok(classification::k_nearest_neighbours(k, &x, &y))
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.