Вращение массива битов в C - PullRequest
       41

Вращение массива битов в C

1 голос
/ 18 сентября 2010

Я только начал изучать C и у меня возникли проблемы с кодом, который я хочу написать.

По сути, у меня есть эта структура, представляющая собой битовый массив с количеством битов в массиве и указатель на буфер символов, в котором хранятся биты.

Моя стратегия для вращения массива битов - просто взять число вращений (изменить длину, чтобы избежать полных вращений) и использовать простой алгоритм обращения для вращения массива.

EDIT:

Однако моя проблема в том, что я хочу повернуть биты в реальном буфере.

Я также хочу иметь возможность вращать подпоследовательность битов во всем битовом массиве. Поэтому для 1101101 я мог бы захотеть повернуть (0-индексированная слева) подпоследовательность, начиная с индекса 2 и заканчивая индексом 5. Я не совсем уверен, как использовать мой буфер символов для этого.

Спасибо за помощь!

struct arrayBits{
   size_t numBits;
   char *buf;
 }

Массив buf содержит 8-битные целые числа, а не bools, как я упоминал ранее.

Я могу получить доступ к отдельному биту и установить его, просто индексировав в байт, содержащий нужный бит (так для массива ab, ab-> buf [index_of_desired_bit / 8], а затем выполнив некоторые побитовые операции над чтобы изменить значение, из соображений производительности.

РЕДАКТИРОВАТЬ: Спасибо всем за все предложения. Я посмотрел на все из них, и я думаю, что я понимаю код лучше. Вот код, который я закончил писать, однако, я думаю, что есть некоторые проблемы с ним.

Несмотря на то, что он проходит некоторые из моих базовых тестовых случаев, он, кажется, работает слишком быстро на битовой матрице размером 98775 бит, заполненной случайным образом. Под этим я подразумеваю, есть ли какой-то случай, когда мой код просто терпит неудачу и вылетает? Тестовые случаи выполняют три чередования подряд на полном 98775-битном массиве. Один оборот -98775/4 (<- это size_t, так что оборачиваться?), Один поворот 98775/4, а затем окончательный поворот 98775/2. </p>

Что-то я пропускаю или какую-то проблему я не вижу?

/*Reverse a bit array*/
/*v1.1: basic bit reversal w/o temp variable*/
static void arrayReversal(bitarray_t *ba, size_t begin, size_t end){
while(begin < end)
{
    bitarray_set(ba, begin, (bitarray_get(ba, begin) ^ bitarray_get(ba, end))); /*x = x ^ y*/
    bitarray_set(ba,  end,   (bitarray_get(ba, begin) ^ bitarray_get(ba, end))); /*y = x ^ y*/
    bitarray_set(ba, begin, (bitarray_get(ba, begin) ^ bitarray_get(ba, end))); /*x = x ^ y*/
    begin++;
    end--;
}

}


/*Main Rotation Routine*/
void bitarray_rotate(bitarray_t *ba, size_t bit_off, size_t bit_len, ssize_t bit_right_amount) {
assert(bit_off + bit_len <= ba->bit_sz);
assert(bit_off + bit_len > 0);

if(bit_off + bit_len > ba->bit_sz || bit_off + bit_len < 0) 
    {
        printf("\nError: Indices out of bounds\n");
        return;
    }

/*Find index to split bitarray on*/
if(bit_len == 0) return; //Rotate only 1 bit i.e. no rotation

size_t reversal_index;
reversal_index  = modulo(-bit_right_amount, bit_len);

if(reversal_index == 0) return; //No rotation to do

/*3 bit string reversals*/

assert(reversal_index - 1 + bit_off < ba->bit_sz);
/* Reverse A*/
arrayReversal(ba, bit_off, reversal_index - 1 + bit_off); 
assert(reversal_index + bit_off < ba->bit_sz);

/*Reverse B*/
arrayReversal(ba, reversal_index + bit_off, (bit_off + bit_len - 1));

/*Reverse ArBr*/    
arrayReversal(ba, bit_off, (bit_off + bit_len -1));

}

Ответы [ 5 ]

2 голосов
/ 19 сентября 2010

Хорошо, простой способ начать - это рассмотреть, как вращать биты в одном значении. Допустим, у вас есть x, что является N -битным значением, и вы хотите повернуть его на k мест. (Я только собираюсь посмотреть на вращение вверх / влево, это легко преобразовать в вниз / вправо). Первое, на что нужно обратить внимание, это то, что если k = N, то х не изменяется. Поэтому перед вращением мы хотим уменьшить k по модулю N, чтобы отбросить полные вращения.

Далее мы должны заметить, что во время вращения старшие биты k будут перемещаться к нижней части значения, а младшие биты N-k будут перемещаться вверх k местами. Это то же самое, что сказать, что верхние k -биты перемещаются вниз N-k мест. Причина, по которой мы формулируем это таким образом, заключается в том, что в Си есть операторы сдвига, но не вращения.

В псевдо-C мы можем сказать:

#define N sizeof(type)*8
type rotate(type x, int k) {
  type lower = x & ((1 << (N-k)) - 1);
  type upper = x >> (N-k) & ((1 <<k)-1);
  return upper | lower;
}

Это касается простого атомарного случая, просто замените type на char или int в зависимости от ситуации. Если type не подписано, то маска значения upper не нужна.

Следующее, что нужно рассмотреть, - это вращение в массиве значений. Если вы думаете о приведенном выше коде как о склеивании двух половинок значения, то в более сложном случае нам нужно склеить верхнюю и нижнюю части из разных мест в массиве. Если k мало, то эти места находятся рядом в массиве, но когда k>N, мы вращаемся через более чем одно промежуточное слово.

В частности, если мы вращаемся на k мест, то мы перемещаем биты из k/N слов в массиве, а N биты могут охватывать floor(k/N) и ceil(k/N) мест в массиве. Хорошо, теперь мы готовы собрать все это вместе. Для каждого слова в массиве новые старшие биты N-(k mod N) будут младшими битами на расстоянии floor(k/N) слов, а новые младшие биты (k mod N) будут старшими битами на расстоянии ceil(k/N) слов.

В том же псевдо-C (т. Е. Заменить type на то, что вы используете) мы можем сказать:

#define N sizeof(type)*8
#define ARR_SIZE ...
type rotate(type *x, int k,type *out) {
  int r = k % N;
  int upperOff = k/N;
  int lowerOff = (k+N-1)/N;
  for(int i=0; i<ARR_SIZE; i++) {
    int lowerPos = (i + ARR_SIZE - lowerOff) % ARR_SIZE
    int upperPos = (i + ARR_SIZE - upperOff) % ARR_SIZE
    type lower = x[lowerPos] & ((1 << (N-k)) - 1)
    type upper = x[upperPos] >> (N-k) & ((1 <<k)-1)
    out[i] = upper | lower;
  }
}

Во всяком случае, это намного больше, чем я собирался написать, поэтому я сейчас уйду. Должно быть достаточно легко преобразовать это в форму, которая работает на месте в одном массиве, но вы, вероятно, захотите сначала исправить типы и диапазон k, чтобы ограничить временное хранилище.

Если у вас есть какие-либо проблемы в этой области, то вам нужно посмотреть на растровую графику спрайтов. Например, эта проблема вращения использовалась для реализации прокрутки много-много лун назад в 8-битных играх.

0 голосов
/ 20 сентября 2010

Поскольку ваши критерии настолько сложны, я думаю, что самый простой способ сделать это - пройти по каждому биту и указать, где он будет в вашем новом массиве.Вы могли бы ускорить его для некоторых операций, скопировав целый символ, если он находится за пределами сдвинутых битов, но я не могу думать о том, как надежно сделать сдвиг, принимая во внимание все переменные, потому что начало и конец сдвинутой последовательности могут бытьв середине байтов и так может конец целых битов.Ключ должен получить новую битовую позицию для бита в старом массиве:

j = (i < startBit || i >= startBit + length) ? i : 
    ((i - startBit + shiftRightCount) % length) + startBit;

Код:

#include "stdafx.h"
#include <stdlib.h>
#include <string.h>

typedef struct {
   size_t numBits;
   unsigned char *buf;
} ARRAYBITS;

// format is big endian, shiftint left 8 bits will shift all bytes to a lower index
ARRAYBITS rotateBits(ARRAYBITS *pOriginalBits, int startBit, int length, int shiftRightCount);
void setBit(unsigned char *buf, int bit, bool isSet);
bool checkBit(unsigned char *buf, int bit);
ARRAYBITS fromString(char *onesAndZeros);
char *toString(ARRAYBITS *pBits);

int _tmain(int argc, _TCHAR* argv[])
{
    char input[1024];
    ARRAYBITS bits = fromString("11110000110010101110"); // 20 bits
    ARRAYBITS bitsA = rotateBits(&bits, 0, bits.numBits, 1);
    ARRAYBITS bitsB = rotateBits(&bits, 0, bits.numBits, -1);
    ARRAYBITS bitsC = rotateBits(&bits, 6, 8, 4);
    ARRAYBITS bitsD = rotateBits(&bits, 6, 8, -2);
    ARRAYBITS bitsE = rotateBits(&bits, 6, 8, 31);
    ARRAYBITS bitsF = rotateBits(&bits, 6, 8, -31);
    printf("Starting   : %s\n", toString(&bits));
    printf("All right 1: %s\n", toString(&bitsA));
    printf("All left 1 : %s\n", toString(&bitsB));
    printf("\n");
    printf("           :       ********\n");
    printf("Starting   : %s\n", toString(&bits));
    printf("6,8,4      : %s\n", toString(&bitsC));
    printf("6,8,-2     : %s\n", toString(&bitsD));
    printf("6,8,31     : %s\n", toString(&bitsE));
    printf("6,8,-31    : %s\n", toString(&bitsF));
    gets(input);
}

ARRAYBITS rotateBits(ARRAYBITS *pOriginalBits, int startBit, int length, int shiftRightCount)
{
    // 0-8 == 1, 9-16 == 2, 17-24 == 3
    ARRAYBITS newBits;
    int i = 0, j = 0;
    int bytes = 0;
    while (shiftRightCount < 0)
        shiftRightCount += length;
    shiftRightCount = shiftRightCount % length;

    newBits.numBits = pOriginalBits->numBits;
    if (pOriginalBits->numBits <= 0)
        return newBits;

    bytes = ((pOriginalBits->numBits -1) / 8) + 1;
    newBits.buf = (unsigned char *)malloc(bytes);
    memset(newBits.buf, 0, bytes);
    for (i = 0; i < pOriginalBits->numBits; i++) {
        j = (i < startBit || i >= startBit + length) ? i : ((i - startBit + shiftRightCount) % length) + startBit;
        if (checkBit(pOriginalBits->buf, i))
        {
            setBit(newBits.buf, j, true);
        }
    }
    return newBits;
}

void setBit(unsigned char *buf, int bit, bool isSet)
{
    int charIndex = bit / 8;
    unsigned char c = 1 << (bit & 0x07);
    if (isSet)
        buf[charIndex] |= c;
    else
        buf[charIndex] &= (c ^ 255);
}

bool checkBit(unsigned char *buf, int bit)
{
    // address of char is (bit / 8), bit within char is (bit & 7)
    int index = bit / 8;
    int b = bit & 7;
    int value = 1 << b;
    return ((buf[index] & value) > 0);
}

ARRAYBITS fromString(char *onesAndZeros)
{
    int i;
    ARRAYBITS bits;
    int charCount;

    bits.numBits = strlen(onesAndZeros);
    charCount = ((bits.numBits -1) / 8) + 1;
    bits.buf = (unsigned char *)malloc(charCount);
    memset(bits.buf, 0, charCount);
    for (i = 0; i < bits.numBits; i++)
    {
        if (onesAndZeros[i] != '0')
            setBit(bits.buf, i, true);
    }
    return bits;
}

char *toString(ARRAYBITS *pBits)
{
    char *buf = (char *)malloc(pBits->numBits + 1);
    int i;
    for (i = 0; i < pBits->numBits; i++)
    {
        buf[i] = checkBit(pBits->buf, i) ? '1' : '0';
    }
    buf[i] = 0;
    return buf;
}
0 голосов
/ 19 сентября 2010

Вам не нужен дополнительный буфер для вращения (только для вывода).Вы должны реализовать функцию для one поворота и зацикливания, например: (изменение вправо)

char *itoa2(char *s,size_t i)
{
  *s=0;
  do {
    memmove(s+1,s,strlen(s)+1);
    *s='0'+(i&1);
  } while( i>>=1 );
  return s;
}

size_t bitrotateOne(size_t i)
{
  return i>>1 | (i&1) << (sizeof i<<3)-1;
}

...

size_t i=12,num=17;
char b[129];
while( num-- )
{
  i = bitrotateOne(i);
  puts( itoa2(b,i) );
}
0 голосов
/ 19 сентября 2010

Я бы предложил указатель / смещение к начальной точке бита в буфере вместо вращения.Не стесняйтесь перегрузить любой оператор, который может быть полезен, на ум приходит оператор [].Поворот (n) будет просто операцией смещения + = n.Но я нахожу цель вашего комментария о - «Тем не менее, моя проблема в том, что я хочу повернуть реальный буфер» запутывающей.

0 голосов
/ 18 сентября 2010

Я предлагаю вам использовать операции на битовом уровне (>>, <<, ~, &, |) вместо того, чтобы тратить пространство на использование int.Тем не менее, используя массив int для поворота, передайте левый и правый индекс подстроки: </p>

void rotate ( struct arrayBits a, int left , int right )
{

   int i;
   int first_bit;

   if(*( a.buf + right ) == 1) first_bit = 1; 
   else first_bit = 0;

   for( i = left+1 ; i <= right ; i++ )
   {
      *( a.buf + i )=*( a.buf + i - 1 ); 
   }

   *a.buf = first_bit;    

}

Пример:

Если struct_array равен 010101,

rotate (struct_array,0,5); => вращает всю строку на 1 int вправо

o / p: 101010

rotate (struct_array,2,4); => вращает подстроку 1 int вправо

o / p: 01 001 1

Чтобы обратить битовый массив, вызовите функцию rotate () для подстроки, size_of_substring раз.

...