@@ -159,10 +159,10 @@ use indicatif::ParallelProgressIterator;
159159use rayon:: prelude:: * ;
160160
161161pub mod cli;
162- #[ cfg( not( target_family = "wasm" ) ) ]
163- use crate :: cli:: * ;
164162#[ cfg( target_family = "wasm" ) ]
165163use crate :: cli:: InvertedQueryType ;
164+ #[ cfg( not( target_family = "wasm" ) ) ]
165+ use crate :: cli:: * ;
166166
167167#[ cfg( not( target_family = "wasm" ) ) ]
168168use crate :: hashing:: HashType ;
@@ -963,10 +963,32 @@ pub fn logw(text: &str, typ: Option<&str>) {
963963#[ wasm_bindgen]
964964/// Struct to interact with JS when working with WebAssembly
965965pub struct SketchlibData {
966- out_probs : Vec < ( f64 , usize ) > ,
966+ out_probs : Vec < ( u32 , usize ) > ,
967967 index : Inverted ,
968968}
969969
970+ #[ cfg( any( target_family = "wasm" , test) ) ]
971+ fn select_ranked_matches ( matches : & [ ( u32 , usize ) ] , nouts : usize ) -> Vec < ( u32 , usize , usize ) > {
972+ let mut selected = Vec :: new ( ) ;
973+ let mut current_rank = 0_usize ;
974+ let mut previous_matches: Option < u32 > = None ;
975+
976+ for ( match_count, index) in matches. iter ( ) . copied ( ) {
977+ if previous_matches. is_some_and ( |prev| match_count != prev) {
978+ current_rank += 1 ;
979+ }
980+ previous_matches = Some ( match_count) ;
981+
982+ if current_rank >= nouts {
983+ break ;
984+ }
985+
986+ selected. push ( ( match_count, index, current_rank) ) ;
987+ }
988+
989+ selected
990+ }
991+
970992#[ cfg( target_family = "wasm" ) ]
971993#[ wasm_bindgen]
972994impl SketchlibData {
@@ -995,7 +1017,11 @@ impl SketchlibData {
9951017 min_qual : u8 ,
9961018 ) {
9971019 let query_type = & InvertedQueryType :: MatchCount ;
998- let prop = if proportion_reads >= 1.0 { None } else { Some ( proportion_reads) } ;
1020+ let prop = if proportion_reads >= 1.0 {
1021+ None
1022+ } else {
1023+ Some ( proportion_reads)
1024+ } ;
9991025
10001026 // Get input files
10011027 let ( queries, _query_names) =
@@ -1016,17 +1042,13 @@ impl SketchlibData {
10161042 InvertedQueryType :: AnyBins => self . index . any_shared_bins ( queries[ 0 ] . as_slice ( ) ) ,
10171043 } ;
10181044
1019- let mut outvec: Vec < ( f64 , usize ) > = Vec :: with_capacity ( dist. len ( ) ) ;
1045+ let mut outvec: Vec < ( u32 , usize ) > = Vec :: with_capacity ( dist. len ( ) ) ;
10201046
10211047 for ( i, d) in dist. iter ( ) . enumerate ( ) {
1022- outvec. push ( (
1023- ( * d as f64 ) / ( ( 2 * self . index . sketch_size ( ) ) as f64 - * d as f64 ) ,
1024- i,
1025- ) ) ;
1048+ outvec. push ( ( * d, i) ) ;
10261049 }
10271050
1028- outvec. sort_by ( |a, b| a. 0 . partial_cmp ( & b. 0 ) . expect ( "NaN obtained!" ) ) ;
1029- outvec. reverse ( ) ;
1051+ outvec. sort_by ( |a, b| b. 0 . cmp ( & a. 0 ) . then_with ( || a. 1 . cmp ( & b. 1 ) ) ) ;
10301052
10311053 self . out_probs = outvec;
10321054 }
@@ -1044,17 +1066,27 @@ impl SketchlibData {
10441066 Some ( "info" ) ,
10451067 ) ;
10461068
1069+ let selected = select_ranked_matches ( & self . out_probs , nouts) ;
1070+
10471071 results[ "probs" ] = json:: JsonValue :: Array (
1048- self . out_probs
1072+ selected
10491073 . iter ( )
1050- . take ( nouts)
1051- . map ( |x| json:: JsonValue :: Number ( x. 0 . into ( ) ) )
1074+ . map ( |x| {
1075+ let matches = x. 0 as f64 ;
1076+ let similarity = matches / ( ( 2 * self . index . sketch_size ( ) ) as f64 - matches) ;
1077+ json:: JsonValue :: Number ( similarity. into ( ) )
1078+ } )
1079+ . collect ( ) ,
1080+ ) ;
1081+ results[ "ranks" ] = json:: JsonValue :: Array (
1082+ selected
1083+ . iter ( )
1084+ . map ( |x| json:: JsonValue :: Number ( ( x. 2 + 1 ) . into ( ) ) )
10521085 . collect ( ) ,
10531086 ) ;
10541087 results[ "names" ] = json:: JsonValue :: Array (
1055- self . out_probs
1088+ selected
10561089 . iter ( )
1057- . take ( nouts)
10581090 . map ( |x| {
10591091 if let Some ( labelsvec) = self . index . get_sample_labels ( ) {
10601092 json:: JsonValue :: String ( labelsvec[ x. 1 ] . clone ( ) )
@@ -1065,9 +1097,8 @@ impl SketchlibData {
10651097 . collect ( ) ,
10661098 ) ;
10671099 results[ "metadata" ] = json:: JsonValue :: Array (
1068- self . out_probs
1100+ selected
10691101 . iter ( )
1070- . take ( nouts)
10711102 . map ( |x| {
10721103 if let Some ( metadatavec) = self . index . get_metadata ( ) {
10731104 json:: JsonValue :: String ( metadatavec[ x. 1 ] . clone ( ) )
@@ -1083,3 +1114,41 @@ impl SketchlibData {
10831114 results. dump ( )
10841115 }
10851116}
1117+
1118+ #[ cfg( test) ]
1119+ mod wasm_result_ranking_tests {
1120+ use super :: select_ranked_matches;
1121+
1122+ #[ test]
1123+ fn returns_only_requested_rank_groups_without_ties ( ) {
1124+ let matches = vec ! [ ( 10 , 0 ) , ( 9 , 1 ) , ( 8 , 2 ) , ( 7 , 3 ) ] ;
1125+ assert_eq ! (
1126+ select_ranked_matches( & matches, 3 ) ,
1127+ vec![ ( 10 , 0 , 0 ) , ( 9 , 1 , 1 ) , ( 8 , 2 , 2 ) ]
1128+ ) ;
1129+ }
1130+
1131+ #[ test]
1132+ fn includes_all_entries_tied_at_first_rank ( ) {
1133+ let matches = vec ! [ ( 10 , 0 ) , ( 10 , 1 ) , ( 9 , 2 ) , ( 8 , 3 ) , ( 7 , 4 ) ] ;
1134+ assert_eq ! (
1135+ select_ranked_matches( & matches, 3 ) ,
1136+ vec![ ( 10 , 0 , 0 ) , ( 10 , 1 , 0 ) , ( 9 , 2 , 1 ) , ( 8 , 3 , 2 ) ]
1137+ ) ;
1138+ }
1139+
1140+ #[ test]
1141+ fn includes_all_entries_tied_at_later_ranks ( ) {
1142+ let matches = vec ! [ ( 10 , 0 ) , ( 9 , 1 ) , ( 9 , 2 ) , ( 8 , 3 ) , ( 8 , 4 ) , ( 7 , 5 ) ] ;
1143+ assert_eq ! (
1144+ select_ranked_matches( & matches, 3 ) ,
1145+ vec![ ( 10 , 0 , 0 ) , ( 9 , 1 , 1 ) , ( 9 , 2 , 1 ) , ( 8 , 3 , 2 ) , ( 8 , 4 , 2 ) ]
1146+ ) ;
1147+ }
1148+
1149+ #[ test]
1150+ fn get_probs_three_excludes_internal_rank_three ( ) {
1151+ let matches = vec ! [ ( 10 , 0 ) , ( 9 , 1 ) , ( 8 , 2 ) , ( 7 , 3 ) ] ;
1152+ assert ! ( !select_ranked_matches( & matches, 3 ) . contains( & ( 7 , 3 , 3 ) ) ) ;
1153+ }
1154+ }
0 commit comments