I have written a C program that calculates Shannon entropy
for a C-String (const char *
). It works well but in many implementation the code uses some-kind of map like structures which are available by default in their respective language. But we all know C does not have maps. My code is very fast even for 100 megabytes of data.
Here's the formula to calculate Shannon entropy
$$ {\displaystyle \mathrm {H} (X)=-\sum _{i=1}^{n}{\mathrm {P} (x_{i})\log \mathrm {P} (x_{i})}} $$
Can anyone suggest me even more efficient way to calculate the entropy in C? Any kind of suggestion or improvement is appreciated.
Here's my code: TRY IT ONLINE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
static double entropy(const char *str)
{
if (!str) // return -1 is str is NULL
return -1.0;
size_t len = strlen(str); // get the length of str
/*
* cnt -> iterate over the str
* map_append -> stores the index of unique character in str
* imap_append -> stores the index of non-unique character in str
*/
size_t cnt = 0, map_append = 0, imap_append = 0;
int check = false; // boolean type value to store whether char is unique or not
char *map_char = (char *)calloc(len + 1, sizeof(char)); // heap allocation to store unique characters
size_t *map_cnt = (size_t *)calloc(len + 1, sizeof(size_t)); // heap allocation to store the freq. of unique characters
for (cnt = 0; cnt < len; cnt++)
{
check = false;
for (imap_append = 0; map_char[imap_append] != '\0'; imap_append++)
{
if (map_char[imap_append] == str[cnt])
{
check = true; // char is not unique
break;
}
}
if (check == false) // char is unique
{
map_char[map_append] = str[cnt];
map_cnt[map_append] = 1;
map_append++;
// add it to the `map_char` and set `map_cnt` value to 1
}
else
map_cnt[imap_append] += 1; // just increment the freq. of that not-unique char, at `imap_append`
}
/* just the application of the formula */
double result = 0.0;
double freq = 0.0;
for (size_t i = 0; map_char[i] != '\0'; i++)
{
freq = (double)map_cnt[i] / (double)len;
result -= freq * (log10(freq) / log10(2.0));
}
free(map_char); // no mem leaks
free(map_cnt); // no mem leaks
return result;
}
int main(int argc, char const **argv)
{
if (argc == 1)
{
fprintf(stderr, "no file input\n");
return EXIT_FAILURE;
}
FILE *f = fopen(argv[1], "r");
if (!f)
{
fprintf(stderr, "`%s` could not be opened or was not found.\n", argv[1]);
return EXIT_FAILURE;
}
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
fseek(f, 0, SEEK_SET);
char *data = (char *)calloc(len, sizeof(char));
fread(data, sizeof(char), len, f);
fclose(f);
printf("Length of file: `%s` = %zu\n", argv[1], len);
printf("Entropy of file: `%s` = %f\n", argv[1], entropy((const char *)data));
return EXIT_SUCCESS;
}
I compiled the above program using the command:
gcc -Og -O3 -Ofast -Os -s main.c -lm -o main
My output was: (That 100MB file was generated by random ASCII characters)
Length of file: `./temp.txt` = 104857600
Entropy of file: `./temp.txt` = 6.569855
Here's a benchmark on a 100 megabytes file on zsh shell on Intel i5-7200U:
./main ./temp.txt 4.54s user 0.06s system 99% cpu 4.598 total
hsearch(3)
, for a start. You don't have to implement it, and there is nothing hard about using it. \$\endgroup\$