Optimize menu sorting
Sorting and deduplicating elements after all items have been registered improves the time complexity of constructing the item list from O(n^2) to O(n log n). On a system with about 4000 menu items, this reduces startup time from about 0.21 seconds to 0.13 seconds.
This commit is contained in:
parent
12b8f83be4
commit
260eaba88e
5 changed files with 50 additions and 42 deletions
74
menu.c
74
menu.c
|
@ -1,4 +1,5 @@
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <assert.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
@ -45,18 +46,12 @@ static void free_pages(struct menu *menu) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void free_item(struct item *item) {
|
|
||||||
free(item->text);
|
|
||||||
free(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void free_items(struct menu *menu) {
|
static void free_items(struct menu *menu) {
|
||||||
struct item *next = menu->items;
|
for (size_t i = 0; i < menu->item_count; i++) {
|
||||||
while (next) {
|
struct item *item = &menu->items[i];
|
||||||
struct item *item = next;
|
free(item->text);
|
||||||
next = item->next;
|
|
||||||
free_item(item);
|
|
||||||
}
|
}
|
||||||
|
free(menu->items);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroys the menu, freeing memory associated with it.
|
// Destroys the menu, freeing memory associated with it.
|
||||||
|
@ -167,34 +162,44 @@ void menu_getopts(struct menu *menu, int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add an item to the menu.
|
// Add an item to the menu.
|
||||||
void menu_add_item(struct menu *menu, char *text, bool sort) {
|
void menu_add_item(struct menu *menu, char *text) {
|
||||||
struct item *new = calloc(1, sizeof(struct item));
|
if ((menu->item_count & (menu->item_count - 1)) == 0) {
|
||||||
if (!new) {
|
size_t alloc_size = menu->item_count ? 2 * menu->item_count : 1;
|
||||||
return;
|
void *new_array = realloc(menu->items, sizeof(struct item) * alloc_size);
|
||||||
|
if (!new_array) {
|
||||||
|
fprintf(stderr, "could not realloc %zu bytes", sizeof(struct item) * alloc_size);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
menu->items = new_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct item *new = &menu->items[menu->item_count];
|
||||||
new->text = text;
|
new->text = text;
|
||||||
|
|
||||||
if (sort) {
|
menu->item_count++;
|
||||||
for (struct item **item = &menu->items; *item; item = &(*item)->next) {
|
}
|
||||||
int result = strcmp(new->text, (*item)->text);
|
|
||||||
if (result == 0) {
|
|
||||||
free_item(new);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (result < 0) {
|
|
||||||
new->next = *item;
|
|
||||||
*item = new;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menu->lastitem) {
|
static int compare_items(const void *a, const void *b) {
|
||||||
menu->lastitem->next = new;
|
const struct item *item_a = a;
|
||||||
|
const struct item *item_b = b;
|
||||||
|
return strcmp(item_a->text, item_b->text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void menu_sort_and_deduplicate(struct menu *menu) {
|
||||||
|
size_t j = 1;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
qsort(menu->items, menu->item_count, sizeof(*menu->items), compare_items);
|
||||||
|
|
||||||
|
for (i = 1; i < menu->item_count; i++) {
|
||||||
|
if (strcmp(menu->items[i].text, menu->items[j - 1].text) == 0) {
|
||||||
|
free(menu->items[i].text);
|
||||||
} else {
|
} else {
|
||||||
menu->items = new;
|
menu->items[j] = menu->items[i];
|
||||||
|
j++;
|
||||||
}
|
}
|
||||||
menu->lastitem = new;
|
}
|
||||||
|
menu->item_count = j;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void append_page(struct page *page, struct page **first, struct page **last) {
|
static void append_page(struct page *page, struct page **first, struct page **last) {
|
||||||
|
@ -291,6 +296,7 @@ static void match_items(struct menu *menu) {
|
||||||
char buf[sizeof menu->input], *tok;
|
char buf[sizeof menu->input], *tok;
|
||||||
char **tokv = NULL;
|
char **tokv = NULL;
|
||||||
int i, tokc = 0;
|
int i, tokc = 0;
|
||||||
|
size_t k;
|
||||||
size_t tok_len;
|
size_t tok_len;
|
||||||
menu->matches = NULL;
|
menu->matches = NULL;
|
||||||
menu->matches_end = NULL;
|
menu->matches_end = NULL;
|
||||||
|
@ -314,8 +320,8 @@ static void match_items(struct menu *menu) {
|
||||||
}
|
}
|
||||||
tok_len = tokc ? strlen(tokv[0]) : 0;
|
tok_len = tokc ? strlen(tokv[0]) : 0;
|
||||||
|
|
||||||
struct item *item;
|
for (k = 0; k < menu->item_count; k++) {
|
||||||
for (item = menu->items; item; item = item->next) {
|
struct item *item = &menu->items[k];
|
||||||
for (i = 0; i < tokc; i++) {
|
for (i = 0; i < tokc; i++) {
|
||||||
if (!fstrstr(menu, item->text, tokv[i])) {
|
if (!fstrstr(menu, item->text, tokv[i])) {
|
||||||
/* token does not match */
|
/* token does not match */
|
||||||
|
|
8
menu.h
8
menu.h
|
@ -13,7 +13,6 @@ typedef void (*menu_callback)(struct menu *menu, char *text, bool exit);
|
||||||
struct item {
|
struct item {
|
||||||
char *text;
|
char *text;
|
||||||
int width;
|
int width;
|
||||||
struct item *next; // traverses all items
|
|
||||||
struct item *prev_match; // previous matching item
|
struct item *prev_match; // previous matching item
|
||||||
struct item *next_match; // next matching item
|
struct item *next_match; // next matching item
|
||||||
struct page *page; // the page holding this item
|
struct page *page; // the page holding this item
|
||||||
|
@ -64,8 +63,8 @@ struct menu {
|
||||||
char input[BUFSIZ];
|
char input[BUFSIZ];
|
||||||
size_t cursor;
|
size_t cursor;
|
||||||
|
|
||||||
struct item *items; // list of all items
|
struct item *items; // array of all items
|
||||||
struct item *lastitem; // last item in the list
|
size_t item_count;
|
||||||
struct item *matches; // list of matching items
|
struct item *matches; // list of matching items
|
||||||
struct item *matches_end; // last matching item
|
struct item *matches_end; // last matching item
|
||||||
struct item *sel; // selected item
|
struct item *sel; // selected item
|
||||||
|
@ -79,7 +78,8 @@ struct menu {
|
||||||
struct menu *menu_create(menu_callback callback);
|
struct menu *menu_create(menu_callback callback);
|
||||||
void menu_destroy(struct menu *menu);
|
void menu_destroy(struct menu *menu);
|
||||||
void menu_getopts(struct menu *menu, int argc, char *argv[]);
|
void menu_getopts(struct menu *menu, int argc, char *argv[]);
|
||||||
void menu_add_item(struct menu *menu, char *text, bool sort);
|
void menu_add_item(struct menu *menu, char *text);
|
||||||
|
void menu_sort_and_deduplicate(struct menu *menu);
|
||||||
void menu_render_items(struct menu *menu);
|
void menu_render_items(struct menu *menu);
|
||||||
void menu_paste(struct menu *menu, const char *text, ssize_t len);
|
void menu_paste(struct menu *menu, const char *text, ssize_t len);
|
||||||
void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state,
|
void menu_keypress(struct menu *menu, enum wl_keyboard_key_state key_state,
|
||||||
|
|
3
render.c
3
render.c
|
@ -28,7 +28,8 @@ void calc_widths(struct menu *menu) {
|
||||||
menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * menu->padding;
|
menu->right_arrow = text_width(cairo, menu->font, ">") + 2 * menu->padding;
|
||||||
|
|
||||||
// Calculate item widths and input area width
|
// Calculate item widths and input area width
|
||||||
for (struct item *item = menu->items; item; item = item->next) {
|
for (size_t i = 0; i < menu->item_count; i++) {
|
||||||
|
struct item *item = &menu->items[i];
|
||||||
item->width = text_width(cairo, menu->font, item->text);
|
item->width = text_width(cairo, menu->font, item->text);
|
||||||
if (item->width > menu->inputw) {
|
if (item->width > menu->inputw) {
|
||||||
menu->inputw = item->width;
|
menu->inputw = item->width;
|
||||||
|
|
|
@ -20,10 +20,11 @@ static void read_items(struct menu *menu) {
|
||||||
if (ent->d_name[0] == '.') {
|
if (ent->d_name[0] == '.') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
menu_add_item(menu, strdup(ent->d_name), true);
|
menu_add_item(menu, strdup(ent->d_name));
|
||||||
}
|
}
|
||||||
closedir(dir);
|
closedir(dir);
|
||||||
}
|
}
|
||||||
|
menu_sort_and_deduplicate(menu);
|
||||||
free(path);
|
free(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
wmenu.c
2
wmenu.c
|
@ -13,7 +13,7 @@ static void read_items(struct menu *menu) {
|
||||||
if (p) {
|
if (p) {
|
||||||
*p = '\0';
|
*p = '\0';
|
||||||
}
|
}
|
||||||
menu_add_item(menu, strdup(buf), false);
|
menu_add_item(menu, strdup(buf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue