4 * Suffix array match-finder for LZ (Lempel-Ziv) compression.
8 * Copyright (c) 2013 Eric Biggers. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 #ifndef _WIMLIB_LZ_SARRAY_H
35 #define _WIMLIB_LZ_SARRAY_H
37 #include "wimlib/compiler.h"
38 #include "wimlib/lz.h"
39 #include "wimlib/types.h"
43 /* Suffix array LZ (Lempel-Ziv) match-finder. */
45 /* Allocated window size for the match-finder. */
46 input_idx_t max_window_size;
48 /* Minimum match length to return. */
49 input_idx_t min_match_len;
51 /* Maximum match length to return. */
52 input_idx_t max_match_len;
54 /* Maximum matches to consider at each position (max search depth). */
55 u32 max_matches_to_consider;
57 /* Maximum number of matches to return at each position. */
58 u32 max_matches_to_return;
60 /* Current position in the window */
63 /* Current window size. */
64 input_idx_t window_size;
66 /* Suffix array for window.
67 * This is a mapping from suffix rank to suffix position. */
70 /* Inverse suffix array for window.
71 * This is a mapping from suffix position to suffix rank.
72 * If 0 <= r < window_size, then ISA[SA[r]] == r. */
75 /* Longest common prefix array corresponding to the suffix array SA.
76 * LCP[i] is the length of the longest common prefix between the
77 * suffixes with positions SA[i - 1] and SA[i]. LCP[0] is undefined.
81 /* Suffix array links.
83 * During a linear scan of the input string to find matches, this array
84 * used to keep track of which rank suffixes in the suffix array appear
85 * before the current position. Instead of searching in the original
86 * suffix array, scans for matches at a given position traverse a linked
87 * list containing only suffixes that appear before that position. */
88 struct salink *salink;
91 /* Suffix array link */
93 /* Rank of highest ranked suffix that has rank lower than the suffix
94 * corresponding to this structure and either has a lower position
95 * (initially) or has a position lower than the highest position at
96 * which matches have been searched for so far, or -1 if there is no
100 /* Rank of lowest ranked suffix that has rank greater than the suffix
101 * corresponding to this structure and either has a lower position
102 * (intially) or has a position lower than the highest position at which
103 * matches have been searched for so far, or -1 if there is no such
107 /* Length of longest common prefix between the suffix corresponding to
108 * this structure and the suffix with rank @prev, or 0 if @prev is -1.
112 /* Length of longest common prefix between the suffix corresponding to
113 * this structure and the suffix with rank @next, or 0 if @next is -1.
119 lz_sarray_init(struct lz_sarray *mf,
120 input_idx_t max_window_size,
121 input_idx_t min_match_len,
122 input_idx_t max_match_len,
123 u32 max_matches_to_consider,
124 u32 max_matches_to_return);
127 lz_sarray_destroy(struct lz_sarray *mf);
130 lz_sarray_load_window(struct lz_sarray *mf, const u8 window[],
131 input_idx_t window_size);
133 static inline input_idx_t
134 lz_sarray_get_pos(const struct lz_sarray *mf)
139 /* Advance the suffix array match-finder to the next position. */
140 static _always_inline_attribute void
141 lz_sarray_update_salink(const input_idx_t i,
142 const input_idx_t SA[const restrict],
143 const input_idx_t ISA[const restrict],
144 struct salink link[const restrict])
146 /* r = Rank of the suffix at the current position. */
147 const input_idx_t r = ISA[i];
149 /* next = rank of LOWEST ranked suffix that is ranked HIGHER than the
150 * current suffix AND has a LOWER position, or -1 if none exists. */
151 const input_idx_t next = link[r].next;
153 /* prev = rank of HIGHEST ranked suffix that is ranked LOWER than the
154 * current suffix AND has a LOWER position, or -1 if none exists. */
155 const input_idx_t prev = link[r].prev;
157 /* Link the suffix at the current position into the linked list that
158 * contains all suffixes in the suffix array that are appear at or
159 * before the current position, sorted by rank.
161 * Save the values of all fields we overwrite so that rollback is
163 if (next != ~(input_idx_t)0) {
166 link[next].lcpprev = link[r].lcpnext;
169 if (prev != ~(input_idx_t)0) {
172 link[prev].lcpnext = link[r].lcpprev;
176 /* Skip the current position in the suffix array match-finder. */
177 static _always_inline_attribute void
178 lz_sarray_skip_position(struct lz_sarray *mf)
180 LZ_ASSERT(mf->cur_pos < mf->window_size);
181 lz_sarray_update_salink(mf->cur_pos++, mf->SA, mf->ISA, mf->salink);
184 typedef input_idx_t lz_sarray_cost_t;
185 #define LZ_SARRAY_INFINITE_COST (~(lz_sarray_cost_t)0)
188 * Use the suffix array match-finder to retrieve a list of LZ matches at the
191 * Returns the number of matches written into @matches. The matches are
192 * returned in decreasing order by length, and each will be of unique length
193 * between the minimum and maximum match lengths passed to lz_sarray_init(). Up
194 * to @max_matches_to_return (passed to lz_sarray_init()) matches will be
197 * @eval_match_cost is a function for evaluating the cost of a match when
198 * deciding which ones to return. It is only used for comparing matches of the
199 * same length. It needs to be fast, and need not be exact; an implementation
200 * might simply rank matches by their offset, for example, although
201 * implementations may choose to take into account additional information such
204 static _always_inline_attribute u32
205 lz_sarray_get_matches(struct lz_sarray *mf,
206 struct raw_match matches[],
207 lz_sarray_cost_t (*eval_match_cost)
211 const void *eval_match_cost_ctx)
213 LZ_ASSERT(mf->cur_pos < mf->window_size);
214 const input_idx_t i = mf->cur_pos++;
216 const input_idx_t * const restrict SA = mf->SA;
217 const input_idx_t * const restrict ISA = mf->ISA;
218 struct salink * const restrict link = mf->salink;
219 const input_idx_t min_match_len = mf->min_match_len;
220 const u32 max_matches_to_consider = mf->max_matches_to_consider;
221 const u32 max_matches_to_return = mf->max_matches_to_return;
223 /* r = Rank of the suffix at the current position. */
224 const input_idx_t r = ISA[i];
226 /* Prepare for searching the current position. */
227 lz_sarray_update_salink(i, SA, ISA, link);
229 /* L = rank of next suffix to the left;
230 * R = rank of next suffix to the right;
231 * lenL = length of match between current position and the suffix with rank L;
232 * lenR = length of match between current position and the suffix with rank R.
234 * This is left and right relative to the rank of the current suffix.
235 * Since the suffixes in the suffix array are sorted, the longest
236 * matches are immediately to the left and right (using the linked list
237 * to ignore all suffixes that occur later in the window). The match
238 * length decreases the farther left and right we go. We shall keep the
239 * length on both sides in sync in order to choose the lowest-cost match
242 input_idx_t L = link[r].prev;
243 input_idx_t R = link[r].next;
244 input_idx_t lenL = link[r].lcpprev;
245 input_idx_t lenR = link[r].lcpnext;
247 /* nmatches = number of matches found so far. */
250 /* best_cost = cost of lowest-cost match found so far.
252 * We keep track of this so that we can ignore shorter matches that do
253 * not have lower costs than a longer matches already found.
255 lz_sarray_cost_t best_cost = LZ_SARRAY_INFINITE_COST;
257 /* count_remaining = maximum number of possible matches remaining to be
259 u32 count_remaining = max_matches_to_consider;
261 /* pending = match currently being considered for a specific length. */
262 struct raw_match pending;
263 lz_sarray_cost_t pending_cost;
265 while (lenL >= min_match_len || lenR >= min_match_len)
268 pending_cost = LZ_SARRAY_INFINITE_COST;
269 lz_sarray_cost_t cost;
272 if (lenL >= min_match_len && lenL >= lenR) {
275 if (--count_remaining == 0)
276 goto out_save_pending;
278 input_idx_t offset = i - SA[L];
280 /* Save match if it has smaller cost. */
281 cost = (*eval_match_cost)(lenL, offset,
282 eval_match_cost_ctx);
283 if (cost < pending_cost) {
284 pending.offset = offset;
288 if (link[L].lcpprev < lenL) {
289 /* Match length decreased. */
291 lenL = link[L].lcpprev;
293 /* Save the pending match unless the
294 * right side still may have matches of
295 * this length to be scanned, or if a
296 * previous (longer) match had lower
298 if (pending.len > lenR) {
299 if (pending_cost < best_cost) {
300 best_cost = pending_cost;
301 matches[nmatches++] = pending;
302 if (nmatches == max_matches_to_return)
306 pending_cost = LZ_SARRAY_INFINITE_COST;
308 if (lenL < min_match_len || lenL < lenR)
318 if (lenR >= min_match_len && lenR > lenL) {
321 if (--count_remaining == 0)
322 goto out_save_pending;
324 input_idx_t offset = i - SA[R];
326 /* Save match if it has smaller cost. */
327 cost = (*eval_match_cost)(lenR,
329 eval_match_cost_ctx);
330 if (cost < pending_cost) {
331 pending.offset = offset;
335 if (link[R].lcpnext < lenR) {
336 /* Match length decreased. */
338 lenR = link[R].lcpnext;
340 /* Save the pending match unless a
341 * previous (longer) match had lower
343 if (pending_cost < best_cost) {
344 matches[nmatches++] = pending;
345 best_cost = pending_cost;
346 if (nmatches == max_matches_to_return)
350 if (lenR < min_match_len || lenR <= lenL)
354 pending_cost = LZ_SARRAY_INFINITE_COST;
363 if (pending_cost != LZ_SARRAY_INFINITE_COST)
364 matches[nmatches++] = pending;
370 #endif /* _WIMLIB_LZ_SARRAY_H */