3

I wanna create some function taking two parameters: int element, and input array. I want this function to place the element parameter at the first place in the array, enlarge the array by single index, and finally place the input array after the pushed element.

To illustrate this, let's say my input array is {1, 2, 3}, and element passed as the parameter is 5. The output should be {5, 1, 2, 3}.

How can I do this optimally?

I came out with this function:

void push(int el, int **arr)
{
    int *arr_temp = *arr;

    *arr = NULL;
    *arr = (int*) malloc(sizeof(int)*(n - 1));

    (*arr)[0] = el;
    for(int i = 0; i < (int)n - 1; i++)
    {
        (*arr)[i + 1] = arr_temp[i];
    }
}

It works, but well, it's not the fastest function I wrote, and it slows down the whole program. Is there a better way to do this?

My guess was doing something like (*arr)[1] = arr_temp, but it didn't work, and I'm not sure if there's a possibility in C to do something like this.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
user2252786
  • 1,627
  • 5
  • 21
  • 32
  • 2
    An array isn't really an ideal data structure for this kind of operation. – Carl Norum Apr 08 '13 at 21:52
  • 1
    Try `realloc` to enlarge the array without copying every element (if possible). – Tushar Apr 08 '13 at 21:52
  • 4
    Technically this is not a "push" operation, but rather an "unshift" operation. If you find yourself doing this more often than a real "push" operation (appending something to the end of the array) then you might want to flip the array around so that you actually *are* appending to the end instead. If you're mixing pushes/pops/unshifts/shifts a lot but rarely access the middle of the array, then a doubly-linked list might be better. – cdhowie Apr 08 '13 at 21:52
  • @Tushar, how would that work in this case? – Carl Norum Apr 08 '13 at 21:53
  • 2
    You're also not `free`ing the old array. And you don't need to cast the return value of `malloc` in a C program. – Carl Norum Apr 08 '13 at 21:54
  • @CarlNorum It wouldn't make it faster, per se, but wouldn't it be better than free-ing the entire array and alloc-ing a new one? – Tushar Apr 08 '13 at 21:54
  • @Tushar - maybe. I was misunderstanding your statement. I thought you meant it would help him with the unshifting. – Carl Norum Apr 08 '13 at 21:55
  • Yes, thank you @cdhowie for correcting me, of course it's unshift operation, I mistaken these words. – user2252786 Apr 08 '13 at 21:56
  • [Don't cast the result of `malloc` in C](http://stackoverflow.com/q/605845/995714) – phuclv Oct 25 '17 at 11:00

2 Answers2

2

Instead of using a raw array, it'd probably be better to wrap your array in a struct:

typedef struct
{
    size_t elementsAllocated;
    size_t elementsUsed;
    int* buffer;
} vector;

And then your pushing function could look something like:

vector* push_front(vector* v, int item)
{
    if (elementsUsed == elementsAllocated)
    {
        // Time to grow the buffer.
        int elementsAllocated = v->elementsAllocated * 2;
        if (elementsAllocated <= v->elementsAllocated)
        {
            abort(); // Overflow occurred.
        }

        int* buffer = realloc(v->buffer, elementsAllocated * sizeof *buffer);
        if (buffer == NULL)
        {
            abort();
        }

        // Shift the existing data over.
        memmove(buffer + elementsAllocated - v->elementsUsed,
                buffer + v->elementsAllocated - v->elementsUsed,
                v->elementsUsed * sizeof *buffer);
        v->buffer = buffer;
        v->elementsAllocated = elementsAllocated;
    }

    // Prepend the new item.
    int* p = v->buffer + v->elementsAllocated - v->elementsUsed - 1;
    *p = item;
    v->elementsUsed++;
    return p;
}

This would be significantly simpler if you can append items to the end of the array instead of prepending to the beginning. Note that the above code is completely untested and may contain off-by-one errors, but the core ideas are:

  • Memory allocation is expensive. Allocate more memory than you need and then write to allocated but unused space. This reduces the number of times you need to reallocate your buffer and to copy your data to shift it over.
  • Minimize the number of times you have to grow the buffer by growing it in larger steps.
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • 1
    +1. "Allocate more memory than you need and then write to allocated but unused space." is worth a lot, though it may not seem like it. – Daniel Kamil Kozar Apr 08 '13 at 22:36
  • Thanks you. Would using vectors instead of structures make it simpler? – user2252786 Apr 08 '13 at 22:38
  • @user2252786 If you already have some kind of vector implementation available to you, I would highly recommend using that instead of writing your own. – jamesdlin Apr 08 '13 at 22:39
  • Could you provide some example using vectors? I'm not really familiar with c++ oop, not to mention I have some serious problems passing vector to the function by reference and operating on it. – user2252786 Apr 08 '13 at 22:40
  • @user2252786 Well, you've asked this as a C question, and there is no standard vector implementation in C. If you're using C++, you may wish to use `std::vector` or `std::deque`, in which case you'd simply do something like: `std::deque d; d.push_front(5);`. – jamesdlin Apr 08 '13 at 22:44
  • I forgot to mention one thing - all unshifted arrays ARE OF THE SIZE OF FIXED INTEGER. It doesnt change anything according to the performance possibilities of the unshift() code? – user2252786 Apr 08 '13 at 23:01
  • @user2252786 I don't understand what you mean. `int` always has a fixed size (for a given platform) in C. Or do you mean that the array has a constant size? – jamesdlin Apr 08 '13 at 23:02
  • I mean the calls to the unshift() function would be: unshift(2, {0, 0, 0}), unshift(3, {1, 2, 3}), every call would have the same array's length. – user2252786 Apr 08 '13 at 23:04
  • @user2252786: That's very different from the question you originally asked (which said it needed to grow the array by one element). If the array size never changes, then you don't need to do any memory allocation at all. Just `memmove` the existing elements and write the new item to the beginning. – jamesdlin Apr 08 '13 at 23:07
  • How should I use memmove() to write this? – user2252786 Apr 08 '13 at 23:12
0

As suggested in the comments, you may want to just append to the array, then use a little struct and function to let you access the element you want as if the array were being prepended to. You seem to have the re-sizing down, so I'll just suggest some example code for the reversal. This is untested, as I don't have a unix box sitting by me at the moment for easy testing - note my C is a bit rusty.

typedef struct{
    int* array;
    int size;
} prependArray;

void accessElement(int element, prependArray myArray) {
    return myArray.array[size-element-1];
}

This way, you can also alloc arrays that are a bit larger than you need. Keep an int in "prependArray" that lists the size you last realloc'd to. When someone tries to add an item, check if you've hit that limit; if you have, resize the list before adding the new value. This saves on allocation time if you're adding lots of numbers.

You might want to look up the "vector" data-structure for a bit more on this sort of idea.

Marshall Conover
  • 855
  • 6
  • 24
  • The thing is the arrays Im operating on are already attributes of a list. Is this o.k. to change int *array list's attribute into 'this' list inside of a list? – user2252786 Apr 08 '13 at 22:13
  • @user2252786 - I've changed the name of the argument, in case the name "List" was confusing. Now, when you say they arrays you're operating on are already attributes of a list, are you saying you're adding all of the elements in a linked list to an array? – Marshall Conover Apr 08 '13 at 22:15
  • I'm looping through the list, and doing unshift for each element's array. – user2252786 Apr 08 '13 at 22:18
  • Ah - well then yes, you should be able to change nodes of the list to use the "prependArray" struct instead of an int*. – Marshall Conover Apr 08 '13 at 22:24