Skip to content
Snippets Groups Projects
array.c 6.23 KiB
Newer Older
Bruce Cowan's avatar
Bruce Cowan committed
/* array.c
 *
 * Copyright 2018 Bruce Cowan <bruce@bcowan.me.uk>
 *
 * This file is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 *
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

Bruce Cowan's avatar
Bruce Cowan committed
#include <errno.h>
Bruce Cowan's avatar
Bruce Cowan committed
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "array.h"
#include "mem.h"

#define MIN_SIZE 2

struct _Array
{
    void **data;
    size_t length;
    size_t capacity;
    FreeFunc free_func;
    
    int ref_count;
Bruce Cowan's avatar
Bruce Cowan committed
};

static void
resize_array (Array *array,
              size_t required)
{
    size_t size = MIN_SIZE;

    while (size < required)
    {
        size = (size_t) ceil (size * 1.5);
    }

    array->data = check_realloc (array->data, sizeof (void *) * size);
    array->capacity = size;
}

static bool
index_in_array (const Array *array,
                size_t       index)
{
    if (index < array->length)
        return true;
    else
    {
Bruce Cowan's avatar
Bruce Cowan committed
        errno = EINVAL;
Bruce Cowan's avatar
Bruce Cowan committed
        return false;
    }
}

static void
maybe_shrink (Array *array)
{
    if (array->length <= (size_t) ceil (array->capacity * 0.3))
        resize_array (array, array->length);
}

/**
 * array_new:
 * @func: (nullable): A #FreeFunc which is used to free deleted items
 *
 * Creates a new #Array.
 *
 * Returns: The new #Array, free with array_free()
 */
Array *
array_new (FreeFunc func)
{
    Array *array = check_malloc (sizeof (Array));

    array->data = check_malloc (sizeof (void *) * MIN_SIZE);
    array->length = 0;
    array->capacity = MIN_SIZE;
    array->free_func = func;
    array->ref_count = 1;
Bruce Cowan's avatar
Bruce Cowan committed

    return array;
}

/**
 * array_ref:
 * @array: The array to increase the reference count of
 *
 * Increase the reference account of the array.
 *
 * Returns: The array
 */
Array *
array_ref (Array *array)
{
    array->ref_count++;
Bruce Cowan's avatar
Bruce Cowan committed

    return array;
}

/**
 * array_unref:
 * @array: The array to decrease the reference count of
 *
 * Decrease the reference account of the array. If it becomes 0, it will be
 * freed.
 */
void
array_unref (Array *array)
{
    array->ref_count--;

    if (array->ref_count == 0)
Bruce Cowan's avatar
Bruce Cowan committed
    {
        if (array->free_func)
        {
            for (size_t i = 0; i < array->length; i++)
                array->free_func (array->data[i]);
        }

        free (array->data);
        free (array);
    }
}

/**
 * array_add:
 * @array: An array
 * @val: The value to add to the array
 *
 * Adds a value to an array.
 */
void
array_add (Array      *array,
           const void *val)
{
    if (array->length == array->capacity)
        resize_array (array, array->length + 1);

    array->data[array->length] = (void *) val;
    array->length += 1;
}

/**
 * array_remove_fast:
 * @array: An array
 * @index: The index of the item to remove from @array
 *
 * Removes an item from an array. This function copies the last member of the
 * array to the location of the item to be removed, which means that ordering
 * is not preserved.
Bruce Cowan's avatar
Bruce Cowan committed
 *
 * Returns: %true if successful
Bruce Cowan's avatar
Bruce Cowan committed
 */
