1 /*
2 * Copyright (c) 2012 Tim Ruehsen
3 * Copyright (c) 2015-2021 Free Software Foundation, Inc.
4 *
5 * This file is part of libwget.
6 *
7 * Libwget is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Libwget is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with libwget. If not, see <https://www.gnu.org/licenses/>.
19 *
20 *
21 * vector routines
22 *
23 * Changelog
24 * 25.04.2012 Tim Ruehsen created
25 *
26 */
27
28 #include <config.h>
29
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdarg.h>
33
34 #include <wget.h>
35 #include "private.h"
36
37 struct wget_vector_st {
38 wget_vector_compare_fn
39 *cmp; //!< comparison function for sorting and searching
40 wget_vector_destructor
41 *destructor; //!< element destructor function
42 void
43 **entry; //!< pointer to array of pointers to elements
44 int
45 max, //!< allocated elements
46 cur; //!< number of elements in use
47 bool
48 sorted : 1; //!< 1=list is sorted, 0=list is not sorted
49 float
50 resize_factor; //!< factor to calculate new vector size
51 };
52
53 /**
54 * \file
55 * \brief Vector functions
56 * \defgroup libwget-vector Vector functions
57 * @{
58 *
59 * Functions to realize vectors (growable arrays).
60 */
61
62 /**
63 * \param[in] max Initial number of pre-allocated entries.
64 * \param[in] cmp Comparison function for sorting/finding/sorted insertion or %NULL.
65 * \return New vector instance
66 *
67 * Create a new vector instance, to be free'd after use with wget_vector_free().
68 */
wget_vector_create(int max,wget_vector_compare_fn * cmp)69 wget_vector *wget_vector_create(int max, wget_vector_compare_fn *cmp)
70 {
71 wget_vector *v = wget_calloc(1, sizeof(wget_vector));
72
73 if (!v)
74 return NULL;
75
76 if (!(v->entry = wget_malloc(max * sizeof(void *)))) {
77 xfree(v);
78 return NULL;
79 }
80
81 v->max = max;
82 v->resize_factor = 2;
83 v->cmp = cmp;
84 v->destructor = free;
85
86 return v;
87 }
88
89 /**
90 * \param[in] v Vector
91 * \param[in] factor Vector growth factor
92 *
93 * Set the factor for resizing the vector when it is full.
94 *
95 * The new size is 'factor * oldsize'. If the new size is less or equal the old size,
96 * the involved insertion function will return an error and the internal state of
97 * the vector will not change.
98 *
99 * Default is 2.
100 */
wget_vector_set_resize_factor(wget_vector * v,float factor)101 void wget_vector_set_resize_factor(wget_vector *v, float factor)
102 {
103 if (v)
104 v->resize_factor = factor;
105 }
106
insert_element(wget_vector * v,const void * elem,int pos,int replace)107 static int WGET_GCC_NONNULL((2)) insert_element(wget_vector *v, const void *elem, int pos, int replace)
108 {
109 if (pos < 0 || !v || pos > v->cur)
110 return WGET_E_INVALID;
111
112 if (!replace) {
113 if (v->max == v->cur) {
114 int newsize = (int) (v->max * v->resize_factor);
115
116 if (newsize <= v->max)
117 return WGET_E_INVALID;
118
119 void **tmp = wget_realloc(v->entry, newsize * sizeof(void *));
120 if (!tmp)
121 return WGET_E_MEMORY;
122
123 v->entry = tmp;
124 v->max = newsize;
125 }
126
127 memmove(&v->entry[pos + 1], &v->entry[pos], (v->cur - pos) * sizeof(void *));
128 v->cur++;
129 }
130
131 v->entry[pos] = (void *) elem;
132
133 if (v->cmp) {
134 if (v->cur == 1) v->sorted = 1;
135 else if (v->cur > 1 && v->sorted) {
136 if (pos == 0) {
137 if (v->cmp(elem, v->entry[1]) > 0) v->sorted = 0;
138 } else if (pos == v->cur - 1) {
139 if (v->cmp(elem, v->entry[v->cur - 2]) < 0) v->sorted = 0;
140 } else {
141 if (v->cmp(elem, v->entry[pos - 1]) < 0 ||
142 v->cmp(elem, v->entry[pos + 1]) > 0) {
143 v->sorted = 0;
144 }
145 }
146 }
147 }
148
149 return pos; // return position of new element
150 }
151
152 /**
153 * \param[in] v Vector where \p elem is inserted into
154 * \param[in] elem Element to insert into \p v
155 * \param[in] pos Position to insert \p elem at
156 * \return Index of inserted element (>= 0) or WGET_E_* on error (< 0)
157 *
158 * Insert \p elem of at index \p pos.
159 *
160 * \p elem is *not* cloned, the vector takes 'ownership' of the element.
161 *
162 * An error is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
163 */
wget_vector_insert(wget_vector * v,const void * elem,int pos)164 int wget_vector_insert(wget_vector *v, const void *elem, int pos)
165 {
166 return insert_element(v, elem, pos, 0);
167 }
168
169 /**
170 * \param[in] v Vector where \p elem is inserted into
171 * \param[in] elem Element to insert into \p v
172 * \return Index of inserted element (>= 0) or WGET_E_* on error (< 0)
173 *
174 * Insert \p elem of at a position that keeps the sort order of the elements.
175 * If the vector has no comparison function, \p elem will be inserted as the last element.
176 * If the elements in the vector are not sorted, they will be sorted after returning from this function.
177 *
178 * \p elem is *not* cloned, the vector takes 'ownership' of the element.
179 *
180 * An error is returned if \p v is %NULL.
181 */
wget_vector_insert_sorted(wget_vector * v,const void * elem)182 int wget_vector_insert_sorted(wget_vector *v, const void *elem)
183 {
184 if (!v)
185 return WGET_E_INVALID;
186
187 if (!v->cmp)
188 return insert_element(v, elem, v->cur, 0);
189
190 if (!v->sorted)
191 wget_vector_sort(v);
192
193 // binary search for element
194 int l = 0, r = v->cur - 1, m = 0, res = 0;
195
196 while (l <= r) {
197 m = (l + r) / 2;
198 if ((res = v->cmp(elem, v->entry[m])) > 0) l = m + 1;
199 else if (res < 0) r = m - 1;
200 else return insert_element(v, elem, m, 0);
201 }
202 if (res > 0) m++;
203
204 return insert_element(v, elem, m, 0);
205 }
206
207 /**
208 * \param[in] v Vector where \p elem is appended to
209 * \param[in] elem Element to append to a \p v
210 * \param[in] size Size of \p elem
211 * \return Index of inserted element (>= 0) or WGET_E_* on error (< 0)
212 *
213 * Append \p elem of given \p size to vector \p v.
214 *
215 * \p elem is cloned / copied (shallow).
216 *
217 * An error is returned if \p v is %NULL.
218 */
wget_vector_add_memdup(wget_vector * v,const void * elem,size_t size)219 int wget_vector_add_memdup(wget_vector *v, const void *elem, size_t size)
220 {
221 void *elemp;
222 int rc;
223
224 if (!v)
225 return WGET_E_INVALID;
226
227 if (!(elemp = wget_memdup(elem, size)))
228 return WGET_E_MEMORY;
229
230 if ((rc = insert_element(v, elemp, v->cur, 0)) < 0)
231 xfree(elemp);
232
233 return rc;
234 }
235
236 /**
237 * \param[in] v Vector where \p elem is appended to
238 * \param[in] elem Element to append to a \p v
239 * \return Index of inserted element (>= 0) or WGET_E_* on error (< 0)
240 *
241 * Append \p elem to vector \p v.
242 *
243 * \p elem is *not* cloned, the vector takes 'ownership' of the element.
244 *
245 * An error is returned if \p v is %NULL.
246 */
wget_vector_add(wget_vector * v,const void * elem)247 int wget_vector_add(wget_vector *v, const void *elem)
248 {
249 return v ? insert_element(v, elem, v->cur, 0) : WGET_E_INVALID;
250 }
251
252 /**
253 * \param[in] v Vector where \p s is appended to
254 * \param[in] fmt Printf-like format string
255 * \param[in] args Arguments for the \p fmt
256 * \return Index of appended element (>= 0) or WGET_E_* on error (< 0)
257 *
258 * Construct string in a printf-like manner and append it as an element to vector \p v.
259 *
260 * An error is returned if \p v or \p fmt is %NULL.
261 */
wget_vector_add_vprintf(wget_vector * v,const char * fmt,va_list args)262 int wget_vector_add_vprintf(wget_vector *v, const char *fmt, va_list args)
263 {
264 if (!v || !fmt)
265 return WGET_E_INVALID;
266
267 char *p = wget_vaprintf(fmt, args);
268 if (!p)
269 return WGET_E_MEMORY;
270
271 return insert_element(v, p, v->cur, 0);
272 }
273
274 /**
275 * \param[in] v Vector where \p s is appended to
276 * \param[in] fmt Printf-like format string
277 * \param[in] ... Arguments for the \p fmt
278 * \return Index of appended element (>= 0) or WGET_E_* on error (< 0)
279 *
280 * Construct string in a printf-like manner and append it as an element to vector \p v.
281 *
282 * An error is returned if \p v or \p fmt is %NULL.
283 */
wget_vector_add_printf(wget_vector * v,const char * fmt,...)284 int wget_vector_add_printf(wget_vector *v, const char *fmt, ...)
285 {
286 if (!v || !fmt)
287 return WGET_E_INVALID;
288
289 va_list args;
290
291 va_start(args, fmt);
292 char *p = wget_vaprintf(fmt, args);
293 va_end(args);
294
295 if (!p)
296 return WGET_E_MEMORY;
297
298 return insert_element(v, p, v->cur, 0);
299 }
300
301 /**
302 * \param[in] v Vector where \p elem is inserted
303 * \param[in] elem Element to insert into \p v
304 * \param[in] pos Position to insert \p elem at
305 * \return Index of inserted element (same as \p pos) (>= 0) or WGET_E_* on error (< 0)
306 *
307 * Replace the element at position \p pos with \p elem.
308 * If the vector has an element destructor function, this is called.
309 * The old element is free'd.
310 *
311 * \p elem is *not* cloned, the vector takes 'ownership' of the element.
312 *
313 * An error is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
314 */
wget_vector_replace(wget_vector * v,const void * elem,int pos)315 int wget_vector_replace(wget_vector *v, const void *elem, int pos)
316 {
317 if (!v || pos < 0 || pos >= v->cur)
318 return WGET_E_INVALID;
319
320 if (v->destructor)
321 v->destructor(v->entry[pos]);
322
323 return insert_element(v, elem, pos, 1); // replace existing entry
324 }
325
remove_element(wget_vector * v,int pos,int free_entry)326 static int remove_element(wget_vector *v, int pos, int free_entry)
327 {
328 if (pos < 0 || !v || pos >= v->cur)
329 return WGET_E_INVALID;
330
331 if (free_entry) {
332 if (v->destructor)
333 v->destructor(v->entry[pos]);
334 }
335
336 memmove(&v->entry[pos], &v->entry[pos + 1], (v->cur - pos - 1) * sizeof(void *));
337 v->cur--;
338
339 return pos;
340 }
341
342 /**
343 * \param[in] v Vector to remove an element from
344 * \param[in] pos Position of element to remove
345 * \return Index of appended element (>= 0) or WGET_E_* on error (< 0)
346 *
347 * Remove the element at position \p pos.
348 * If the vector has an element destructor function, this is called.
349 * The element is free'd.
350 *
351 * An error is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
352 */
wget_vector_remove(wget_vector * v,int pos)353 int wget_vector_remove(wget_vector *v, int pos)
354 {
355 return remove_element(v, pos, 1);
356 }
357
358 /**
359 * \param[in] v Vector to remove an element from
360 * \param[in] pos Position of element to remove
361 * \return Index of removed element (same as \p pos) (>= 0) or WGET_E_* on error (< 0)
362 *
363 * Remove the element at position \p pos.
364 * No element destructor function is called, the element is not free'd.
365 *
366 * An error is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
367 */
wget_vector_remove_nofree(wget_vector * v,int pos)368 int wget_vector_remove_nofree(wget_vector *v, int pos)
369 {
370 return remove_element(v, pos, 0);
371 }
372
373 /**
374 * \param[in] v Vector to act on
375 * \param[in] old_pos Position to move element from
376 * \param[in] new_pos Position to move element to
377 * \return Index of new position (same as \p new_pos) (>= 0) or WGET_E_* on error (< 0)
378 *
379 * Move the element at position \p old_pos to \p new_pos.
380 *
381 * Other elements may change the position.
382 *
383 * An error is returned if \p v is %NULL or either \p old_pos or
384 * \p new_pos is out of range (< 0 or > # of entries).
385 */
wget_vector_move(wget_vector * v,int old_pos,int new_pos)386 int wget_vector_move(wget_vector *v, int old_pos, int new_pos)
387 {
388 if (!v) return WGET_E_INVALID;
389 if (old_pos < 0 || old_pos >= v->cur) return WGET_E_INVALID;
390 if (new_pos < 0 || new_pos >= v->cur) return WGET_E_INVALID;
391 if (old_pos == new_pos) return new_pos;
392
393 if (v->sorted && v->cmp && v->cmp(v->entry[old_pos], v->entry[new_pos]))
394 v->sorted = 0;
395
396 if (old_pos < new_pos) {
397 void *tmp = v->entry[old_pos];
398 memmove(&v->entry[old_pos], &v->entry[old_pos + 1], (new_pos - old_pos) * sizeof(void *));
399 v->entry[new_pos] = tmp;
400 } else {
401 void *tmp = v->entry[old_pos];
402 memmove(&v->entry[new_pos + 1], &v->entry[new_pos], (old_pos - new_pos) * sizeof(void *));
403 v->entry[new_pos] = tmp;
404 }
405
406 return new_pos;
407 }
408
409 /**
410 * \param[in] v Vector to act on
411 * \param[in] pos1 Position of element one
412 * \param[in] pos2 Position of element two
413 * \return Index of second position (same as \p pos2) (>= 0) or WGET_E_* on error (< 0)
414 *
415 * Swap the two elements at position \p pos1 and \p pos2.
416 *
417 * An error is returned if \p v is %NULL or either \p pos1 or
418 * \p pos2 is out of range (< 0 or > # of entries).
419 */
wget_vector_swap(wget_vector * v,int pos1,int pos2)420 int wget_vector_swap(wget_vector *v, int pos1, int pos2)
421 {
422 if (!v) return WGET_E_INVALID;
423 if (pos1 < 0 || pos1 >= v->cur) return WGET_E_INVALID;
424 if (pos2 < 0 || pos2 >= v->cur) return WGET_E_INVALID;
425 if (pos1 == pos2) return pos2;
426
427 void *tmp = v->entry[pos1];
428 v->entry[pos1] = v->entry[pos2];
429 v->entry[pos2] = tmp;
430
431 if (v->sorted && v->cmp && v->cmp(v->entry[pos1], v->entry[pos2]))
432 v->sorted = 0;
433
434 return pos2;
435 }
436
437 /**
438 * \param[in] v Vector to be free'd
439 *
440 * Free the vector \p v and it's contents.
441 *
442 * For each element the destructor function is called and the element free'd thereafter.
443 * Then the vector itself is free'd and set to %NULL.
444 */
wget_vector_free(wget_vector ** v)445 void wget_vector_free(wget_vector **v)
446 {
447 if (v && *v) {
448 wget_vector_clear(*v);
449 xfree((*v)->entry);
450 xfree(*v);
451 }
452 }
453
454 /**
455 * \param[in] v Vector to be cleared
456 *
457 * Free all elements of the vector \p v but not the vector itself.
458 *
459 * For each element the destructor function is called and the element free'd thereafter.
460 * The vector is then empty and can be reused.
461 */
wget_vector_clear(wget_vector * v)462 void wget_vector_clear(wget_vector *v)
463 {
464 if (v) {
465 if (v->destructor) {
466 for (int it = 0; it < v->cur; it++) {
467 v->destructor(v->entry[it]);
468 v->entry[it] = NULL;
469 }
470 }
471
472 v->cur = 0;
473 }
474 }
475
476 /**
477 * \param[in] v Vector to be cleared
478 *
479 * Remove all elements of the vector \p v without free'ing them.
480 * The caller is responsible to care for the elements.
481 *
482 * The vector is then empty and can be reused.
483 */
wget_vector_clear_nofree(wget_vector * v)484 void wget_vector_clear_nofree(wget_vector *v)
485 {
486 if (v) {
487 for (int it = 0; it < v->cur; it++)
488 v->entry[it] = NULL;
489 v->cur = 0;
490 }
491 }
492
493 /**
494 * \param[in] v Vector
495 * \return The number of elements in the vector \p v
496 *
497 * Retrieve the number of elements of the vector \p v.
498 * If \p v is %NULL, 0 is returned.
499 */
wget_vector_size(const wget_vector * v)500 int wget_vector_size(const wget_vector *v)
501 {
502 return v ? v->cur : 0;
503 }
504
505 /**
506 * \param[in] v Vector
507 * \param[in] pos Position of element to retrieve
508 * \return The element at position \p pos or %NULL on error
509 *
510 * Retrieve the element at position \p pos.
511 *
512 * %NULL is returned if \p v is %NULL or \p pos is out of range (< 0 or > # of entries).
513 */
wget_vector_get(const wget_vector * v,int pos)514 void *wget_vector_get(const wget_vector *v, int pos)
515 {
516 if (pos < 0 || !v || pos >= v->cur)
517 return NULL;
518
519 return v->entry[pos];
520 }
521
522 /**
523 * \param[in] v Vector
524 * \param[in] browse Function to be called for each element of \p v
525 * \param[in] ctx Context variable use as param to \p browse
526 * \return Return value of the last call to \p browse
527 *
528 * Call function \p browse for each element of vector \p v or until \p browse
529 * returns a value not equal to zero.
530 *
531 * \p browse is called with \p ctx and the pointer to the current element.
532 *
533 * The return value of the last call to \p browse is returned or 0 if \p v is %NULL.
534 */
wget_vector_browse(const wget_vector * v,wget_vector_browse_fn * browse,void * ctx)535 int wget_vector_browse(const wget_vector *v, wget_vector_browse_fn *browse, void *ctx)
536 {
537 if (v) {
538 for (int ret, it = 0; it < v->cur; it++)
539 if ((ret = browse(ctx, v->entry[it])) != 0)
540 return ret;
541 }
542
543 return 0;
544 }
545
546 /**
547 * \param[in] v Vector
548 * \param[in] cmp Function to compare elements
549 *
550 * Set the compare function used by wget_vector_sort().
551 */
wget_vector_setcmpfunc(wget_vector * v,wget_vector_compare_fn * cmp)552 void wget_vector_setcmpfunc(wget_vector *v, wget_vector_compare_fn *cmp)
553 {
554 if (v) {
555 v->cmp = cmp;
556
557 if (v->cur == 1)
558 v->sorted = 1;
559 else
560 v->sorted = 0;
561 }
562 }
563
564 /**
565 * \param[in] v Vector
566 * \param[in] destructor Function to be called for element destruction
567 *
568 * Set the destructor function that is called for each element to be removed.
569 * It should not free the element (pointer) itself.
570 */
wget_vector_set_destructor(wget_vector * v,wget_vector_destructor * destructor)571 void wget_vector_set_destructor(wget_vector *v, wget_vector_destructor *destructor)
572 {
573 if (v)
574 v->destructor = destructor;
575 }
576
577 WGET_GCC_NONNULL_ALL
compare_element(const void * p1,const void * p2,void * v)578 static int compare_element(const void *p1, const void *p2, void *v)
579 {
580 return ((wget_vector *)v)->cmp(*((void **)p1), *((void **)p2));
581 }
582
583 /**
584 * \param[in] v Vector
585 *
586 * Sort the elements in vector \p v using the compare function.
587 * Do nothing if \p v is %NULL or the compare function is not set.
588 */
wget_vector_sort(wget_vector * v)589 void wget_vector_sort(wget_vector *v)
590 {
591 if (v && v->cmp) {
592 qsort_r(v->entry, v->cur, sizeof(void *), compare_element, v);
593 v->sorted = 1;
594 }
595 }
596
597 /**
598 * \param[in] v Vector
599 * \param[in] elem Element to search for
600 * \return Index of the found element, WGET_E_UNKNOWN if not found or WGET_E_INVALID
601 * if v was %NULL or there was no comparison function set
602 *
603 * Searches for the given element using the compare function of the vector.
604 */
wget_vector_find(const wget_vector * v,const void * elem)605 int wget_vector_find(const wget_vector *v, const void *elem)
606 {
607 if (!v || !v->cmp)
608 return WGET_E_INVALID;
609
610 if (v->cur == 1) {
611 if (v->cmp(elem, v->entry[0]) == 0) return 0;
612 } else if (v->sorted) {
613 // binary search for element (exact match)
614 for (int l = 0, r = v->cur - 1; l <= r;) {
615 int res, m = (l + r) / 2;
616 if ((res = v->cmp(elem, v->entry[m])) > 0) l = m + 1;
617 else if (res < 0) r = m - 1;
618 else return m;
619 }
620 } else {
621 // linear search for element
622 for (int it = 0; it < v->cur; it++)
623 if (v->cmp(elem, v->entry[it]) == 0) return it;
624 }
625
626 return WGET_E_UNKNOWN; // not found
627 }
628
629 /**
630 * \param[in] v Vector
631 * \param[in] elem Element to check for
632 * \return True if element exists, else false
633 *
634 * Checks whether the element \p elem exists or not.
635 */
wget_vector_contains(const wget_vector * v,const void * elem)636 bool wget_vector_contains(const wget_vector *v, const void *elem)
637 {
638 return wget_vector_find(v, elem) >= 0;
639 }
640
641 /**
642 * \param[in] v Vector
643 * \param[in] start Index to start search from
644 * \param[in] direction Direction of search
645 * \param[in] find Function to be called for each element
646 * \return Index of the found element, WGET_E_UNKNOWN if not found or WGET_E_INVALID
647 * if v was %NULL or there was no comparison function set
648 *
649 * Call \p find for each element starting at \p start.
650 * If \p find returns 0 the current index is returned.
651 */
wget_vector_findext(const wget_vector * v,int start,int direction,wget_vector_find_fn * find)652 int wget_vector_findext(const wget_vector *v, int start, int direction, wget_vector_find_fn *find)
653 {
654 if (!v)
655 return WGET_E_INVALID;
656
657 if (direction) { // down
658 if (start < v->cur) {
659 for (int it = start; it >= 0; it--)
660 if (find(v->entry[it]) == 0)
661 return it;
662 }
663 } else { // up
664 if (start >= 0) {
665 for (int it = start; it < v->cur; it++)
666 if (find(v->entry[it]) == 0)
667 return it;
668 }
669 }
670
671 return WGET_E_UNKNOWN;
672 }
673
674 /**@}*/
675