Skip to main content

Fast Unicode grapheme cluster segmentation with Rust + PyO3

Project description

graphemex

Fast Unicode grapheme cluster segmentation library written in Rust using PyO3.

About

graphemex is a high-performance Python library for Unicode grapheme cluster handling, powered by Rust. It provides correct and efficient text segmentation according to Unicode standards, which is essential for proper text processing in many applications.

Key Benefits

  • ๐Ÿš€ High Performance: Implemented in Rust for maximum speed
  • ๐ŸŒ Unicode Correctness: Properly handles all Unicode grapheme clusters
  • ๐Ÿ”ง Simple API: Just 4 intuitive functions for common text operations
  • ๐Ÿ’ป Cross-Platform: Works on all major operating systems
  • ๐Ÿ Python Friendly: Seamless Python integration via PyO3
  • ๐Ÿ›  Zero Dependencies: Only requires Python standard library at runtime

Features

  • split(text: str) -> List[str]: Splits text into grapheme clusters
  • len(text: str) -> int: Returns the number of grapheme clusters in the string
  • slice(text: str, start: int, end: int) -> str: Extracts a substring by grapheme cluster indices
  • truncate(text: str, max_len: int) -> str: Truncates string to maximum number of grapheme clusters

Installation

Use Cases

  • Text editors and IDEs
  • Input validation and processing
  • Social media character counting
  • Text truncation for UI elements
  • Natural language processing
  • Data cleaning and normalization

Performance

graphemex is significantly faster than pure Python implementations:

  • Up to 100x faster for grapheme splitting
  • Minimal memory overhead
  • Efficient handling of large texts

Running benchmarks... Each test runs 1000 times Comparing graphemex (Rust) vs grapheme (Python)

=== Simple Text - Split === +----------+--------------------+---------------------+----------------+ | Metric | graphemex (Rust) | grapheme (Python) | Times Faster | +==========+====================+=====================+================+ | Mean | 2.714 ms | 3.461 ms | 1.3x | +----------+--------------------+---------------------+----------------+ | Median | 2.700 ms | 3.454 ms | 1.3x | +----------+--------------------+---------------------+----------------+ | Min | 2.627 ms | 3.310 ms | 1.3x | +----------+--------------------+---------------------+----------------+ | Max | 5.687 ms | 5.660 ms | 1.0x | +----------+--------------------+---------------------+----------------+ | StdDev | 0.121 ms | 0.102 ms | N/A | +----------+--------------------+---------------------+----------------+

=== Emoji Text - Split === +----------+--------------------+---------------------+----------------+ | Metric | graphemex (Rust) | grapheme (Python) | Times Faster | +==========+====================+=====================+================+ | Mean | 2.595 ms | 8.621 ms | 3.3x | +----------+--------------------+---------------------+----------------+ | Median | 2.526 ms | 8.642 ms | 3.4x | +----------+--------------------+---------------------+----------------+ | Min | 2.446 ms | 8.486 ms | 3.5x | +----------+--------------------+---------------------+----------------+ | Max | 3.730 ms | 9.093 ms | 2.4x | +----------+--------------------+---------------------+----------------+ | StdDev | 0.151 ms | 0.071 ms | N/A | +----------+--------------------+---------------------+----------------+

=== Mixed Text - Split === +----------+--------------------+---------------------+----------------+ | Metric | graphemex (Rust) | grapheme (Python) | Times Faster | +==========+====================+=====================+================+ | Mean | 3.525 ms | 14.382 ms | 4.1x | +----------+--------------------+---------------------+----------------+ | Median | 3.529 ms | 14.315 ms | 4.1x | +----------+--------------------+---------------------+----------------+ | Min | 3.441 ms | 14.014 ms | 4.1x | +----------+--------------------+---------------------+----------------+ | Max | 3.884 ms | 18.410 ms | 4.7x | +----------+--------------------+---------------------+----------------+ | StdDev | 0.040 ms | 0.495 ms | N/A | +----------+--------------------+---------------------+----------------+

=== Simple Text - Length === +----------+--------------------+---------------------+----------------+ | Metric | graphemex (Rust) | grapheme (Python) | Times Faster | +==========+====================+=====================+================+ | Mean | 2.210 ms | 3.467 ms | 1.6x | +----------+--------------------+---------------------+----------------+ | Median | 2.208 ms | 3.465 ms | 1.6x | +----------+--------------------+---------------------+----------------+ | Min | 2.144 ms | 3.369 ms | 1.6x | +----------+--------------------+---------------------+----------------+ | Max | 2.426 ms | 3.826 ms | 1.6x | +----------+--------------------+---------------------+----------------+ | StdDev | 0.029 ms | 0.039 ms | N/A | +----------+--------------------+---------------------+----------------+

