From bd5a715caeda14f69b1747c3747ea25e0d4ecdaf Mon Sep 17 00:00:00 2001 From: L <457124+liborty@users.noreply.github.com> Date: Wed, 20 Dec 2023 23:46:36 +1000 Subject: [PATCH] 3.0.3 --- Cargo.toml | 7 +- README.md | 2 + src/algos.rs | 312 +++++++++++++++++++----------------------------- src/error.rs | 21 +--- src/lib.rs | 33 +++-- src/oldalgos.rs | 60 ++++++++++ tests/tests.rs | 32 ++--- 7 files changed, 235 insertions(+), 232 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 437b353..178d353 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "medians" -version = "3.0.2" +version = "3.0.3" authors = ["Libor Spacek"] edition = "2021" description = "Median, Statistical Measures, Mathematics, Statistics" @@ -19,11 +19,14 @@ include = [ "README.md", "LICENSE" ] +[lints.rust] +unsafe_code = "forbid" +missing_docs = "warn" [badges] maintenance = { status = "actively-developed" } [lib] [dependencies] indxvec = "1.8" -ran = "1.1" [dev-dependencies] +ran = "2.0" times = "1.0" diff --git a/README.md b/README.md index 9cb97b0..043025c 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,8 @@ pub trait Median<'a, T> { ## Release Notes +**Version 3.0.3** - Updated dev dependency `ran` to 2.0. + **Version 3.0.2** - Added function `medianu8` that finds median byte by superfast radix search. More primitive types to follow. **Version 3.0.1** - Renamed `correlation` to `med_correlation` to avoid name clashes elsewhere. diff --git a/src/algos.rs b/src/algos.rs index 76669be..1bc20f0 100644 --- a/src/algos.rs +++ b/src/algos.rs @@ -4,7 +4,7 @@ use std::ops::Range; const FSIGN: u64 = 0x8000_0000_0000_0000; -/// Tests a slice of f64s for the presence of NANs +/// Scan a slice of f64s for being free of NANs pub fn no_nans(v: &[f64]) -> bool { for &f in v { if f.is_nan() { @@ -56,31 +56,97 @@ pub fn ref_vec(v: &[T], rng: Range) -> Vec<&T> { v.iter().take(rng.end).skip(rng.start).collect() } -/// Insert logsort of refs within [&T]. -/// Use for large types T, as they do not get copied here. +/// Builds Vec from refs in Vec<&T> (inverse of ref_vec()) +pub fn deref_vec(v: &[&T]) -> Vec +where T:Clone { + v.iter().map(|&x| x.clone()).collect() +} + +/// Insert logsort of refs (within range). +/// Use for large types T, they do not get copied. /// Pass in reversed comparator for descending sort. -pub fn isort_refs(s: &mut [&T], rng: Range, c: impl Fn(&T, &T) -> Ordering) { - if s.len() < 2 { - return; +pub fn isort_refs(s: &[T], rng: Range, c: impl Fn(&T, &T) -> Ordering) -> Vec<&T> { + match rng.len() { + 0 => return vec![], + 1 => return vec![&s[rng.start];1], + _ => () }; - if c(s[rng.start + 1], s[rng.start]) == Less { - s.swap(rng.start, rng.start + 1); + // build a mutable vec of refs + let mut rv:Vec<&T> = s.iter().take(rng.end).skip(rng.start).collect(); + if c(rv[rng.start + 1], rv[rng.start]) == Less { + rv.swap(rng.start, rng.start + 1); }; - for i in rng.start + 2..rng.end { + for i in rng.start+2..rng.end { // first two already swapped - if c(s[i], s[i - 1]) != Less { + if c(rv[i], rv[i - 1]) != Less { continue; - } // s[i] item is already in order - let thisref = s[i]; - let insert = match s[rng.start..i - 1].binary_search_by(|&j| c(j, thisref)) { + } // rv[i] item is already in order + let thisref = rv[i]; + let insert = match rv[rng.start..i - 1].binary_search_by(|&j| c(j, thisref)) { Ok(ins) => ins + 1, Err(ins) => ins, }; - s.copy_within(insert..i, insert + 1); - s[insert] = thisref; + rv.copy_within(insert..i, insert + 1); + rv[insert] = thisref; } + rv } +/// middle valued ref out of three, at most three comparisons +pub fn midof3<'a, T>(item1: &'a T, item2: &'a T, item3: &'a T, c: &mut impl FnMut(&T, &T) -> Ordering) -> &'a T +{ + let (min, max) = if c(item2,item1) == Less { + (item2, item1) + } else { + (item1, item2) + }; + if c(min,item3) != Less { + return min; + }; + if c(item3,max) != Less { + return max; + }; + item3 +} + +/* +/// pivot estimate as recursive mid of mids of three +pub fn midofmids<'a, T>(s: &[&T], rng: Range, c: &mut impl FnMut(&T, &T) -> Ordering) -> &'a T +where + T: PartialOrd, +{ + if s.len() < 3 { return s[0]; }; + for i in + let (min, max) = if *item1 <= *item2 { + (item1, item2) + } else { + (item2, item1) + }; + if *item3 <= *min { + return min; + }; + if *item3 <= *max { + return item3; + }; + max +} + +/// Mid two values of four refs. Makes four comparisons +fn mid2( + idx0: usize, + idx1: usize, + idx2: usize, + idx3: usize, + c: &mut impl FnMut(usize, usize) -> Ordering, +) -> (usize,usize) { + let (min1,max1) = if c(idx0, idx1) == Less { (idx0,idx1) } else { (idx1,idx0) }; + let (min2,max2) = if c(idx2, idx3) == Less { (idx2,idx3) } else { (idx3,idx2) }; + let mid1 = if c(min1, min2) == Less { min2 } else { min1 }; + let mid2 = if c(max1, max2) == Less { max1 } else { max2 }; + (mid1,mid2) +} +*/ + /// Index of the middling value of four refs. Makes only three comparisons fn middling( idx0: usize, @@ -236,6 +302,47 @@ pub fn evenmedianu8(s: &[u8]) -> f64 { if res == f64::MIN { resbucket as f64 } else { (res + resbucket as f64)/ 2.0 } } +/// Odd median of `&[u16]` +pub fn oddmedianu16(s: &[u16]) -> f64 { + let need = s.len() / 2; // median target position + let mut histogram = [0_usize; 65536]; + let mut cummulator = 0_usize; + let mut resbucket = 0_u16; + for &u in s.iter() { + histogram[u as usize] += 1; + }; + for &hist in histogram.iter() { + cummulator += hist; + if need < cummulator { break; }; + resbucket += 1; + }; + resbucket as f64 +} + +/// Even median of `&[u16]` +pub fn evenmedianu16(s: &[u16]) -> f64 { + let mut need = s.len() / 2; // first median target position + let mut histogram = [0_usize; 65536]; + let mut cummulator = 0_usize; + let mut resbucket = 0_u16; + let mut res = f64::MIN; + for &u in s.iter() { + histogram[u as usize] += 1; + } + for &hist in histogram.iter() { + cummulator += hist; + if need < cummulator { break; }; // cummulator exceeds need, found at least two items + if need == cummulator { // the last (possibly only) item in this bucket + if res == f64::MIN { // is the first median + res = resbucket as f64; // save it + need += 1; // next look for the second one (in the following buckets) + } else { break; }; // this item is already the second median + }; + resbucket += 1; + continue; + }; + if res == f64::MIN { resbucket as f64 } else { (res + resbucket as f64)/ 2.0 } +} /// Median of odd sized generic data with Odering comparisons by custom closure pub fn oddmedian_by<'a, T>(s: &mut [&'a T], c: &mut impl FnMut(&T, &T) -> Ordering) -> &'a T { let mut rng = 0..s.len(); @@ -337,178 +444,3 @@ pub fn evenmedian_by<'a, T>( rng.start = gtsub; } } - -/* -/// Partitions mutable set s within rng by pivot value. -/// The reordering is done in a single pass, with minimal comparisons. -/// Returns a triple of subscripts to new s: `(gtstart, mid, ltend)`. -/// The count of items equal to pivot is: `(gtstart-rng.start) + (rng.end-ltend)`. -/// Items greater than pivot are in range (gtstart,mid) -/// Items less than pivot are in range (mid,ltend). -/// Any of these four resulting sub-slices may be empty. -pub fn part(s: &mut [&T], rng: &Range, pivot: &T) -> (usize, usize, usize) -where - T: PartialOrd, -{ - let mut startsub = rng.start; - let mut gtsub = startsub; - let mut ltsub = rng.end - 1; - let mut endsub = rng.end - 1; - loop { - while s[gtsub] > pivot { - if gtsub == ltsub { - return (startsub, 1 + gtsub, 1 + endsub); - }; - gtsub += 1; - } - if s[gtsub] == pivot { - s[gtsub] = s[startsub]; - if gtsub == ltsub { - return (1 + startsub, 1 + gtsub, 1 + endsub); - }; - startsub += 1; - gtsub += 1; - continue; - }; - 'lt: loop { - if s[ltsub] < pivot { - ltsub -= 1; - // s[gtsub] here is already known to be lt pivot, so assign it to lt set - if gtsub >= ltsub { - return (startsub, gtsub, 1 + endsub); - }; - continue 'lt; - } - if s[ltsub] == pivot { - s[ltsub] = s[endsub]; - ltsub -= 1; - if gtsub >= ltsub { - return (startsub, gtsub, endsub); - }; - endsub -= 1; - continue 'lt; - }; - break 'lt; - } - s.swap(ltsub, gtsub); - gtsub += 1; - ltsub -= 1; - if gtsub > ltsub { - return (startsub, gtsub, 1 + endsub); - }; - } -} - -/// Median of odd sized generic data with Odering comparisons by custom closure -pub fn oddmedian_by<'a, T>(s: &mut [&'a T], c: &mut impl FnMut(&T, &T) -> Ordering) -> &'a T { -{ - let mut rng = 0..set.len(); - let mut need = set.len() / 2; // need as subscript - let mut s: Vec<&T> = set.iter().collect(); - loop { - // Take a sample from start,mid,end of data and use their midpoint as a pivot - let pivot = part_sort4(&mut[s[rng.start],s[rng.start+1],s[rng.end-2],s[rng.end-1]],c); - let (gtsub, ltsub, ltend) = part(&mut s, &rng, pivot); - // somewhere within ltset, iterate on it - if need + ltsub - rng.start + 2 < ltend { - need += ltsub - rng.start; - rng.start = ltsub; - rng.end = ltend; - continue; - } - // when need is within reach of the end of ltset, we have a solution: - if need + ltsub - rng.start < rng.end { - // jump over geset, which was placed at the beginning - need += ltsub - rng.start; - if need + 2 == ltend { - return max2(&s, ltsub..ltend).0; - }; - if need + 1 == ltend { - return max(&s, ltsub..ltend); - }; - // else need is in the end equals set (need >= ltend) - return pivot; - }; - // geset was placed at the beginning, so reduce need by leset - need -= rng.end - ltsub; - // somewhere within gtset, iterate on it - if need > gtsub+1 { - rng.start = gtsub; - rng.end = ltsub; - continue; - } - // here need is within reach of the beginning of the ge set, we have a solution: - // does it fall within the first equals set? - if need < gtsub { - return pivot; - }; - if need == gtsub { - return min(&s, gtsub..ltsub); - }; - // else need == gtsub + 1 - return min2(&s, gtsub..ltsub).1; - } -} - -/// Median of even sized generic data with Odering comparisons by custom closure -pub fn evenmedian_by<'a, T>( - s: &mut [&'a T], - c: &mut impl FnMut(&T, &T) -> Ordering, -) -> (&'a T, &'a T) { - let mut rng = 0..set.len(); - let mut need = set.len() / 2 - 1; // need as subscript - 1 - let mut s: Vec<&T> = set.iter().collect(); - loop { - let pivot = part_sort4(&mut[s[rng.start],s[rng.start+1],s[rng.end-2],s[rng.end-1]],c); - // let pivot = midof3(s[rng.start], s[(rng.start + rng.end) / 2], s[rng.end - 1]); - let (gtsub, ltsub, ltend) = part(&mut s, &rng, pivot); - // need falls somewhere within ltset, iterate on it - if need + ltsub - rng.start + 2 < ltend { - need += ltsub - rng.start; - rng.start = ltsub; - rng.end = ltend; - continue; - }; - // if need is within reach of the end of ltset, we have a solution: - if need + ltsub - rng.start < rng.end { - // jump over geset, which was placed at the beginning - need += ltsub - rng.start; - if need + 2 == ltend { - return max2(&s, ltsub..ltend); - }; - // there will always be at least one item equal to pivot and therefore it is the minimum of the ge set - if need + 1 == ltend { - return (max(&s, ltsub..ltend), pivot); - }; - // need is within the equals sets (need >= ltend) - let eqend = rng.end-1+gtsub-rng.start; - if need < eqend { return (pivot,pivot); }; - if need == eqend { - if gtsub > rng.start { return (pivot,pivot); } - else { return (pivot,min(&s, gtsub..ltsub)); } - }; - }; - // geset was placed at the beginning, so reduce need by leset - need -= rng.end - ltsub; - // somewhere within gtset, iterate on it - if need+1 > gtsub { - rng.start = gtsub; - rng.end = ltsub; - continue; - }; - // need is within reach of the beginning of the ge set, we have a solution: - // is need in the first equals set? - if need+1 < gtsub { - return (pivot,pivot); - }; - // last of the first equals set - if need+1 == gtsub { - return (pivot, min(&s, gtsub..ltsub)); - }; - // first of the gtset - if need == gtsub { - return min2(&s, gtsub..ltsub); - }; - } -} -*/ diff --git a/src/error.rs b/src/error.rs index 21d7964..c91843e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,6 @@ use std::error::Error; use std::fmt; use std::fmt::{Debug, Display}; -use crate::Me; -use ran::Re; #[derive(Debug)] /// custom error @@ -30,20 +28,13 @@ where } } -/// Automatically converting RanError to MedError::OtherError -impl From for Me { - fn from(e: Re) -> Self { - MedError::Other(format!("RanError: {e}")) - } -} - -/// Convenience function for building RanError +/// Convenience function for building MedError /// from error kind name and payload message, which can be either &str or String -pub fn merror(kind: &str, msg: impl Into) -> Me { +pub fn merror(kind: &str, msg: impl Into) -> Result> { match kind { - "size" => MedError::Size(msg.into()), - "nan" => MedError::Nan(msg.into()), - "other" => MedError::Other(msg.into()), - _ => MedError::Other("Wrong error kind given to merror".into()) + "size" => Err(MedError::Size(msg.into())), + "nan" => Err(MedError::Nan(msg.into())), + "other" => Err(MedError::Other(msg.into())), + _ => Err(MedError::Other("Wrong error kind given to merror".into())) } } diff --git a/src/lib.rs b/src/lib.rs index 70dbd1b..30ba27e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![warn(missing_docs)] +// #![warn(missing_docs)] // #![feature(slice_swap_unchecked)] //! Fast new algorithms for computing medians of @@ -12,7 +12,7 @@ pub mod algos; pub mod error; use crate::algos::*; -use crate::error::{merror, MedError}; +pub use crate::error::{merror, MedError}; use core::{cmp::Ordering, fmt::Display}; use indxvec::printing::{GR, UN, YL}; @@ -47,7 +47,7 @@ where pub fn medianu8(s:&[u8]) -> Result { let n = s.len(); match n { - 0 => return Err(merror("size", "median: zero length data")), + 0 => return merror("size", "median: zero length data")?, 1 => return Ok(s[0] as f64), 2 => return Ok((s[0] as f64 + s[1] as f64) / 2.0), _ => (), @@ -59,6 +59,21 @@ pub fn medianu8(s:&[u8]) -> Result { } } +/// Median of primitive type u16 by fast radix search +pub fn medianu16(s:&[u16]) -> Result { + let n = s.len(); + match n { + 0 => return merror("size", "median: zero length data"), + 1 => return Ok(s[0] as f64), + 2 => return Ok((s[0] as f64 + s[1] as f64) / 2.0), + _ => (), + }; + if (n & 1) == 1 { + Ok(oddmedianu16(s)) + } else { + Ok(evenmedianu16(s)) + } +} /// Fast 1D medians of floating point data, plus related methods pub trait Medianf64 { /// Median of f64s, checked for NaNs @@ -102,7 +117,7 @@ impl Medianf64 for &[f64] { fn medf_checked(self) -> Result { let n = self.len(); match n { - 0 => return Err(merror("size", "medf_checked: zero length data")), + 0 => return merror("size", "medf_checked: zero length data"), 1 => return Ok(self[0]), 2 => return Ok((self[0] + self[1]) / 2.0), _ => (), @@ -111,7 +126,7 @@ impl Medianf64 for &[f64] { .iter() .map(|x| { if x.is_nan() { - Err(merror("Nan", "medf_checked: Nan in input!")) + merror("Nan", "medf_checked: Nan in input!") } else { Ok(x) } @@ -170,7 +185,7 @@ impl Medianf64 for &[f64] { .sum(); let res = sxy / (sx2 * sy2).sqrt(); if res.is_nan() { - Err(merror("Nan", "medf_correlation: Nan result!")) + merror("Nan", "medf_correlation: Nan result!") } else { Ok(res) } @@ -201,7 +216,7 @@ impl<'a, T> Median<'a, T> for &'a [T] { ) -> Result { let n = self.len(); match n { - 0 => return Err(merror("size", "qmedian_by: zero length data")), + 0 => return merror("size", "qmedian_by: zero length data"), 1 => return Ok(q(&self[0])), 2 => return Ok((q(&self[0]) + q(&self[1])) / 2.0), _ => (), @@ -219,7 +234,7 @@ impl<'a, T> Median<'a, T> for &'a [T] { fn median_by(self, c: &mut impl FnMut(&T, &T) -> Ordering) -> Result, Me> { let n = self.len(); match n { - 0 => return Err(merror("size", "median_ord: zero length data")), + 0 => return merror("size", "median_ord: zero length data"), 1 => return Ok(Medians::Odd(&self[0])), 2 => return Ok(Medians::Even((&self[0], &self[1]))), _ => (), @@ -271,7 +286,7 @@ impl<'a, T> Median<'a, T> for &'a [T] { .sum(); let res = sxy / (sx2 * sy2).sqrt(); if res.is_nan() { - Err(merror("Nan", "correlation: Nan result!")) + merror("Nan", "correlation: Nan result!") } else { Ok(res) } diff --git a/src/oldalgos.rs b/src/oldalgos.rs index c856594..fbf58a6 100644 --- a/src/oldalgos.rs +++ b/src/oldalgos.rs @@ -112,6 +112,66 @@ pub fn ord_vec(v: &[T]) -> Vec> { } /* +/// Partitions mutable set s within rng by pivot value. +/// The reordering is done in a single pass, with minimal comparisons. +/// Returns a triple of subscripts to new s: `(gtstart, mid, ltend)`. +/// The count of items equal to pivot is: `(gtstart-rng.start) + (rng.end-ltend)`. +/// Items greater than pivot are in range (gtstart,mid) +/// Items less than pivot are in range (mid,ltend). +/// Any of these four resulting sub-slices may be empty. +pub fn part(s: &mut [&T], rng: &Range, pivot: &T) -> (usize, usize, usize) +where + T: PartialOrd, +{ + let mut startsub = rng.start; + let mut gtsub = startsub; + let mut ltsub = rng.end - 1; + let mut endsub = rng.end - 1; + loop { + while s[gtsub] > pivot { + if gtsub == ltsub { + return (startsub, 1 + gtsub, 1 + endsub); + }; + gtsub += 1; + } + if s[gtsub] == pivot { + s[gtsub] = s[startsub]; + if gtsub == ltsub { + return (1 + startsub, 1 + gtsub, 1 + endsub); + }; + startsub += 1; + gtsub += 1; + continue; + }; + 'lt: loop { + if s[ltsub] < pivot { + ltsub -= 1; + // s[gtsub] here is already known to be lt pivot, so assign it to lt set + if gtsub >= ltsub { + return (startsub, gtsub, 1 + endsub); + }; + continue 'lt; + } + if s[ltsub] == pivot { + s[ltsub] = s[endsub]; + ltsub -= 1; + if gtsub >= ltsub { + return (startsub, gtsub, endsub); + }; + endsub -= 1; + continue 'lt; + }; + break 'lt; + } + s.swap(ltsub, gtsub); + gtsub += 1; + ltsub -= 1; + if gtsub > ltsub { + return (startsub, gtsub, 1 + endsub); + }; + } +} + /// Subscripts of minimum and maximum locations within rng in slice s pub fn minmax( s: &[T], diff --git a/tests/tests.rs b/tests/tests.rs index 4f01028..87ebc41 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -2,15 +2,17 @@ #![allow(dead_code)] #[cfg(test)] use indxvec::{here, printing::*, Indices, Mutops, Printing, Vecops}; -use medians::algos::{scrub_nans, to_u64s, to_f64s, qbalance, part, ref_vec, min, min2, isort_refs}; +use medians::algos::{scrub_nans, to_u64s, to_f64s, qbalance, part, ref_vec, deref_vec, min, min2, isort_refs}; // use medians::algosf64::partord; -use medians::{Me, medianu8, Median, Medianf64}; -use ran::{generators::*, *}; +use medians::{Me, merror, medianu8, Median, Medianf64}; +use ran:: *; // use std::io::{stdout,Write}; use std::convert::From; +use std::error::Error; use core::cmp::{Ordering,Ordering::*}; use times::{benchf64, benchu64, benchu8, mutbenchf64}; + #[test] fn parting() -> Result<(), Me> { let data = [ @@ -37,8 +39,8 @@ fn parting() -> Result<(), Me> { (gtsub - eqsub).yl(), data[0].yl() ); - refdata.mutisort(0..len, |a,b| a.total_cmp(b) ); - println!("isort_copy ascending sorted:\n{}",refdata.gr()); + let refindex = isort_refs(&data,0..len, |a,b| a.total_cmp(b)); + println!("isort_refs ascending sorted:\n{}",deref_vec(&refindex).gr()); let indx = data.isort_indexed(0..len, |a,b| b.total_cmp(a)); println!("isort_index (descending):\n{}",indx.gr()); println!("Unindexed:\n{}",indx.unindex(&data,true).gr()); @@ -99,9 +101,8 @@ fn medf64() { #[test] fn correlation() -> Result<(), Me> { - let rv = Rnum::newf64(); - let v1 = rv.ranv(100).expect("Random vec genertion failed").getvf64()?; // random vector - let v2 = rv.ranv(100).expect("Random vec genertion failed").getvf64()?; // random vector + let v1 = ranv_f64(100).expect("Random vec1 generation failed"); // random vector + let v2 = ranv_f64(100).expect("Random vec2 generation failed"); // random vector println!("medf_correlation: {}",v1.medf_correlation(&v2)?.gr()); Ok(()) } @@ -110,23 +111,22 @@ fn correlation() -> Result<(), Me> { fn errors() -> Result<(), Me> { let n = 10_usize; // number of vectors to test for each magnitude // set_seeds(33333); - let rv = Rnum::newu8(); for d in [10, 50, 100, 1000, 10000, 100000] { let mut error = 0_i64; trait Eq: PartialEq {} impl Eq for f64 {} for _ in 0..n { - let v = rv.ranv(d).expect("Random vec genertion failed").getvu8()?; // random vector - let med = medianu8(&v)?; - // v - //.as_slice() - //.medf_unchecked(); + let Ok(v) = ranv_u8(d) else { + return merror("other","Random vec genertion failed"); }; + let med = medianu8(&v)?; // random vector + // v.as_slice().medf_unchecked(); error += qbalance(&v, &med, |&f| f as f64); } println!("Even length {GR}{d}{UN}, repeats: {GR}{n}{UN}, errors: {GR}{error}{UN}"); error = 0_i64; for _ in 0..n { - let v = rv.ranv(d + 1).expect("Random vec genertion failed").getvu8()?; // random vector + let Ok(v) = ranv_u8(d + 1) else { + return merror("other","Random vec genertion failed"); }; // random vector let med = medianu8(&v)?; // v // .as_slice() @@ -179,5 +179,5 @@ const CLOSURESU8: [fn(&[u8]); 4] = [ fn comparison() { // set_seeds(0); // intialise random numbers generator // Rnum encapsulates the type of random data to be generated - benchu8(Rnum::newu8(), 2..5000, 100, 10, &NAMES, &CLOSURESU8); + benchu8(3..5000, 100, 10, &NAMES, &CLOSURESU8); }