use std::clone::Clone;
use std::cmp::Ordering::{self, Less};
use std::collections::VecDeque;
use std::error::Error;
use std::fs::{File, OpenOptions};
use std::io::SeekFrom::Start;
use std::io::{BufRead, BufReader, Seek, Write};
use std::marker::PhantomData;
use std::path::PathBuf;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
use tempdir::TempDir;
pub trait ExternallySortable: Ord + Clone + Serialize + DeserializeOwned {
fn get_size(&self) -> u64;
}
pub struct ExtSortedIterator<T> {
buffers: Vec<VecDeque<T>>,
chunk_offsets: Vec<u64>,
max_per_chunk: u64,
chunks: u64,
tmp_dir: TempDir,
sort_by_fn: Box<dyn FnMut(&T, &T) -> Ordering>,
failed: bool,
}
impl<T> Iterator for ExtSortedIterator<T>
where
T: ExternallySortable,
{
type Item = Result<T, Box<dyn Error>>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed {
return None;
}
let mut empty = true;
for chunk_num in 0..self.chunks {
if self.buffers[chunk_num as usize].is_empty() {
let mut f = match File::open(self.tmp_dir.path().join(chunk_num.to_string())) {
Ok(f) => f,
Err(e) => {
self.failed = true;
return Some(Err(Box::new(e)));
}
};
match f.seek(Start(self.chunk_offsets[chunk_num as usize])) {
Ok(_) => (),
Err(e) => {
self.failed = true;
return Some(Err(Box::new(e)));
}
}
let bytes_read =
match fill_buff(&mut self.buffers[chunk_num as usize], f, self.max_per_chunk) {
Ok(bytes_read) => bytes_read,
Err(e) => {
self.failed = true;
return Some(Err(e));
}
};
self.chunk_offsets[chunk_num as usize] += bytes_read;
if !self.buffers[chunk_num as usize].is_empty() {
empty = false;
}
} else {
empty = false;
}
}
if empty {
return None;
}
let mut idx = 0;
for chunk_num in 0..self.chunks as usize {
if !self.buffers[chunk_num].is_empty() {
if self.buffers[idx].is_empty()
|| (self.sort_by_fn)(
self.buffers[chunk_num].front().unwrap(),
self.buffers[idx].front().unwrap(),
) == Less
{
idx = chunk_num;
}
}
}
let r = self.buffers[idx].pop_front().unwrap();
Some(Ok(r))
}
}
pub struct ExternalSorter<T>
where
T: ExternallySortable,
{
tmp_dir: Option<PathBuf>,
buffer_bytes: u64,
phantom: PhantomData<T>,
}
impl<T> ExternalSorter<T>
where
T: ExternallySortable,
{
pub fn new(buffer_bytes: u64, tmp_dir: Option<PathBuf>) -> ExternalSorter<T> {
ExternalSorter {
buffer_bytes,
tmp_dir,
phantom: PhantomData,
}
}
pub fn sort<I>(&self, unsorted: I) -> Result<ExtSortedIterator<T>, Box<dyn Error>>
where
I: Iterator<Item = T>,
{
self.sort_by(unsorted, |a, b| a.cmp(b))
}
pub fn sort_by<I, F>(&self, unsorted: I, compare: F) -> Result<ExtSortedIterator<T>, Box<dyn Error>>
where
I: Iterator<Item = T>,
F: 'static + FnMut(&T, &T) -> Ordering,
{
let tmp_dir = match self.tmp_dir {
Some(ref p) => TempDir::new_in(p, "sort_fasta")?,
None => TempDir::new("sort_fasta")?,
};
let mut iter = ExtSortedIterator {
buffers: Vec::new(),
chunk_offsets: Vec::new(),
max_per_chunk: 0,
chunks: 0,
tmp_dir,
sort_by_fn: Box::new(compare),
failed: false,
};
{
let mut total_read = 0;
let mut chunk = Vec::new();
for seq in unsorted {
total_read += seq.get_size();
chunk.push(seq);
if total_read >= self.buffer_bytes {
chunk.sort_by(|a, b| (iter.sort_by_fn)(a, b));
self.write_chunk(
&iter.tmp_dir.path().join(iter.chunks.to_string()),
&mut chunk,
)?;
chunk.clear();
total_read = 0;
iter.chunks += 1;
}
}
if chunk.len() > 0 {
chunk.sort_by(|a, b| (iter.sort_by_fn)(a, b));
self.write_chunk(
&iter.tmp_dir.path().join(iter.chunks.to_string()),
&mut chunk,
)?;
iter.chunks += 1;
}
iter.max_per_chunk = self.buffer_bytes / iter.chunks;
iter.buffers = vec![VecDeque::new(); iter.chunks as usize];
iter.chunk_offsets = vec![0 as u64; iter.chunks as usize];
for chunk_num in 0..iter.chunks {
let offset = fill_buff(
&mut iter.buffers[chunk_num as usize],
File::open(iter.tmp_dir.path().join(chunk_num.to_string()))?,
iter.max_per_chunk,
)?;
iter.chunk_offsets[chunk_num as usize] = offset;
}
}
Ok(iter)
}
fn write_chunk(&self, file: &PathBuf, chunk: &mut Vec<T>) -> Result<(), Box<dyn Error>> {
let mut new_file = OpenOptions::new().create(true).append(true).open(file)?;
for s in chunk {
let mut serialized = serde_json::to_string(&s)?;
serialized.push_str("\n");
new_file.write_all(serialized.as_bytes())?;
}
Ok(())
}
}
fn fill_buff<T>(vec: &mut VecDeque<T>, file: File, max_bytes: u64) -> Result<u64, Box<dyn Error>>
where
T: ExternallySortable,
{
let mut total_read = 0;
let mut bytes_read = 0;
for line in BufReader::new(file).lines() {
let line_s = line?;
bytes_read += line_s.len() + 1;
let deserialized: T = serde_json::from_str(&line_s)?;
total_read += deserialized.get_size();
vec.push_back(deserialized);
if total_read > max_bytes {
break;
}
}
Ok(bytes_read as u64)
}