I'm working on a port of NLTK to Rust.
I am fairly new to Rust, so I wanted to post a small file to check if I was writing idiomatic Rust. I've included the original Python. The Python has docstrings that describes usage, so I didn't include comments for brevity in the Rust.
This file passes all of the tests we've written, so we're certain that it mostly works.
util.py
from re import finditer def string_span_tokenize(s, sep): r""" Return the offsets of the tokens in *s*, as a sequence of ``(start, end)`` tuples, by splitting the string at each occurrence of *sep*. >>> from nltk.tokenize.util import string_span_tokenize >>> s = '''Good muffins cost $3.88\nin New York. Please buy me ... two of them.\n\nThanks.''' >>> list(string_span_tokenize(s, " ")) [(0, 4), (5, 12), (13, 17), (18, 26), (27, 30), (31, 36), (37, 37), (38, 44), (45, 48), (49, 55), (56, 58), (59, 73)] :param s: the string to be tokenized :type s: str :param sep: the token separator :type sep: str :rtype: iter(tuple(int, int)) """ if len(sep) == 0: raise ValueError("Token delimiter must not be empty") left = 0 while True: try: right = s.index(sep, left) if right != 0: yield left, right except ValueError: if left != len(s): yield left, len(s) break left = right + len(sep) def regexp_span_tokenize(s, regexp): r""" Return the offsets of the tokens in *s*, as a sequence of ``(start, end)`` tuples, by splitting the string at each successive match of *regexp*. >>> from nltk.tokenize import WhitespaceTokenizer >>> s = '''Good muffins cost $3.88\nin New York. Please buy me ... two of them.\n\nThanks.''' >>> list(WhitespaceTokenizer().span_tokenize(s)) [(0, 4), (5, 12), (13, 17), (18, 23), (24, 26), (27, 30), (31, 36), (38, 44), (45, 48), (49, 51), (52, 55), (56, 58), (59, 64), (66, 73)] :param s: the string to be tokenized :type s: str :param regexp: regular expression that matches token separators :type regexp: str :rtype: iter(tuple(int, int)) """ left = 0 for m in finditer(regexp, s): right, next = m.span() if right != 0: yield left, right left = next yield left, len(s) def spans_to_relative(spans): r""" Return a sequence of relative spans, given a sequence of spans. >>> from nltk.tokenize import WhitespaceTokenizer >>> from nltk.tokenize.util import spans_to_relative >>> s = '''Good muffins cost $3.88\nin New York. Please buy me ... two of them.\n\nThanks.''' >>> list(spans_to_relative(WhitespaceTokenizer().span_tokenize(s))) [(0, 4), (1, 7), (1, 4), (1, 5), (1, 2), (1, 3), (1, 5), (2, 6), (1, 3), (1, 2), (1, 3), (1, 2), (1, 5), (2, 7)] :param spans: a sequence of (start, end) offsets of the tokens :type spans: iter(tuple(int, int)) :rtype: iter(tuple(int, int)) """ prev = 0 for left, right in spans: yield left - prev, right - left prev = right
util.rs
extern crate regex;
use regex::Regex;
pub fn string_span_tokenize(s: &str, sep: &str) -> Result<Vec<(usize, usize)>, String> {
if sep.len() == 0 {
Err(String::from("Error! Separator has a length of 0!"))
} else {
// TODO: we'll likely want to do some error checking
// to ensure s.len() and str.len() don't exceed usize::MAX
let strlen = s.len();
let seplen = sep.len();
let mut result: Vec<(usize, usize)> = Vec::new();
let mut left = 0;
let mut r_idx;
loop {
let right = s[left..].find(sep); // TODO: Will this work on unicode?
match right {
Some(right_idx) => {
if right_idx != 0 {
result.push((left, right_idx));
}
r_idx = right_idx;
},
None => {
if left != strlen {
result.push((left, strlen));
}
break;
}
}
left = r_idx + seplen;
}
return Ok(result);
}
}
pub fn regexp_span_tokenize(s: &str, regexp: ®ex::Regex) -> Vec<(usize, usize)> {
let mut result: Vec<(usize, usize)> = Vec::new();
let strlen = s.len();
let mut left = 0;
for (right, next) in regexp.find_iter(s) {
if right != 0 {
result.push((left, right));
}
left = next
}
result.push((left, strlen));
return result;
}
pub fn spans_to_relative(spans: Vec<(usize, usize)>) -> Vec<(usize, usize)> {
let mut prev = 0;
let mut result: Vec<(usize, usize)> = Vec::new();
for (left, right) in spans {
result.push((left - prev, right - left));
prev = right;
}
return result;
}
We decided to return Vec
s instead of using generators because generators are verbose in Rust.
regexp_span_tokenize
don't seem to actually be testing that method. \$\endgroup\$