0

In C we can use a left bit shift to move each digit of a number's binary representation to the left by one space which is regarded to be O(1) time. For example, 0010 << 1 = 0100

Is there a similar function/operation in C that can do this for arrays in O(1) time? For example, [1, 2, 3, 4, 5] shifted by one space gives [2, 3, 4, 5, 0].

One way that came to my mind was by using memcpy() like this:

int arr1[] = {1, 2, 3, 4, 5};
memcpy(arr1, arr1+1, 4*sizeof(int));

for(int i=0 ; i<5 ; i++)
{
        printf("%d ", arr1[i]);
}

Output: 2, 3, 4, 5, 5

The only problem with this is that, I guess memcpy() isn't exactly O(1). I was hoping that might be some low-level function/method in C that can manipulate memory quickly to achieve this, but I haven't been able to find anything while googling so far.

Edit: On searching, I found out that bit shifting uses a special circuit which probably allows the shifting operation to be much faster than something like memcpy. This leads to my question of whether there is a way to represent the array as a string of bits and perform traditional bit shifting on that bit string?

Edit 2: I know what I'm trying to achieve cannot be done in literal O(1) time. I would be satisfied by any answers that suggest how I could improve the speed of shifting the array so that it somewhat seems like an O(1) operation (even though it actually isn't).

