【问题标题】:Using c and bit shifting to solve a specific requirement使用 c 和位移来解决特定要求
【发布时间】:2013-07-21 18:31:17
【问题描述】:

我有一个 16 个字母的字母表。给定一个句子,我想计算每个字母的频率,然后使用巧妙的位移将所有频率封装在一个数字中。让我们假设这些句子每个总是 100 个字母,并且假设没有字母出现超过 31 次,我想要这样的东西:

A: occurs 2 times -> 0010
B: occurs 10 times -> 1010
C: occurs 7 times -> 0111

等等。

现在,我想将这样的连接起来: 001010100111...

我只是集中了上面的频率。为了方便存储数字,我想将上面的二进制转换为 64 位无符号整数。

我的另一个要求是有那么长并重新提取每个字母的频率。因此,我需要能够生成小数,然后将其解析为各个频率位。

我将如何在 c 中做到这一点?我可以对这些频率进行位移和添加,但这意味着我正在重叠频率。另一个问题是在提取频率时,我怎么知道要移动多少位,因为尾随的 0 是微不足道的并且没有保存在十进制中,但它们在我的算法中非常重要。

有什么聪明的主意吗?谢谢你。

【问题讨论】:

  • 最多 31 意味着存储一个字母的计数需要 5 位,乘以 16 个字母意味着 80 位 - 你有问题。可以将每个字母的限制设为 15 吗?还是只有 12 个字母?
  • 您的规范最多只允许出现 15 个字母表中的单个字母。
  • 抱歉,我的意思是最多 15 个。
  • 一个解决方案可以通过使用汉明码适应 64 位整数来满足您最初的 16 个字母、31 个最大用法、100 个长句子的要求。这不是您建议的串联,但可行。

标签: c algorithm bit-shift


【解决方案1】:

你有两个问题:一个数学问题和一个编码问题。

让我们暂时忽略数学问题。您可以构建一个包含 16 个整数的数组,并在扫描文本时计算每个字母的出现次数。如果您假设没有字母出现超过 15 次,那么您不必担心溢出,您可以轻松地将计数放入 64 位整数中。你会写:

int counts[16];  // has the counts
unsigned long long freqs;  // this holds the encoded value

// after you compute the counts
freqs = 0;
for (int i = 0; i < 16; ++i)
{
    freqs <<= 4;
    freqs |= (counts[i] & 0xF);
}

此时,第一个字母的计数在freqs 的前 4 位,最后一个字母的计数在后 4 位。所有其他计数都介于两者之间。每个 64 位数字恰好占据 4 位。

现在,如果您希望能够处理更大的文本,或者一个字母出现的次数可能超过 15 次,您必须在计数后缩放数字,使最大值不超过 15。这就是数学我提到的问题。我想你可能会弄清楚如何处理那个。你只需要缩放数字。

【讨论】:

    【解决方案2】:

    这样就足够了:

    #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>
    
    const static int  SIZE       = 16;
    const static char ALPHABET[] = "0123456789ABCDEF";
    
    char* getFrequency(char* str);
    uint64_t getFrequencyNumber(char* freq);
    
    int main() {
      char*    str     = "1337CODE";
      uint64_t freqNum = getFrequencyNumber(getFrequency(str));
      printf("%llu\n",freqNum);
      return 0;
    }
    
    char* getFrequency(char* str) {
      int i,j;
      char* freq = (char*) calloc(SIZE, sizeof(char));
      for(i=0; str[i]; ++i)
        for(j=0; j<SIZE; ++j)
          if(str[i] == ALPHABET[j])
            if(freq[i] < 15) //ignore overflow
              (freq[j])++;
      return freq;
    }
    
    uint64_t getFrequencyNumber(char* freq) {
      uint64_t i,num;
      for(i=num=0; i<SIZE; ++i)
        num |= freq[i] << (4*i); //use bit shifting to concatenate 4 bit values
      return num;
    }
    

    【讨论】:

      【解决方案3】:

      试试这个,好处是不需要中间数组来计算你的字母:

      int ch_to_index(char ch) { return ch-'A'; }
      
      unsigned long long get_freq(unsigned long long freq, int index)
      {
          return (freq>>(4*index))&0x0f;
      }
      
      
      unsigned long long set_freq(unsigned long long freq, int index, unsigned long val)
      {
          return (  ((val&0x0fULL)<<(4*index)) | (freq & (0xffffffffffffffffULL ^ (0xfULL<<(4*index)))) );
      }
      
      unsigned long long inc_freq(unsigned long long freq, int index)
      {
          return set_freq(freq, index, get_freq(freq, index) +1) ;
      }
      
      int main()
      {
          int i;
          unsigned long long freq=0;
          freq = inc_freq(freq, ch_to_index('A'));
          freq = inc_freq(freq, ch_to_index('A'));
          freq = inc_freq(freq, ch_to_index('B'));
      
          for(i=0;i<16;i++)
          {
              printf("%i = %i\n", i, (int)get_freq(freq, i));
          }
      }
      

      【讨论】:

      • 有趣,我从来没有想过这种方法!赞一个!
      • 我只是认为有必要摆脱一个大数组。您有时可以通过将所有数据放入一个或多个适合一个 CPU 寄存器的变量来优化代码的速度。这些不需要访问缓存或 RAM。或者在为具有 1k 或更少 RAM 的 MCU 进行编码时,位优化可能会有所帮助。
      • 有趣的方法,如果你能保证值将保留在寄存器中,可能会非常快。如果您确定计数不会超过 15。
      • 此解决方案效果很好,但您需要更改行:'return ( ((val&0x0f)
      【解决方案4】:

      现有的答案很好;不过也许以下更好。

      很容易只使用一个 64 位数字,并增加其中的各个 4 位部分。

      例如,以下增加第 3、5 和 13 个字母的计数器(从 0 开始计数):

      uint64_t my_counters = 0;
      my_counters += (uint64_t)1 << (4 * 3);
      my_counters += (uint64_t)1 << (4 * 5);
      my_counters += (uint64_t)1 << (4 * 13);
      

      如果你的字母在 ASCII 表中是连续的(例如,[a-p]),很容易从它的数值计算出字母的索引:

      uint64_t my_counters = 0;
      size_t i;
      for (i = 0; str[i] != '\0'; ++i)
      {
          int index = str[i] - 'a';
          my_counters += (uint64_t)1 << (4 * index);
      }
      

      打印:

      char c;
      for (c = 'a'; c <= 'p'; ++c)
      {
          int index = c - 'a';
          int counter = (int)((my_counters >> (4 * index)) & 0xf);
          printf("Letter %c, count %d\n", c, counter);
      }
      

      注意:与您想要的相比,我的代码以相反的顺序连接位;似乎这种方式更清楚了。如果将4 * index 替换为60 - 4 * index,则可以颠倒顺序。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-04-13
        • 2021-06-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-09
        • 2014-04-17
        相关资源
        最近更新 更多