Build reverse index: token_id → list of (doc_id, position) pairs. * SEQUENTIAL (fast: just pointer arithmetic + flat array fill). */
| 1205 | /* Build reverse index: token_id → list of (doc_id, position) pairs. |
| 1206 | * SEQUENTIAL (fast: just pointer arithmetic + flat array fill). */ |
| 1207 | static reverse_index_t *build_reverse_index(cbm_sem_corpus_t *corpus) { |
| 1208 | reverse_index_t *rev = calloc(SKIP_ONE, sizeof(reverse_index_t)); |
| 1209 | if (!rev) { |
| 1210 | return NULL; |
| 1211 | } |
| 1212 | /* Phase A: count occurrences per token */ |
| 1213 | int *counts = calloc((size_t)corpus->entry_count + SKIP_ONE, sizeof(int)); |
| 1214 | if (!counts) { |
| 1215 | free(rev); |
| 1216 | return NULL; |
| 1217 | } |
| 1218 | long total = 0; |
| 1219 | for (int d = 0; d < corpus->doc_count; d++) { |
| 1220 | int *ids = corpus->doc_token_ids[d]; |
| 1221 | int len = corpus->doc_token_counts[d]; |
| 1222 | for (int i = 0; i < len; i++) { |
| 1223 | int tid = ids[i]; |
| 1224 | if (tid >= 0 && tid < corpus->entry_count) { |
| 1225 | counts[tid]++; |
| 1226 | total++; |
| 1227 | } |
| 1228 | } |
| 1229 | } |
| 1230 | /* Phase B: exclusive prefix sum → offsets[] */ |
| 1231 | rev->offsets = malloc(((size_t)corpus->entry_count + SKIP_ONE) * sizeof(int)); |
| 1232 | if (!rev->offsets) { |
| 1233 | free(counts); |
| 1234 | free(rev); |
| 1235 | return NULL; |
| 1236 | } |
| 1237 | int running = 0; |
| 1238 | for (int t = 0; t < corpus->entry_count; t++) { |
| 1239 | rev->offsets[t] = running; |
| 1240 | running += counts[t]; |
| 1241 | counts[t] = 0; /* reuse as per-token fill cursor */ |
| 1242 | } |
| 1243 | rev->offsets[corpus->entry_count] = running; |
| 1244 | /* Phase C: fill flat array. Ensure allocation size > 0 even for empty |
| 1245 | * corpora (avoids malloc(0) which is implementation-defined). */ |
| 1246 | size_t flat_bytes = (total > 0 ? (size_t)total : SKIP_ONE) * sizeof(cooccur_pos_t); |
| 1247 | rev->flat = malloc(flat_bytes); |
| 1248 | if (!rev->flat) { |
| 1249 | free(rev->offsets); |
| 1250 | free(counts); |
| 1251 | free(rev); |
| 1252 | return NULL; |
| 1253 | } |
| 1254 | for (int d = 0; d < corpus->doc_count; d++) { |
| 1255 | int *ids = corpus->doc_token_ids[d]; |
| 1256 | int len = corpus->doc_token_counts[d]; |
| 1257 | for (int i = 0; i < len; i++) { |
| 1258 | int tid = ids[i]; |
| 1259 | if (tid >= 0 && tid < corpus->entry_count) { |
| 1260 | int slot = rev->offsets[tid] + counts[tid]++; |
| 1261 | rev->flat[slot].doc_id = (int32_t)d; |
| 1262 | rev->flat[slot].pos = (int32_t)i; |
| 1263 | } |
| 1264 | } |
no outgoing calls
no test coverage detected