Mathphile
  • 185
  • 1
  • 9
  • 7
    I think in that case you should use `memmove` instead of `memcpy` to prevent undefined behavior. AFAIK, `dest` and `src` should not overlap in `memcpy`. – mediocrevegetable1 Jul 26 '21 at 10:54
  • 7
    You can't realistically move an arbitrary amount of data around in memory in constant time. But you *can* change how you *interpret* the array by storing a "zero-index" and wrapping any access around if it hits either end (using modulu). That would be implementing a [ring buffer](https://en.wikipedia.org/wiki/Circular_buffer). – Joachim Sauer Jul 26 '21 at 10:57
  • 2
    Make the array larger than you need, copy the first element to the back, and increment a pointer to the array. – Weather Vane Jul 26 '21 at 10:57
  • @mediocrevegetable1 Thanks for the suggestion. Didn't know that memcpy had the problem before. But the problem still stands that the TC of memmove is closer towards O(n). – Mathphile Jul 26 '21 at 10:57
  • @WeatherVane sorry I forgot to include that this needs to be done in O(1) space complexity. – Mathphile Jul 26 '21 at 10:59
  • 1
    The shift operation isn't O(1) time, but O(N) in the number of bits. You just happen to use a rather small number of bits -- but if you were to shift 512 bits, this would no longer work in a single instruction. – Simon Richter Jul 26 '21 at 10:59
  • 1
    You can't move an arbitrary number of objects in constant (O(1)) time. it's not going to happen. – lulle2007200 Jul 26 '21 at 11:00
  • My suggestion *isn't* in O(n) complexity. It is independent of `n`, requiring the same 2 operations no matter the value of `n`. – Weather Vane Jul 26 '21 at 11:00
  • 6
    What are you _actually_ trying to achieve. This could be an [XY Problem](https://xyproblem.info/). Maybe you need a ring buffer, then using `memmove` is not what you need. BTW what's the typical size of the memory you want to move? If it's a few dozend bytes, memmove might be fast enough, if it's a few megabytes, it's probably too slow. – Jabberwocky Jul 26 '21 at 11:02
  • @WeatherVane I am talking about space complexity not time complexity in my last comment. – Mathphile Jul 26 '21 at 11:13
  • _". This leads to my question of whether there is a way to represent the array as a string of bits and perform traditional bit shifting on that bit string?"_: no, there is no such thing. BTW you're talking about shifting _bytes_ not _ bits_. – Jabberwocky Jul 26 '21 at 11:14
  • What are you actually trying to achieve? You can't do it with O(1) time *and* space complexity for an arbitrary amount of shifts on an arbitrary large array. Do as JoachimSauer suggested and implement a ring buffer, or do as Weather Vane suggested and add *n* extra elements to the array, that gives you *n* "free" shifts by 1. – lulle2007200 Jul 26 '21 at 11:19
  • @Jabberwocky those bytes can be represented as a string of bits which can be shifted right? – Mathphile Jul 26 '21 at 11:22
  • Shifting bits is not truly O(1). Shifting bits is O(n). However, in analysis of many practical programs, we are concerned with how the program behaves up to problem sizes for which all of its operations fit within the native instructions and data of the architecture; there is no overflow. In this practical analysis, shifting bits under some limit, say 1-32 bits, takes the same amount of time. Going to more bits takes more data units and becomes O(n). An array already takes multiple data units, by definition, and shifting it cannot be O(1)… – Eric Postpischil Jul 26 '21 at 11:27
  • @Mathphile: You can’t shift bits across different objects (array elements). There’s no way to do what you want in a single operation in C. If you want a circular shift, you’ll have to save a copy of the first element, do the `memmove`, then overwrite the last element with the saved data. If you just want a left shift, use `memmove` and overwrite the last element with `0`. Or use a ring buffer. – John Bode Jul 26 '21 at 11:27
  • … Even if the array is represented as a string of bits, if it has more than a few small elements, it is more than the threshold (typically 32 or 64 bits in modern processors), and so it breaks the limit used for the practical analysis, and shifting its bits will be O(n). – Eric Postpischil Jul 26 '21 at 11:28
  • Your second edit just makes it sound like you don't understand asymptotic complexity. Is there a specific performance target you're trying to hit? Are there some bounds on your array size you haven't shared? Is asymptotic complexity actually what you care about in the first place? – Useless Jul 26 '21 at 12:18
  • @Useless After reading my edit, I realize that it does seem that way. Tbh, I just want to find the fastest way I can shift elements in an array. – Mathphile Jul 26 '21 at 12:25
  • 1
    If you really need to move memory, nothing is likely to perform better than `memmove` (unless you have an obscure platform with a badly-optimized libc). The only thing faster than the fastest way of moving memory, is not moving it in the first place, which you say is not suitable. For a **very** large array, some funky memory remapping might work, but only if you're shifting by whole pages, and it still has to save more than the TLB shootdown costs. – Useless Jul 26 '21 at 13:54

3 Answers3

4

Is there a similar function/operation in C that can do this for arrays in O(1) time? For example, [1, 2, 3, 4, 5] shifted by one space gives [2, 3, 4, 5, 0].

No. But you can produce the same result by just storing an offset and using modulo accesses

struct array {
  int offset;
  int size;
  int elems[];
};

struct array* alloc_array(int size)
{
  struct array *a = malloc(sizeof(struct array) + sizeof(int)*size);
  a->offset = 0;
  a->size = size;
  return a;
}

/* Constant-time shift!
   Just use shiftleft with a negative distance to shift right */
void shiftleft(struct array *a, int distance)
{
  a->offset += distance;
}

/* Note we actually made each of our N element accesses slightly more expensive.
   However, that was already linear (ie, when you print all elements)
   so we still have the desired asymptotic complexity.
*/
int elem(struct array *a, int idx)
{
  return a->elems[(idx + a->offset) % a->size];
}

NB. This is totally un-tested, it's just a sketch so you get the idea. Also, I've assumed you actually want an element shift, since that's what you showed. If you want to shift an array N bits to the left, it's a little more involved.


I found out that bit shifting uses a special circuit

PS. This "special circuit" is one of the CPU's built-in operations which operates in constant time on a fixed-size register. The register size is baked into the CPU hardware. The size of a C int is chosen to match the CPU register size.

There is no way of performing a constant-time (per register) operation on N register-sized integers (for example) that doesn't take N times as long, so obviously it has linear complexity.


I know what I'm trying to achieve cannot be done in literal O(1) time.

Well, asymptotic complexity is only relevant if the number of elements varies. If you always operate on arrays of fixed size, even an operation that's linear with the number of elements is constant-time per array.

  • Edit - please stop saying that

    bit-shifting in integers are considered O(1) time since they are so fast, even though they actually aren't O(1)

    1. I said it was constant time because it is, assuming a modern ALU with a barrel shifter

    2. if it wasn't constant-time, there is no value of "so fast" that would induce me to call it constant-time unless it always took the same number of clock cycles in practice (including reciprocal throughput). Since there's no practical way to measure when within a single clock cycle it completed, this is a distinction with no practical difference.

    Again, speed and asymptotic complexity are orthogonal. There is no sufficiently small coefficient for an O(N) operation to be considered O(1). There is of course a sufficiently small value of N, which I mentioned below (but you have declined to bound N in your question).

If you always operate on arrays of variable but bounded size, you can sort of make the same claim, although if your effective array size covers multiple orders of magnitude it won't be very persuasive.

I would be satisfied by any answers that suggest how I could improve the speed of shifting the array so that it somewhat seems like an O(1) operation (even though it actually isn't).

That makes literally no sense. Asymptotic complexity is not a measure of speed, and optimizing speed has no effect on asymptotic complexity.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • Although this reproduces the result, it isn't really what I need. Thanks for taking the time to answer though! – Mathphile Jul 26 '21 at 11:44
  • What I meant was that operations like bit-shifting in integers are considered O(1) time since they are so fast, even though they actually aren't O(1). I would like to find something similar for shifting in arrays so that even though the time scales linearly, the shifting operation itself performs so fast in comparison to other operations that it can be considered O(1). – Mathphile Jul 26 '21 at 12:33
  • Why are you convinced that bit-shifting is linear time? A complex [ALU](https://en.wikipedia.org/wiki/Arithmetic_logic_unit#Bit_shift_operations) probably uses a [barrel shifter](https://en.wikipedia.org/wiki/Barrel_shifter) which provides constant time rotation of the fixed-width native register. Doing N shifts of different ints is qualitatively different than shifting one int N bits on this hardware. – Useless Jul 26 '21 at 13:49
1

As said in the comments, you should use memmove instead of memcpy as the source and destination overlap.

Another problem in your code is that you assume that there will be a "0" after your initialized array that you are free to use.

memmove and memcpy will probably need O(n) time, quicker would be a code something like this:

int arr1[] = {1, 2, 3, 4, 5, 0};
int *arr2 = &arr1[1];

for(int i=0 ; i<5 ; i++)
{
        printf("%d ", arr2[i]);
}
lulle2007200
  • 888
  • 9
  • 20
Henrik Carlqvist
  • 1,138
  • 5
  • 6
0

The cheap way is to not move large amounts of memory around, but just manipulate the index.


#include <stdio.h>

int main(void)
{
int arr1[] = {1, 2, 3, 4, 5};
int ii, jj;

for(ii=0 ; ii<5 ; ii++)
        {
        jj = (ii<4) ? ii+1 : ii;

        printf("%d ", arr1[jj] );
        }

printf("\n" );
return 0;
}
wildplasser
  • 43,142
  • 8
  • 66
  • 109