=== Emoji Text - Length === +----------+--------------------+---------------------+----------------+ | Metric | graphemex (Rust) | grapheme (Python) | Times Faster | +==========+====================+=====================+================+ | Mean | 2.192 ms | 8.672 ms | 4.0x | +----------+--------------------+---------------------+----------------+ | Median | 2.186 ms | 8.678 ms | 4.0x | +----------+--------------------+---------------------+----------------+ | Min | 2.130 ms | 8.495 ms | 4.0x | +----------+--------------------+---------------------+----------------+ | Max | 2.516 ms | 9.439 ms | 3.8x | +----------+--------------------+---------------------+----------------+ | StdDev | 0.036 ms | 0.071 ms | N/A | +----------+--------------------+---------------------+----------------+

=== Mixed Text - Length === +----------+--------------------+---------------------+----------------+ | Metric | graphemex (Rust) | grapheme (Python) | Times Faster | +==========+====================+=====================+================+ | Mean | 2.700 ms | 14.353 ms | 5.3x | +----------+--------------------+---------------------+----------------+ | Median | 2.698 ms | 14.340 ms | 5.3x | +----------+--------------------+---------------------+----------------+ | Min | 2.615 ms | 14.050 ms | 5.4x | +----------+--------------------+---------------------+----------------+ | Max | 2.978 ms | 15.605 ms | 5.2x | +----------+--------------------+---------------------+----------------+ | StdDev | 0.035 ms | 0.116 ms | N/A | +----------+--------------------+---------------------+----------------+

Batch

Requirements

  • Python โ‰ฅ3.7
  • No additional runtime dependencies

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Alexandr Sukhryn (alexandrvirtual@gmail.com)

use rayon::prelude::; use pyo3::prelude::;

#[pyfunction] fn batch_grapheme_len(texts: Vec) -> PyResult<Vec> { Ok(texts.par_iter() .map(|text| UnicodeSegmentation::graphemes(text, true).count()) .collect()) }

#[cfg(test)] mod tests { use super::*;

#[test]
fn test_split_basic() {
    let text = "Hello";
    assert_eq!(split(text), vec!["H", "e", "l", "l", "o"]);
}

#[test]
fn test_split_emoji() {
    let text = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ๐Ÿ‘‹";
    assert_eq!(split(text), vec!["๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ", "๐Ÿ‘‹"]);
}

#[test]
fn test_len_basic() {
    assert_eq!(len("Hello"), 5);
    assert_eq!(len(""), 0);
}

#[test]
fn test_len_complex() {
    assert_eq!(len("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Hello ๐ŸŒ!"), 10);
}

#[test]
fn test_slice_basic() {
    let text = "Hello";
    assert_eq!(slice(text, 0, 2).unwrap(), "He");
    assert_eq!(slice(text, 1, 4).unwrap(), "ell");
}

#[test]
fn test_slice_emoji() {
    let text = "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Hello ๐ŸŒ!";
    assert_eq!(slice(text, 0, 1).unwrap(), "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ");
    assert_eq!(slice(text, 8, 9).unwrap(), "๐ŸŒ");
}

#[test]
fn test_slice_invalid() {
    let text = "Hello";
    assert!(slice(text, 3, 2).is_err()); // start > end
    assert!(slice(text, 0, 6).is_err()); // end > len
}

#[test]
fn test_truncate_basic() {
    assert_eq!(truncate("Hello", 3), "Hel");
    assert_eq!(truncate("Hello", 5), "Hello");
    assert_eq!(truncate("Hello", 10), "Hello");
}

#[test]
fn test_truncate_emoji() {
    assert_eq!(truncate("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Hello", 1), "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ");
    assert_eq!(truncate("๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Hello", 2), "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ");
}

}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

graphemex-0.1.2-cp37-abi3-macosx_11_0_arm64.whl (611.6 kB view details)

Uploaded CPython 3.7+macOS 11.0+ ARM64

File details

Details for the file graphemex-0.1.2-cp37-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for graphemex-0.1.2-cp37-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 34fcceb5a95d622df399e8e9b89067498d64b2e5484c8478b44073aaa3978126
MD5 fbaf12d54bf7ae3a786dd9cc66d69643
BLAKE2b-256 37317e89c8149cb19b874fa7e251208f970b99afdf9329fbc63b175c8a9ada81

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page