From 8f697951141216d3b3fe654126f2e31c23d96660 Mon Sep 17 00:00:00 2001 From: Dmitry Pershin Date: Sun, 9 Jan 2022 14:46:17 +0500 Subject: [PATCH 1/4] documentation fixed. --- README.md | 107 +++++++++++++++++++++++++---------------- examples/quickstart.rs | 5 +- src/buffer.rs | 38 +++++++++++++-- src/chunk.rs | 30 +++++++++++- src/lib.rs | 65 ++++++++++++++++++++++--- src/merger.rs | 7 ++- src/sort.rs | 18 +++++-- 7 files changed, 205 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index eccf498..0484790 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,82 @@ +[![Crates.io][crates-badge]][crates-url] +[![License][licence-badge]][licence-url] +[![Test Status][test-badge]][test-url] +[![Documentation][doc-badge]][doc-url] + +[crates-badge]: https://img.shields.io/crates/v/ext-sort.svg +[crates-url]: https://crates.io/crates/ext-sort +[licence-badge]: https://img.shields.io/badge/license-Unlicense-blue.svg +[licence-url]: https://github.com/dapper91/ext-sort-rs/blob/master/LICENSE +[test-badge]: https://github.com/dapper91/ext-sort-rs/actions/workflows/test.yml/badge.svg?branch=master +[test-url]: https://github.com/dapper91/ext-sort-rs/actions/workflows/test.yml +[doc-badge]: https://docs.rs/ext-sort/badge.svg +[doc-url]: https://docs.rs/ext-sort + + # Rust external sort `ext-sort` is a rust external sort algorithm implementation. -External sort algorithm implementation. External sorting is a class of sorting algorithms -that can handle massive amounts of data. External sorting is required when the data being -sorted do not fit into the main memory (RAM) of a computer and instead must be resided in -slower external memory, usually a hard disk drive. Sorting is achieved in two passes. -During the first pass it sorts chunks of data that each fit in RAM, during the second pass -it merges the sorted chunks together. -For more information see https://en.wikipedia.org/wiki/External_sorting. +External sorting is a class of sorting algorithms that can handle massive amounts of data. External sorting +is required when the data being sorted do not fit into the main memory (RAM) of a computer and instead must be +resided in slower external memory, usually a hard disk drive. Sorting is achieved in two passes. During the +first pass it sorts chunks of data that each fit in RAM, during the second pass it merges the sorted chunks together. +For more information see [External Sorting](https://en.wikipedia.org/wiki/External_sorting). + +## Overview -## Features +`ext-sort` supports the following features: * **Data agnostic:** - `ext-sort` support all data types that that implement `serde` serialization/deserialization. + it supports all data types that implement `serde` serialization/deserialization by default, + otherwise you can implement your own serialization/deserialization mechanism. * **Serialization format agnostic:** - `ext-sort` use `MessagePack` serialization format by default, but it can be easily substituted by your custom one - if `MessagePack` serialization/deserialization performance is not sufficient for your task. + the library uses `MessagePack` serialization format by default, but it can be easily substituted by your custom one + if `MessagePack` serialization/deserialization performance is not sufficient for your task. * **Multithreading support:** - `ext-sort` support multithreading, which means data is sorted in multiple threads utilizing maximum CPU resources + multi-threaded sorting is supported, which means data is sorted in multiple threads utilizing maximum CPU resources and reducing sorting time. +* **Memory limit support:** + memory limited sorting is supported. It allows you to limit sorting memory consumption + (`memory-limit` feature required). # Basic example +Activate `memory-limit` feature of the ext-sort crate on Cargo.toml: + +```toml +[dependencies] +ext-sort = { version = "^0.1.0", features = ["memory-limit"] } +``` + ``` rust - use std::fs; - use std::io::{self, prelude::*}; - use std::path; - - use bytesize::MB; - use env_logger; - use log; - - use ext_sort::buffer::mem::MemoryLimitedBufferBuilder; - use ext_sort::{ExternalSorter, ExternalSorterBuilder}; - - fn main() { - env_logger::Builder::new().filter_level(log::LevelFilter::Debug).init(); - - let input_reader = io::BufReader::new(fs::File::open("input.txt").unwrap()); - let mut output_writer = io::BufWriter::new(fs::File::create("output.txt").unwrap()); - - let sorter: ExternalSorter = ExternalSorterBuilder::new() - .with_tmp_dir(path::Path::new("tmp")) - .with_buffer(MemoryLimitedBufferBuilder::new(50 * MB)) - .build() - .unwrap(); - - let sorted = sorter.sort(input_reader.lines()).unwrap(); - - for item in sorted.map(Result::unwrap) { - output_writer.write_all(format!("{}\n", item).as_bytes()).unwrap(); - } - output_writer.flush().unwrap(); +use std::fs; +use std::io::{self, prelude::*}; +use std::path; + +use bytesize::MB; +use env_logger; +use log; + +use ext_sort::{buffer::mem::MemoryLimitedBufferBuilder, ExternalSorter, ExternalSorterBuilder}; + +fn main() { + env_logger::Builder::new().filter_level(log::LevelFilter::Debug).init(); + + let input_reader = io::BufReader::new(fs::File::open("input.txt").unwrap()); + let mut output_writer = io::BufWriter::new(fs::File::create("output.txt").unwrap()); + + let sorter: ExternalSorter = ExternalSorterBuilder::new() + .with_tmp_dir(path::Path::new("./")) + .with_buffer(MemoryLimitedBufferBuilder::new(50 * MB)) + .build() + .unwrap(); + + let sorted = sorter.sort(input_reader.lines()).unwrap(); + + for item in sorted.map(Result::unwrap) { + output_writer.write_all(format!("{}\n", item).as_bytes()).unwrap(); } + output_writer.flush().unwrap(); +} ``` diff --git a/examples/quickstart.rs b/examples/quickstart.rs index 68a8cb9..9bfa113 100644 --- a/examples/quickstart.rs +++ b/examples/quickstart.rs @@ -6,8 +6,7 @@ use bytesize::MB; use env_logger; use log; -use ext_sort::buffer::mem::MemoryLimitedBufferBuilder; -use ext_sort::{ExternalSorter, ExternalSorterBuilder}; +use ext_sort::{buffer::mem::MemoryLimitedBufferBuilder, ExternalSorter, ExternalSorterBuilder}; fn main() { env_logger::Builder::new().filter_level(log::LevelFilter::Debug).init(); @@ -16,7 +15,7 @@ fn main() { let mut output_writer = io::BufWriter::new(fs::File::create("output.txt").unwrap()); let sorter: ExternalSorter = ExternalSorterBuilder::new() - .with_tmp_dir(path::Path::new("tmp")) + .with_tmp_dir(path::Path::new("./")) .with_buffer(MemoryLimitedBufferBuilder::new(50 * MB)) .build() .unwrap(); diff --git a/src/buffer.rs b/src/buffer.rs index 0a1e7f6..91dc458 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,33 +1,43 @@ -//! Limited buffer implementations. +//! Limited chunk buffer. use rayon; -/// Buffer builder. +/// Limited buffer builder. Creates buffers using provided buffer parameters. pub trait ChunkBufferBuilder: Default { + /// Building buffer type type Buffer: ChunkBuffer; - /// Creates a new buffer. + /// Creates a new [`ChunkBuffer`] trait instance. fn build(&self) -> Self::Buffer; } -/// Base limited buffer interface. +/// Base limited buffer interface. Provides methods for pushing data to the buffer and checking buffer state. pub trait ChunkBuffer: IntoIterator + rayon::slice::ParallelSliceMut + Send { /// Adds a new element to the buffer. + /// + /// # Arguments + /// * `item` - Item to be added to the buffer fn push(&mut self, item: T); - /// Returns buffer length + /// Returns the buffer length. fn len(&self) -> usize; /// Checks if the buffer reached the limit. fn is_full(&self) -> bool; } +/// [`LimitedBuffer`] builder. pub struct LimitedBufferBuilder { buffer_limit: usize, preallocate: bool, } impl LimitedBufferBuilder { + /// Creates a new instance of a builder. + /// + /// # Arguments + /// * `buffer_limit` - Buffer size limit in element count + /// * `preallocate` - If buffer should be preallocated pub fn new(buffer_limit: usize, preallocate: bool) -> Self { LimitedBufferBuilder { buffer_limit, @@ -64,6 +74,10 @@ pub struct LimitedBuffer { } impl LimitedBuffer { + /// Creates a new buffer instance. + /// + /// # Arguments + /// * `limit` - Buffer elements count limit pub fn new(limit: usize) -> Self { LimitedBuffer { limit, @@ -71,6 +85,10 @@ impl LimitedBuffer { } } + /// Creates a new buffer instance with provided capacity. + /// + /// # Arguments + /// * `limit` - Buffer elements count limit pub fn with_capacity(limit: usize) -> Self { LimitedBuffer { limit, @@ -134,11 +152,16 @@ pub mod mem { use super::{ChunkBuffer, ChunkBufferBuilder}; + /// [`MemoryLimitedBuffer`] builder. pub struct MemoryLimitedBufferBuilder { buffer_limit: u64, } impl MemoryLimitedBufferBuilder { + /// Creates a new instance of a builder. + /// + /// # Arguments + /// * `buffer_limit` - Buffer size limit in bytes pub fn new(buffer_limit: u64) -> Self { MemoryLimitedBufferBuilder { buffer_limit } } @@ -169,6 +192,10 @@ pub mod mem { } impl MemoryLimitedBuffer { + /// Creates a new instance of a buffer. + /// + /// # Arguments + /// * `limit` - Buffer size limit in bytes pub fn new(limit: u64) -> Self { MemoryLimitedBuffer { limit, @@ -177,6 +204,7 @@ pub mod mem { } } + /// Returns buffer size in bytes. pub fn mem_size(&self) -> u64 { self.current_size } diff --git a/src/chunk.rs b/src/chunk.rs index c85b8fc..fb6bdfe 100644 --- a/src/chunk.rs +++ b/src/chunk.rs @@ -1,3 +1,5 @@ +//! External chunk. + use std::error::Error; use std::fmt::{self, Display}; use std::fs; @@ -32,10 +34,17 @@ impl From for ExternalChunkError { /// External chunk interface. Provides methods for creating a chunk stored on file system and reading data from it. pub trait ExternalChunk: Sized + Iterator> { + /// Error returned when data serialization failed. type SerializationError: Error; + /// Error returned when data deserialization failed. type DeserializationError: Error; - /// Builds an instance of an external chunk. + /// Builds an instance of an external chunk creating file and dumping the items to it. + /// + /// # Arguments + /// * `dir` - Directory the chunk file is created in + /// * `items` - Items to be dumped to the chunk + /// * `buf_size` - File I/O buffer size fn build( dir: &tempfile::TempDir, items: impl IntoIterator, @@ -64,9 +73,16 @@ pub trait ExternalChunk: Sized + Iterator>) -> Self; /// Dumps items to an external file. + /// + /// # Arguments + /// * `chunk_writer` - The writer of the file the data should be dumped in + /// * `items` - Items to be dumped fn dump( chunk_writer: &mut io::BufWriter, items: impl IntoIterator, @@ -75,7 +91,17 @@ pub trait ExternalChunk: Sized + Iterator = ExternalChunk::build(&dir, (0..1000), None).unwrap(); +/// ``` pub struct RmpExternalChunk { reader: io::Take>, diff --git a/src/lib.rs b/src/lib.rs index fe7bc29..6806568 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,61 @@ -//! External sort algorithm implementation. External sorting is a class of sorting algorithms -//! that can handle massive amounts of data. External sorting is required when the data being -//! sorted do not fit into the main memory (RAM) of a computer and instead must be resided in -//! slower external memory, usually a hard disk drive. Sorting is achieved in two passes. -//! During the first pass it sorts chunks of data that each fit in RAM, during the second pass -//! it merges the sorted chunks together. -//! For more information see https://en.wikipedia.org/wiki/External_sorting. +//! `ext-sort` is a rust external sort algorithm implementation. +//! +//! External sorting is a class of sorting algorithms that can handle massive amounts of data. External sorting +//! is required when the data being sorted do not fit into the main memory (RAM) of a computer and instead must be +//! resided in slower external memory, usually a hard disk drive. Sorting is achieved in two passes. During the +//! first pass it sorts chunks of data that each fit in RAM, during the second pass it merges the sorted chunks +//! together. For more information see [External Sorting](https://en.wikipedia.org/wiki/External_sorting). +//! +//! # Overview +//! +//! `ext-sort` supports the following features: +//! +//! * **Data agnostic:** +//! it supports all data types that implement `serde` serialization/deserialization by default, +//! otherwise you can implement your own serialization/deserialization mechanism. +//! * **Serialization format agnostic:** +//! the library uses `MessagePack` serialization format by default, but it can be easily substituted by your custom +//! one if `MessagePack` serialization/deserialization performance is not sufficient for your task. +//! * **Multithreading support:** +//! multi-threaded sorting is supported, which means data is sorted in multiple threads utilizing maximum CPU +//! resources and reducing sorting time. +//! * **Memory limit support:** +//! memory limited sorting is supported. It allows you to limit sorting memory consumption +//! (`memory-limit` feature required). +//! +//! # Example +//! +//! ```no_run +//! use std::fs; +//! use std::io::{self, prelude::*}; +//! use std::path; +//! +//! use bytesize::MB; +//! use env_logger; +//! use log; +//! +//! use ext_sort::{buffer::mem::MemoryLimitedBufferBuilder, ExternalSorter, ExternalSorterBuilder}; +//! +//! fn main() { +//! env_logger::Builder::new().filter_level(log::LevelFilter::Debug).init(); +//! +//! let input_reader = io::BufReader::new(fs::File::open("input.txt").unwrap()); +//! let mut output_writer = io::BufWriter::new(fs::File::create("output.txt").unwrap()); +//! +//! let sorter: ExternalSorter = ExternalSorterBuilder::new() +//! .with_tmp_dir(path::Path::new("./")) +//! .with_buffer(MemoryLimitedBufferBuilder::new(50 * MB)) +//! .build() +//! .unwrap(); +//! +//! let sorted = sorter.sort(input_reader.lines()).unwrap(); +//! +//! for item in sorted.map(Result::unwrap) { +//! output_writer.write_all(format!("{}\n", item).as_bytes()).unwrap(); +//! } +//! output_writer.flush().unwrap(); +//! } +//! ``` pub mod buffer; pub mod chunk; diff --git a/src/merger.rs b/src/merger.rs index f356069..248400b 100644 --- a/src/merger.rs +++ b/src/merger.rs @@ -25,8 +25,11 @@ where E: Error, C: IntoIterator>, { - /// Creates an instance of binary heap merger using chunks as inputs. + /// Creates an instance of a binary heap merger using chunks as inputs. /// Chunk items should be sorted in ascending order otherwise the result is undefined. + /// + /// # Arguments + /// * `chunks` - Chunks to be merged in a single sorted one pub fn new(chunks: I) -> Self where I: IntoIterator, @@ -50,7 +53,7 @@ where { type Item = Result; - /// Returns the next item from inputs in ascending order. + /// Returns the next item from the inputs in ascending order. fn next(&mut self) -> Option { if !self.initiated { for (idx, chunk) in self.chunks.iter_mut().enumerate() { diff --git a/src/sort.rs b/src/sort.rs index a1d13c6..656db56 100644 --- a/src/sort.rs +++ b/src/sort.rs @@ -1,4 +1,4 @@ -//! External sorter implementation. +//! External sorter. use log; use std::error::Error; @@ -60,7 +60,7 @@ impl Display for SortError { } } -/// External sorter builder. +/// External sorter builder. Provides methods for [`ExternalSorter`] initialization. #[derive(Clone)] pub struct ExternalSorterBuilder> where @@ -93,12 +93,12 @@ where B: ChunkBufferBuilder, C: ExternalChunk, { - /// Creates an instance of builder with default parameters. + /// Creates an instance of a builder with default parameters. pub fn new() -> Self { ExternalSorterBuilder::default() } - /// Builds external sorter using provided configuration. + /// Builds an [`ExternalSorter`] instance using provided configuration. pub fn build( self, ) -> Result, SortError> { @@ -188,6 +188,14 @@ where C: ExternalChunk, { /// Creates a new external sorter instance. + /// + /// # Arguments + /// * `threads_number` - Number of threads to be used to sort data in parallel. If the parameter is [`None`] + /// threads number will be selected based on available CPU core number. + /// * `tmp_path` - Directory to be used to store temporary data. If paramater is [`None`] default OS temporary + /// directory will be used. + /// * `buffer_builder` - An instance of a buffer builder that will be used for chunk buffer creation. + /// * `rw_buf_size` - Chunks file read/write buffer size. pub fn new( threads_number: Option, tmp_path: Option<&Path>, @@ -239,6 +247,7 @@ where } /// Sorts data from input using external sort algorithm. + /// Returns an iterator that can be used to get sorted data stream. pub fn sort( &self, input: I, @@ -273,7 +282,6 @@ where return Ok(BinaryHeapMerger::new(external_chunks)); } - /// Sorts data and dumps it to an external chunk. fn create_chunk( &self, mut chunk: impl ChunkBuffer, From a065e5d520ec0b00c69dd0a1b5da7d12fe52b120 Mon Sep 17 00:00:00 2001 From: Dmitry Pershin Date: Sun, 9 Jan 2022 18:18:59 +0500 Subject: [PATCH 2/4] examples added. --- Cargo.toml | 8 +++ examples/custom_serializer.rs | 77 ++++++++++++++++++++++++++ examples/custom_type.rs | 100 ++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 examples/custom_serializer.rs create mode 100644 examples/custom_type.rs diff --git a/Cargo.toml b/Cargo.toml index b5cf452..78988c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,11 @@ memory-limit = ["deepsize"] [[example]] name = "quickstart" required-features = ["bytesize", "env_logger"] + +[[example]] +name = "custom_serializer" +required-features = ["env_logger"] + +[[example]] +name = "custom_type" +required-features = ["env_logger"] diff --git a/examples/custom_serializer.rs b/examples/custom_serializer.rs new file mode 100644 index 0000000..03c68d1 --- /dev/null +++ b/examples/custom_serializer.rs @@ -0,0 +1,77 @@ +use std::fs; +use std::fs::File; +use std::io::{self, prelude::*, BufReader, BufWriter, Take}; +use std::path; + +use env_logger; +use log; + +use ext_sort::{ExternalChunk, ExternalSorter, ExternalSorterBuilder, LimitedBufferBuilder}; + +struct CustomExternalChunk { + reader: io::Take>, +} + +impl ExternalChunk for CustomExternalChunk { + type SerializationError = io::Error; + type DeserializationError = io::Error; + + fn new(reader: Take>) -> Self { + CustomExternalChunk { reader } + } + + fn dump( + chunk_writer: &mut BufWriter, + items: impl IntoIterator, + ) -> Result<(), Self::SerializationError> { + for item in items { + chunk_writer.write_all(&item.to_le_bytes())?; + } + + return Ok(()); + } +} + +impl Iterator for CustomExternalChunk { + type Item = Result; + + fn next(&mut self) -> Option { + if self.reader.limit() == 0 { + None + } else { + let mut buf: [u8; 4] = [0; 4]; + match self.reader.read_exact(&mut buf.as_mut_slice()) { + Ok(_) => Some(Ok(u32::from_le_bytes(buf))), + Err(err) => Some(Err(err)), + } + } + } +} + +fn main() { + env_logger::Builder::new().filter_level(log::LevelFilter::Debug).init(); + + let input_reader = io::BufReader::new(fs::File::open("input.txt").unwrap()); + let mut output_writer = io::BufWriter::new(fs::File::create("output.txt").unwrap()); + + let sorter: ExternalSorter = + ExternalSorterBuilder::new() + .with_tmp_dir(path::Path::new("./")) + .with_buffer(LimitedBufferBuilder::new(1_000_000, true)) + .build() + .unwrap(); + + let sorted = sorter + .sort(input_reader.lines().map(|line| { + let line = line.unwrap(); + let number = line.parse().unwrap(); + + return Ok(number); + })) + .unwrap(); + + for item in sorted.map(Result::unwrap) { + output_writer.write_all(format!("{}\n", item).as_bytes()).unwrap(); + } + output_writer.flush().unwrap(); +} diff --git a/examples/custom_type.rs b/examples/custom_type.rs new file mode 100644 index 0000000..e73d2d4 --- /dev/null +++ b/examples/custom_type.rs @@ -0,0 +1,100 @@ +use std::cmp::Ordering; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::fs; +use std::io::{self, prelude::*}; +use std::path; + +use env_logger; +use log; +use serde; + +use ext_sort::{ExternalSorter, ExternalSorterBuilder, LimitedBufferBuilder}; + +#[derive(Debug)] +enum CsvParseError { + RowError(String), + ColumnError(String), +} + +impl Display for CsvParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CsvParseError::ColumnError(err) => write!(f, "column format error: {}", err), + CsvParseError::RowError(err) => write!(f, "row format error: {}", err), + } + } +} + +impl Error for CsvParseError {} + +#[derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)] +struct Person { + name: String, + surname: String, + age: u8, +} + +impl Person { + fn as_csv(&self) -> String { + format!("{},{},{}", self.name, self.surname, self.age) + } + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split(',').collect(); + if parts.len() != 3 { + Err(CsvParseError::RowError("wrong columns number".to_string())) + } else { + Ok(Person { + name: parts[0].to_string(), + surname: parts[1].to_string(), + age: parts[2] + .parse() + .map_err(|err| CsvParseError::ColumnError(format!("age field format error: {}", err)))?, + }) + } + } +} + +impl PartialOrd for Person { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(&other)) + } +} + +impl Ord for Person { + fn cmp(&self, other: &Self) -> Ordering { + self.surname + .cmp(&other.surname) + .then(self.name.cmp(&other.name)) + .then(self.age.cmp(&other.age)) + } +} + +fn main() { + env_logger::Builder::new().filter_level(log::LevelFilter::Debug).init(); + + let input_reader = io::BufReader::new(fs::File::open("input.csv").unwrap()); + let mut output_writer = io::BufWriter::new(fs::File::create("output.csv").unwrap()); + + let sorter: ExternalSorter = ExternalSorterBuilder::new() + .with_tmp_dir(path::Path::new("./")) + .with_buffer(LimitedBufferBuilder::new(1_000_000, true)) + .build() + .unwrap(); + + let sorted = sorter + .sort( + input_reader + .lines() + .map(|line| line.map(|line| Person::from_str(&line).unwrap())), + ) + .unwrap(); + + for item in sorted.map(Result::unwrap) { + output_writer + .write_all(format!("{}\n", item.as_csv()).as_bytes()) + .unwrap(); + } + output_writer.flush().unwrap(); +} From 6cc587ea4f797d2130f9126f7a458bec242f287c Mon Sep 17 00:00:00 2001 From: Dmitry Pershin Date: Sun, 9 Jan 2022 19:39:05 +0500 Subject: [PATCH 3/4] log dependency version fixed. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 78988c4..b6f66e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ keywords = ["algorithms", "sort", "sorting", "external-sort", "external"] bytesize = { version = "^1.1", optional = true } deepsize = { version = "^0.2", optional = true } env_logger = { version = "^0.9", optional = true} -log = "0.4" +log = "^0.4" rayon = "^1.5" rmp-serde = "^0.15" serde = { version = "^1.0", features = ["derive"] } From c5243d4e5ccc6f06a359b202081180bd4ee5efd8 Mon Sep 17 00:00:00 2001 From: Dmitry Pershin Date: Sun, 9 Jan 2022 19:43:39 +0500 Subject: [PATCH 4/4] bump version 0.1.1. --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b6f66e7..a99feec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ext-sort" -version = "0.1.0" +version = "0.1.1" edition = "2021" license = "Unlicense" description = "rust external sort algorithm implementation" diff --git a/README.md b/README.md index 0484790..f2ad6fd 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Activate `memory-limit` feature of the ext-sort crate on Cargo.toml: ```toml [dependencies] -ext-sort = { version = "^0.1.0", features = ["memory-limit"] } +ext-sort = { version = "^0.1.1", features = ["memory-limit"] } ``` ``` rust