Bruce Cowan's avatar
Bruce Cowan committed
bool
Bruce Cowan's avatar
Bruce Cowan committed
array_remove_fast (Array *array,
                   size_t index)
{
    if (!index_in_array (array, index))
Bruce Cowan's avatar
Bruce Cowan committed
    {
        perror ("array_remove_fast");
        return false;
    }
Bruce Cowan's avatar
Bruce Cowan committed

    if (array->free_func)
        array->free_func (array->data[index]);

    void *last = array->data[array->length - 1];
    array->data[index] = last;

    array->length -= 1;
    maybe_shrink (array);
Bruce Cowan's avatar
Bruce Cowan committed

    return true;
Bruce Cowan's avatar
Bruce Cowan committed
}

/**
 * array_remove:
 * @array: An array
 * @index: The index of the item to remove from @array
 *
 * Removes an item from an array. This function moves the items following the
 * removed item down one space. This preserves the ordering of the items, but is
 * slower than array_remove_fast().
Bruce Cowan's avatar
Bruce Cowan committed
 *
 * Returns: %true if successful
Bruce Cowan's avatar
Bruce Cowan committed
 */
Bruce Cowan's avatar
Bruce Cowan committed
bool
Bruce Cowan's avatar
Bruce Cowan committed
array_remove (Array *array,
              size_t index)
{
    if (!index_in_array (array, index))
Bruce Cowan's avatar
Bruce Cowan committed
    {
        perror ("array_remove");
        return false;
    }
Bruce Cowan's avatar
Bruce Cowan committed

    if (array->free_func)
        array->free_func (array->data[index]);

    memmove (array->data + index,
             array->data + index + 1,
             sizeof (void *) * (array->length - index - 1));

    array->length -= 1;
    maybe_shrink (array);
Bruce Cowan's avatar
Bruce Cowan committed

    return true;
Bruce Cowan's avatar
Bruce Cowan committed
}

/**
 * array_set:
 * @array: An array
 * @index: The index of the item to set a value for
 * @val: The value to set the item to
 *
 * Sets an element of @array to @val, using the @index.
Bruce Cowan's avatar
Bruce Cowan committed
 *
 * Returns: %true if successful
Bruce Cowan's avatar
Bruce Cowan committed
 */
Bruce Cowan's avatar
Bruce Cowan committed
bool
Bruce Cowan's avatar
Bruce Cowan committed
array_set (Array      *array,
           size_t      index,
           const void *val)
{
    if (!index_in_array (array, index))
Bruce Cowan's avatar
Bruce Cowan committed
    {
        perror ("array_set");
        return false;
    }
Bruce Cowan's avatar
Bruce Cowan committed

    array->data[index] = (void *) val;
Bruce Cowan's avatar
Bruce Cowan committed
    return true;
Bruce Cowan's avatar
Bruce Cowan committed
}

/**
 * array_get:
 * @array: An array
 * @index: The index of @array to get a value from
 *
 * Get a value of @array, using @index.
 *
 * Returns: (transfer none): The value at @index of @array
 */
const void *
array_get (const Array *array,
           size_t       index)
{
    if (index_in_array (array, index))
        return array->data[index];
    else
Bruce Cowan's avatar
Bruce Cowan committed
    {
        perror ("array_get");
Bruce Cowan's avatar
Bruce Cowan committed
        return NULL;
Bruce Cowan's avatar
Bruce Cowan committed
    }
Bruce Cowan's avatar
Bruce Cowan committed
}

/**
 * array_get_length:
 * @array: An array
 *
 * Gets the number of elements in an array.
 *
 * Returns: The length of the array
 */
size_t
array_get_length (const Array *array)
{
    return array->length;
}

/**
 * array_get_capacity:
 * @array: An array
 *
 * Get the capacity (proportional to memory usage) of @array.
 *
 * Returns: The capacity, in items.
 */
size_t
array_get_capacity (const Array *array)
{
    return array->capacity;
}

/**
 * array_sort:
 * @array: An array
 * @func: A function which returns -1 for less than, 0 for equal, and 1 for more than
 *
 * Sorts the array
 */
void
array_sort (Array      *array,
            CompareFunc func)
{
    qsort (*array->data, array->length, sizeof (void *), func);
}