A blazing fast natural sorting library for Python written in Rust
import time
import random
import string

from natsort import natsorted as natsort_py
from natsort_rs import natsort as natsort_rs
from tomark import Tomark


def generate_random_strings(seed, num, length=10, prefix=''):
    random.seed(seed)
    
    characters = string.ascii_letters + string.digits
    
    random_strings = []
    for _ in range(num):
        random_string = ''.join(random.choice(characters) for _ in range(length))
        random_strings.append(prefix + random_string)
    
    return random_strings


def measure_execution_time(fn_name, func, *args, **kwargs):
    num_runs = 10
    total_execution_time = 0
    for _ in range(num_runs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        execution_time = end_time - start_time
        total_execution_time += execution_time
    average_execution_time = total_execution_time / num_runs
    print(f"{fn_name} took an average of {average_execution_time:.6f} seconds "
          f"to execute ({len(args[0])} items).")
    return average_execution_time, result


if __name__ == '__main__':
    random_seed = 1
    string_length = 15
    example_datas = [
        generate_random_strings(random_seed, rep, string_length) for rep in [
            10, 
            100, 
            1_000, 
            10_000, 
            100_000, 
            1_000_000
        ]
    ]

    benchmark_results = []
    for example_data in example_datas:
        dur_py, _ = measure_execution_time('natsort_py', natsort_py, example_data)
        dur_rs, _ = measure_execution_time('natsort_rs', natsort_rs, example_data, None, False, False)
        dur_rs_par, _ = measure_execution_time('natsort_rs_par', natsort_rs, example_data, None, False, True)
        ratio = dur_py / dur_rs_par

        print(f'Speed improvement: {ratio:.1f} times')
        benchmark_results.append({
            'No. of items': len(example_data),
            'Duration natsort [s]': f'{dur_py:.5f}',
            'Duration natsort-rs [s]': f'{dur_rs:.5f}',
            'Duration natsort-rs parallel [s]': f'{dur_rs_par:.5f}',
            'Relative speedup': f'{ratio:.1f}'
        })
    
    print(Tomark.table(benchmark_results))
benchmark.py
valentinstn/natsort-rs
from .natsort_rs import get_sorted_indices


def natsort(
        vals: list, 
        key = None, 
        ignore_case = False, 
        return_indices = False
):
    if key is None:
        strs_for_sorting = vals
    else:
        strs_for_sorting = [key(item) for item in vals]
    
    sorted_indices = get_sorted_indices(strs_for_sorting, ignore_case)

    if return_indices:
        return sorted_indices
    else:
        return [vals[idx] for idx in sorted_indices]


natsort_rs/__init__.py
valentinstn/natsort-rs
from natsort_rs import natsort
from unittest import TestCase


class NatSortTestCase(TestCase):

    def test_simple_natsorting(self):
        self.assertListEqual(
            natsort(['item 1', 'item 10', 'item 3']),
            ['item 1', 'item 3', 'item 10']
        )

    def test_case_unsensitive_natsorting(self):
        self.assertListEqual(
            natsort(['Item 1', 'Item 3', 'item 2'], ignore_case=True),
            ['Item 1', 'item 2', 'Item 3']
        )

    def test_more_complex_object_natsorting(self):
        sorted_objs = natsort(
            [
                {'name': 'item 1', 'id': 1},
                {'name': 'item 3', 'id': 3},
                {'name': 'item 2', 'id': 2}
            ],
            key=lambda d: d['name']
        )
        self.assertEqual(len(sorted_objs), 3)
        self.assertDictEqual(
            sorted_objs[0], 
            {'name': 'item 1', 'id': 1}
        )

        self.assertDictEqual(
            sorted_objs[1], 
            {'name': 'item 2', 'id': 2}
        )

        self.assertDictEqual(
            sorted_objs[2], 
            {'name': 'item 3', 'id': 3}
        )
    
    def test_with_numbers_attached_to_string(self):
        self.assertListEqual(
            natsort(
                ['item 0', 'item 1b', 'Item 5', 'Item 50000', 
                 'item 3b', 'item 3000b', 'item 2b'], 
                ignore_case=True
            ),
            ['item 0', 'item 1b', 'item 2b', 'item 3b', 
             'Item 5', 'item 3000b', 'Item 50000']
        )

    def test_numbers_with_unit(self):
        self.assertListEqual(
            natsort(['Vol: 20L', 'Vol: 1L', 'Vol: 2L', 'Vol: 10L']),
            ['Vol: 1L', 'Vol: 2L', 'Vol: 10L', 'Vol: 20L']
        )

    def test_returned_indices(self):
        self.assertEqual(
            natsort(['Vol: 20L', 'Vol: 1L', 'Vol: 2L', 'Vol: 10L'], 
                    return_indices=True),
            [1, 2, 3, 0]
        )
tests/test_natsort.py
valentinstn/natsort-rs