@@ -23,7 +23,7 @@ mod parse;
2323mod write;
2424
2525use crate :: parse:: parse_xid_properties;
26- use std:: collections:: { BTreeMap as Map , VecDeque } ;
26+ use std:: collections:: BTreeMap as Map ;
2727use std:: fs;
2828use std:: io:: { self , Write } ;
2929use std:: path:: Path ;
@@ -86,58 +86,120 @@ fn main() {
8686 index_continue. pop ( ) ;
8787 }
8888
89- let mut halfchunkmap = Map :: new ( ) ;
90- for chunk in & dense {
91- let mut front = [ 0u8 ; CHUNK / 2 ] ;
92- let mut back = [ 0u8 ; CHUNK / 2 ] ;
93- front. copy_from_slice ( & chunk[ ..CHUNK / 2 ] ) ;
94- back. copy_from_slice ( & chunk[ CHUNK / 2 ..] ) ;
95- halfchunkmap
96- . entry ( front)
97- . or_insert_with ( VecDeque :: new)
98- . push_back ( back) ;
89+ // Compress the LEAF array by overlapping chunks at half-chunk boundaries.
90+ //
91+ // If chunk i's back half equals chunk j's front half, placing them
92+ // adjacently saves 32 bytes. We find the maximum number of such overlaps
93+ // by modeling this as a bipartite matching problem (left side = back
94+ // halves, right side = front halves) and solving with Kuhn's algorithm.
95+
96+ let num_chunks = dense. len ( ) ;
97+
98+ let front_of: Vec < [ u8 ; CHUNK / 2 ] > = dense
99+ . iter ( )
100+ . map ( |c| c[ ..CHUNK / 2 ] . try_into ( ) . unwrap ( ) )
101+ . collect ( ) ;
102+ let back_of: Vec < [ u8 ; CHUNK / 2 ] > = dense
103+ . iter ( )
104+ . map ( |c| c[ CHUNK / 2 ..] . try_into ( ) . unwrap ( ) )
105+ . collect ( ) ;
106+
107+ // Build index from front-half value to chunk indices for efficient lookup.
108+ let mut chunks_by_front: Map < [ u8 ; CHUNK / 2 ] , Vec < usize > > = Map :: new ( ) ;
109+ for ( j, front) in front_of. iter ( ) . enumerate ( ) {
110+ chunks_by_front. entry ( * front) . or_default ( ) . push ( j) ;
111+ }
112+
113+ // adj_list[i] = chunks whose front half matches chunk i's back half,
114+ // meaning they can follow chunk i with a 32-byte overlap. Exclude
115+ // self-edges (the all-zeros and all-ones chunks have front == back).
116+ let adj_list: Vec < Vec < usize > > = ( 0 ..num_chunks)
117+ . map ( |i| {
118+ chunks_by_front
119+ . get ( & back_of[ i] )
120+ . map ( |js| js. iter ( ) . copied ( ) . filter ( |& j| j != i) . collect ( ) )
121+ . unwrap_or_default ( )
122+ } )
123+ . collect ( ) ;
124+
125+ // Maximum bipartite matching via Kuhn's algorithm (augmenting paths).
126+ // prev_of[j] = Some(i) means chunk i is matched to precede chunk j.
127+ let mut prev_of: Vec < Option < usize > > = vec ! [ None ; num_chunks] ;
128+
129+ // DFS for an augmenting path from `src`. If found, augments the matching
130+ // in-place (rehoming existing matches to preserve validity) and returns true.
131+ fn try_kuhn (
132+ src : usize ,
133+ adj_list : & [ Vec < usize > ] ,
134+ visited : & mut [ bool ] ,
135+ prev_of : & mut [ Option < usize > ] ,
136+ ) -> bool {
137+ for & dst in & adj_list[ src] {
138+ if !visited[ dst] {
139+ visited[ dst] = true ;
140+ // If dst is free, or its current match can be rehomed, claim dst.
141+ if prev_of[ dst] . is_none_or ( |prev| try_kuhn ( prev, adj_list, visited, prev_of) ) {
142+ prev_of[ dst] = Some ( src) ;
143+ return true ;
144+ }
145+ }
146+ }
147+ false
148+ }
149+
150+ // Try every left vertex. A failed attempt stays failed because later
151+ // rounds only shrink the set of free right vertices (Berge's theorem).
152+ for i in 0 ..num_chunks {
153+ let mut visited = vec ! [ false ; num_chunks] ;
154+ try_kuhn ( i, & adj_list, & mut visited, & mut prev_of) ;
155+ }
156+
157+ // Invert the matching into a forward map for chain traversal.
158+ let mut next_of: Vec < Option < usize > > = vec ! [ None ; num_chunks] ;
159+ for ( j, & prev) in prev_of. iter ( ) . enumerate ( ) {
160+ if let Some ( prev) = prev {
161+ next_of[ prev] = Some ( j) ;
162+ }
163+ }
164+
165+ // Chunk 0 (all zeros) is special and must be laid out first at halfdense position 0,
166+ // because the runtime defaults to index 0 for codepoints beyond the trie.
167+ // Remove any incoming edge so chunk 0 becomes a chain start.
168+ if let Some ( prev) = prev_of[ 0 ] {
169+ next_of[ prev] = None ;
170+ prev_of[ 0 ] = None ;
99171 }
100172
173+ // Lay out chains into halfdense, starting with chunk 0's chain.
101174 let mut halfdense = Vec :: < u8 > :: new ( ) ;
102175 let mut dense_to_halfdense = Map :: < u8 , u8 > :: new ( ) ;
103- for chunk in & dense {
104- let original_pos = chunkmap[ chunk] ;
105- if dense_to_halfdense. contains_key ( & original_pos) {
106- continue ;
107- }
108- let mut front = [ 0u8 ; CHUNK / 2 ] ;
109- let mut back = [ 0u8 ; CHUNK / 2 ] ;
110- front. copy_from_slice ( & chunk[ ..CHUNK / 2 ] ) ;
111- back. copy_from_slice ( & chunk[ CHUNK / 2 ..] ) ;
176+
177+ for start in ( 0 ..num_chunks) . filter ( |& i| prev_of[ i] . is_none ( ) ) {
112178 dense_to_halfdense. insert (
113- original_pos,
114- match u8:: try_from ( halfdense. len ( ) / ( CHUNK / 2 ) ) {
115- Ok ( byte) => byte,
116- Err ( _) => panic ! ( "exceeded 256 half-chunks" ) ,
117- } ,
179+ start as u8 ,
180+ u8:: try_from ( halfdense. len ( ) / ( CHUNK / 2 ) ) . expect ( "exceeded 256 half-chunks" ) ,
118181 ) ;
119- halfdense. extend_from_slice ( & front) ;
120- halfdense. extend_from_slice ( & back) ;
121- while let Some ( next) = halfchunkmap. get_mut ( & back) . and_then ( VecDeque :: pop_front) {
122- let mut concat = empty_chunk;
123- concat[ ..CHUNK / 2 ] . copy_from_slice ( & back) ;
124- concat[ CHUNK / 2 ..] . copy_from_slice ( & next) ;
125- let original_pos = chunkmap[ & concat] ;
126- if dense_to_halfdense. contains_key ( & original_pos) {
127- continue ;
128- }
182+ halfdense. extend_from_slice ( & front_of[ start] ) ;
183+ halfdense. extend_from_slice ( & back_of[ start] ) ;
184+
185+ // Write the rest of the chain: each chunk's front half overlaps the
186+ // previous chunk's back half, so only append the back half.
187+ let mut curr = start;
188+ while let Some ( next) = next_of[ curr] {
129189 dense_to_halfdense. insert (
130- original_pos,
131- match u8:: try_from ( halfdense. len ( ) / ( CHUNK / 2 ) - 1 ) {
132- Ok ( byte) => byte,
133- Err ( _) => panic ! ( "exceeded 256 half-chunks" ) ,
134- } ,
190+ next as u8 ,
191+ u8:: try_from ( halfdense. len ( ) / ( CHUNK / 2 ) - 1 ) . expect ( "exceeded 256 half-chunks" ) ,
135192 ) ;
136- halfdense. extend_from_slice ( & next) ;
137- back = next;
193+ halfdense. extend_from_slice ( & back_of [ next] ) ;
194+ curr = next;
138195 }
139196 }
140197
198+ // Each chunk can be both a predecessor (back half) and a successor
199+ // (front half), so next_of can form cycles with no chain start.
200+ // We broke chunk 0's cycle above; verify no others exist.
201+ assert_eq ! ( dense_to_halfdense. len( ) , num_chunks, "not all chunks were laid out" ) ;
202+
141203 for index in & mut index_start {
142204 * index = dense_to_halfdense[ index] ;
143205 }
0 commit comments