Avoid use of VLA and reduce memory usage in match to O(m)
| ... | ... |
@@ -1,7 +1,7 @@ |
| 1 | 1 |
VERSION=1.0 |
| 2 | 2 |
|
| 3 | 3 |
CPPFLAGS=-DVERSION=\"${VERSION}\" -D_GNU_SOURCE
|
| 4 |
-CFLAGS+=-Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps |
|
| 4 |
+CFLAGS+=-Wall -Wextra -g -std=c99 -O3 -pedantic -Ideps -Werror=vla |
|
| 5 | 5 |
PREFIX?=/usr/local |
| 6 | 6 |
MANDIR?=$(PREFIX)/share/man |
| 7 | 7 |
BINDIR?=$(PREFIX)/bin |
| ... | ... |
@@ -4,6 +4,7 @@ |
| 4 | 4 |
#include <stdio.h> |
| 5 | 5 |
#include <float.h> |
| 6 | 6 |
#include <math.h> |
| 7 |
+#include <stdlib.h> |
|
| 7 | 8 |
|
| 8 | 9 |
#include "match.h" |
| 9 | 10 |
#include "bonus.h" |
| ... | ... |
@@ -27,122 +28,177 @@ int has_match(const char *needle, const char *haystack) {
|
| 27 | 28 |
return 1; |
| 28 | 29 |
} |
| 29 | 30 |
|
| 31 |
+#define SWAP(x, y, T) do { T SWAP = x; x = y; y = SWAP; } while (0)
|
|
| 32 |
+ |
|
| 30 | 33 |
#define max(a, b) (((a) > (b)) ? (a) : (b)) |
| 31 | 34 |
|
| 32 |
-#ifdef DEBUG_VERBOSE |
|
| 33 |
-/* print one of the internal matrices */ |
|
| 34 |
-void mat_print(score_t *mat, char name, const char *needle, const char *haystack) {
|
|
| 35 |
- int n = strlen(needle); |
|
| 36 |
- int m = strlen(haystack); |
|
| 37 |
- int i, j; |
|
| 38 |
- fprintf(stderr, "%c ", name); |
|
| 39 |
- for (j = 0; j < m; j++) {
|
|
| 40 |
- fprintf(stderr, " %c", haystack[j]); |
|
| 41 |
- } |
|
| 42 |
- fprintf(stderr, "\n"); |
|
| 43 |
- for (i = 0; i < n; i++) {
|
|
| 44 |
- fprintf(stderr, " %c |", needle[i]); |
|
| 45 |
- for (j = 0; j < m; j++) {
|
|
| 46 |
- score_t val = mat[i * m + j]; |
|
| 47 |
- if (val == SCORE_MIN) {
|
|
| 48 |
- fprintf(stderr, " -\u221E"); |
|
| 49 |
- } else {
|
|
| 50 |
- fprintf(stderr, " %.3f", val); |
|
| 51 |
- } |
|
| 52 |
- } |
|
| 53 |
- fprintf(stderr, "\n"); |
|
| 54 |
- } |
|
| 55 |
- fprintf(stderr, "\n\n"); |
|
| 56 |
-} |
|
| 57 |
-#endif |
|
| 35 |
+struct match_struct {
|
|
| 36 |
+ int needle_len; |
|
| 37 |
+ int haystack_len; |
|
| 38 |
+ |
|
| 39 |
+ char lower_needle[MATCH_MAX_LEN]; |
|
| 40 |
+ char lower_haystack[MATCH_MAX_LEN]; |
|
| 41 |
+ |
|
| 42 |
+ score_t match_bonus[MATCH_MAX_LEN]; |
|
| 43 |
+}; |
|
| 58 | 44 |
|
| 59 | 45 |
static void precompute_bonus(const char *haystack, score_t *match_bonus) {
|
| 60 | 46 |
/* Which positions are beginning of words */ |
| 61 |
- int m = strlen(haystack); |
|
| 62 | 47 |
char last_ch = '/'; |
| 63 |
- for (int i = 0; i < m; i++) {
|
|
| 48 |
+ for (int i = 0; haystack[i]; i++) {
|
|
| 64 | 49 |
char ch = haystack[i]; |
| 65 | 50 |
match_bonus[i] = COMPUTE_BONUS(last_ch, ch); |
| 66 | 51 |
last_ch = ch; |
| 67 | 52 |
} |
| 68 | 53 |
} |
| 69 | 54 |
|
| 70 |
-score_t match_positions(const char *needle, const char *haystack, size_t *positions) {
|
|
| 55 |
+static void setup_match_struct(struct match_struct *match, const char *needle, const char *haystack) {
|
|
| 56 |
+ match->needle_len = strlen(needle); |
|
| 57 |
+ match->haystack_len = strlen(haystack); |
|
| 58 |
+ |
|
| 59 |
+ if (match->needle_len > MATCH_MAX_LEN || match->needle_len > match->haystack_len) {
|
|
| 60 |
+ return; |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ for (int i = 0; i < match->needle_len; i++) |
|
| 64 |
+ match->lower_needle[i] = tolower(needle[i]); |
|
| 65 |
+ |
|
| 66 |
+ for (int i = 0; i < match->haystack_len; i++) |
|
| 67 |
+ match->lower_haystack[i] = tolower(haystack[i]); |
|
| 68 |
+ |
|
| 69 |
+ precompute_bonus(haystack, match->match_bonus); |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+static inline void match_row(const struct match_struct *match, int row, score_t *curr_D, score_t *curr_M, const score_t *last_D, const score_t *last_M) {
|
|
| 73 |
+ int n = match->needle_len; |
|
| 74 |
+ int m = match->haystack_len; |
|
| 75 |
+ int i = row; |
|
| 76 |
+ |
|
| 77 |
+ const char *lower_needle = match->lower_needle; |
|
| 78 |
+ const char *lower_haystack = match->lower_haystack; |
|
| 79 |
+ const score_t *match_bonus = match->match_bonus; |
|
| 80 |
+ |
|
| 81 |
+ score_t prev_score = SCORE_MIN; |
|
| 82 |
+ score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER; |
|
| 83 |
+ |
|
| 84 |
+ for (int j = 0; j < m; j++) {
|
|
| 85 |
+ if (lower_needle[i] == lower_haystack[j]) {
|
|
| 86 |
+ score_t score = SCORE_MIN; |
|
| 87 |
+ if (!i) {
|
|
| 88 |
+ score = (j * SCORE_GAP_LEADING) + match_bonus[j]; |
|
| 89 |
+ } else if (j) { /* i > 0 && j > 0*/
|
|
| 90 |
+ score = max( |
|
| 91 |
+ last_M[j - 1] + match_bonus[j], |
|
| 92 |
+ |
|
| 93 |
+ /* consecutive match, doesn't stack with match_bonus */ |
|
| 94 |
+ last_D[j - 1] + SCORE_MATCH_CONSECUTIVE); |
|
| 95 |
+ } |
|
| 96 |
+ curr_D[j] = score; |
|
| 97 |
+ curr_M[j] = prev_score = max(score, prev_score + gap_score); |
|
| 98 |
+ } else {
|
|
| 99 |
+ curr_D[j] = SCORE_MIN; |
|
| 100 |
+ curr_M[j] = prev_score = prev_score + gap_score; |
|
| 101 |
+ } |
|
| 102 |
+ } |
|
| 103 |
+} |
|
| 104 |
+ |
|
| 105 |
+score_t match(const char *needle, const char *haystack) {
|
|
| 71 | 106 |
if (!*needle) |
| 72 | 107 |
return SCORE_MIN; |
| 73 | 108 |
|
| 74 |
- int n = strlen(needle); |
|
| 75 |
- int m = strlen(haystack); |
|
| 109 |
+ struct match_struct match; |
|
| 110 |
+ setup_match_struct(&match, needle, haystack); |
|
| 76 | 111 |
|
| 77 |
- if (n == m) {
|
|
| 112 |
+ int n = match.needle_len; |
|
| 113 |
+ int m = match.haystack_len; |
|
| 114 |
+ |
|
| 115 |
+ if (m > MATCH_MAX_LEN || n > m) {
|
|
| 116 |
+ /* |
|
| 117 |
+ * Unreasonably large candidate: return no score |
|
| 118 |
+ * If it is a valid match it will still be returned, it will |
|
| 119 |
+ * just be ranked below any reasonably sized candidates |
|
| 120 |
+ */ |
|
| 121 |
+ return SCORE_MIN; |
|
| 122 |
+ } else if (n == m) {
|
|
| 78 | 123 |
/* Since this method can only be called with a haystack which |
| 79 | 124 |
* matches needle. If the lengths of the strings are equal the |
| 80 | 125 |
* strings themselves must also be equal (ignoring case). |
| 81 | 126 |
*/ |
| 82 |
- if (positions) |
|
| 83 |
- for (int i = 0; i < n; i++) |
|
| 84 |
- positions[i] = i; |
|
| 85 | 127 |
return SCORE_MAX; |
| 86 | 128 |
} |
| 87 | 129 |
|
| 88 |
- if (m > 1024) {
|
|
| 130 |
+ /* |
|
| 131 |
+ * D[][] Stores the best score for this position ending with a match. |
|
| 132 |
+ * M[][] Stores the best possible score at this position. |
|
| 133 |
+ */ |
|
| 134 |
+ score_t D[2][MATCH_MAX_LEN], M[2][MATCH_MAX_LEN]; |
|
| 135 |
+ |
|
| 136 |
+ score_t *last_D, *last_M; |
|
| 137 |
+ score_t *curr_D, *curr_M; |
|
| 138 |
+ |
|
| 139 |
+ last_D = D[0]; |
|
| 140 |
+ last_M = M[0]; |
|
| 141 |
+ curr_D = D[1]; |
|
| 142 |
+ curr_M = M[1]; |
|
| 143 |
+ |
|
| 144 |
+ for (int i = 0; i < n; i++) {
|
|
| 145 |
+ match_row(&match, i, curr_D, curr_M, last_D, last_M); |
|
| 146 |
+ |
|
| 147 |
+ SWAP(curr_D, last_D, score_t *); |
|
| 148 |
+ SWAP(curr_M, last_M, score_t *); |
|
| 149 |
+ } |
|
| 150 |
+ |
|
| 151 |
+ return last_M[m - 1]; |
|
| 152 |
+} |
|
| 153 |
+ |
|
| 154 |
+score_t match_positions(const char *needle, const char *haystack, size_t *positions) {
|
|
| 155 |
+ if (!*needle) |
|
| 156 |
+ return SCORE_MIN; |
|
| 157 |
+ |
|
| 158 |
+ struct match_struct match; |
|
| 159 |
+ setup_match_struct(&match, needle, haystack); |
|
| 160 |
+ |
|
| 161 |
+ int n = match.needle_len; |
|
| 162 |
+ int m = match.haystack_len; |
|
| 163 |
+ |
|
| 164 |
+ if (m > MATCH_MAX_LEN || n > m) {
|
|
| 89 | 165 |
/* |
| 90 | 166 |
* Unreasonably large candidate: return no score |
| 91 | 167 |
* If it is a valid match it will still be returned, it will |
| 92 | 168 |
* just be ranked below any reasonably sized candidates |
| 93 | 169 |
*/ |
| 94 | 170 |
return SCORE_MIN; |
| 171 |
+ } else if (n == m) {
|
|
| 172 |
+ /* Since this method can only be called with a haystack which |
|
| 173 |
+ * matches needle. If the lengths of the strings are equal the |
|
| 174 |
+ * strings themselves must also be equal (ignoring case). |
|
| 175 |
+ */ |
|
| 176 |
+ if (positions) |
|
| 177 |
+ for (int i = 0; i < n; i++) |
|
| 178 |
+ positions[i] = i; |
|
| 179 |
+ return SCORE_MAX; |
|
| 95 | 180 |
} |
| 96 | 181 |
|
| 97 |
- char lower_needle[n]; |
|
| 98 |
- char lower_haystack[m]; |
|
| 99 |
- |
|
| 100 |
- for (int i = 0; i < n; i++) |
|
| 101 |
- lower_needle[i] = tolower(needle[i]); |
|
| 102 |
- |
|
| 103 |
- for (int i = 0; i < m; i++) |
|
| 104 |
- lower_haystack[i] = tolower(haystack[i]); |
|
| 105 |
- |
|
| 106 |
- score_t match_bonus[m]; |
|
| 107 |
- score_t D[n][m], M[n][m]; |
|
| 108 |
- |
|
| 109 | 182 |
/* |
| 110 | 183 |
* D[][] Stores the best score for this position ending with a match. |
| 111 | 184 |
* M[][] Stores the best possible score at this position. |
| 112 | 185 |
*/ |
| 113 |
- precompute_bonus(haystack, match_bonus); |
|
| 186 |
+ score_t (*D)[MATCH_MAX_LEN], (*M)[MATCH_MAX_LEN]; |
|
| 187 |
+ M = malloc(sizeof(score_t) * MATCH_MAX_LEN * n); |
|
| 188 |
+ D = malloc(sizeof(score_t) * MATCH_MAX_LEN * n); |
|
| 189 |
+ |
|
| 190 |
+ score_t *last_D, *last_M; |
|
| 191 |
+ score_t *curr_D, *curr_M; |
|
| 114 | 192 |
|
| 115 | 193 |
for (int i = 0; i < n; i++) {
|
| 116 |
- score_t prev_score = SCORE_MIN; |
|
| 117 |
- score_t gap_score = i == n - 1 ? SCORE_GAP_TRAILING : SCORE_GAP_INNER; |
|
| 118 |
- |
|
| 119 |
- for (int j = 0; j < m; j++) {
|
|
| 120 |
- if (lower_needle[i] == lower_haystack[j]) {
|
|
| 121 |
- score_t score = SCORE_MIN; |
|
| 122 |
- if (!i) {
|
|
| 123 |
- score = (j * SCORE_GAP_LEADING) + match_bonus[j]; |
|
| 124 |
- } else if (j) { /* i > 0 && j > 0*/
|
|
| 125 |
- score = max( |
|
| 126 |
- M[i - 1][j - 1] + match_bonus[j], |
|
| 127 |
- |
|
| 128 |
- /* consecutive match, doesn't stack with match_bonus */ |
|
| 129 |
- D[i - 1][j - 1] + SCORE_MATCH_CONSECUTIVE); |
|
| 130 |
- } |
|
| 131 |
- D[i][j] = score; |
|
| 132 |
- M[i][j] = prev_score = max(score, prev_score + gap_score); |
|
| 133 |
- } else {
|
|
| 134 |
- D[i][j] = SCORE_MIN; |
|
| 135 |
- M[i][j] = prev_score = prev_score + gap_score; |
|
| 136 |
- } |
|
| 137 |
- } |
|
| 138 |
- } |
|
| 194 |
+ curr_D = &D[i][0]; |
|
| 195 |
+ curr_M = &M[i][0]; |
|
| 139 | 196 |
|
| 140 |
-#ifdef DEBUG_VERBOSE |
|
| 141 |
- fprintf(stderr, "\"%s\" =~ \"%s\"\n", needle, haystack); |
|
| 142 |
- mat_print(&D[0][0], 'D', needle, haystack); |
|
| 143 |
- mat_print(&M[0][0], 'M', needle, haystack); |
|
| 144 |
- fprintf(stderr, "\n"); |
|
| 145 |
-#endif |
|
| 197 |
+ match_row(&match, i, curr_D, curr_M, last_D, last_M); |
|
| 198 |
+ |
|
| 199 |
+ last_D = curr_D; |
|
| 200 |
+ last_M = curr_M; |
|
| 201 |
+ } |
|
| 146 | 202 |
|
| 147 | 203 |
/* backtrace to find the positions of optimal matching */ |
| 148 | 204 |
if (positions) {
|
| ... | ... |
@@ -173,9 +229,10 @@ score_t match_positions(const char *needle, const char *haystack, size_t *positi |
| 173 | 229 |
} |
| 174 | 230 |
} |
| 175 | 231 |
|
| 176 |
- return M[n - 1][m - 1]; |
|
| 177 |
-} |
|
| 232 |
+ score_t result = M[n - 1][m - 1]; |
|
| 178 | 233 |
|
| 179 |
-score_t match(const char *needle, const char *haystack) {
|
|
| 180 |
- return match_positions(needle, haystack, NULL); |
|
| 234 |
+ free(M); |
|
| 235 |
+ free(D); |
|
| 236 |
+ |
|
| 237 |
+ return result; |
|
| 181 | 238 |
} |
| ... | ... |
@@ -7,6 +7,8 @@ typedef double score_t; |
| 7 | 7 |
#define SCORE_MAX INFINITY |
| 8 | 8 |
#define SCORE_MIN -INFINITY |
| 9 | 9 |
|
| 10 |
+#define MATCH_MAX_LEN 1024 |
|
| 11 |
+ |
|
| 10 | 12 |
int has_match(const char *needle, const char *haystack); |
| 11 | 13 |
score_t match_positions(const char *needle, const char *haystack, size_t *positions); |
| 12 | 14 |
score_t match(const char *needle, const char *haystack); |
| ... | ... |
@@ -36,8 +36,8 @@ static void draw_match(tty_interface_t *state, const char *choice, int selected) |
| 36 | 36 |
char *search = state->last_search; |
| 37 | 37 |
|
| 38 | 38 |
int n = strlen(search); |
| 39 |
- size_t positions[n + 1]; |
|
| 40 |
- for (int i = 0; i < n + 1; i++) |
|
| 39 |
+ size_t positions[MATCH_MAX_LEN]; |
|
| 40 |
+ for (int i = 0; i < n + 1 && i < MATCH_MAX_LEN; i++) |
|
| 41 | 41 |
positions[i] = -1; |
| 42 | 42 |
|
| 43 | 43 |
score_t score = match_positions(search, choice, &positions[0]); |
| ... | ... |
@@ -135,8 +135,8 @@ TEST test_choices_unicode() {
|
| 135 | 135 |
} |
| 136 | 136 |
|
| 137 | 137 |
TEST test_choices_large_input() {
|
| 138 |
- int N = 100000; |
|
| 139 |
- char *strings[N]; |
|
| 138 |
+ const int N = 100000; |
|
| 139 |
+ char *strings[100000]; |
|
| 140 | 140 |
|
| 141 | 141 |
for(int i = 0; i < N; i++) {
|
| 142 | 142 |
asprintf(&strings[i], "%i", i); |