Sections

2025-11-17

Sator square search in C

// Started with ChatGPT, optimized manually. Valgrinded. Thread and Unicode unsafe.

// 300 ms on 7 MB american-english-insane with size 5 on Ryzen 5 3600 with -O3.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h> //toupper (replace with ICU case folding)

#define MAXW 262144
#define MAXL 32
#define HSIZE 262139

static char **WL; static unsigned WC = 0; static unsigned found = 0;

// HASHTABLE (with linked lists to handle collisions)
typedef struct W { char *w; struct W *n; } W;
static W *H[HSIZE]; // 2 MB
unsigned hfn(const char *s){
    unsigned h=5381;for(;*s;)h=((h<<5)+h)+(unsigned char)*s++;return h%HSIZE;
}
int has(const char *s){
    for(W*w=H[hfn(s)];w;w=w->n)if(!strcmp(s,w->w))return 1;
    return 0;
}
void ins(const char *s){
    unsigned h=hfn(s);W*w=malloc(sizeof* w);w->w=strdup(s);w->n=H[h];H[h]=w;
}

static inline void rev(const char *s, char *d) {
    size_t n=strlen(s);for(size_t i=0;i<n;++i)d[i]=s[n-1-i];
    d[n]=0; // not Unicode safe (replace with ICU reversing)
}

void search(int lvl, char **sq, int N){
    if(lvl==N/2){ //check square symmetry, not Unicode safe
        for(int r=0;r<N;++r) for(int c=0;c<N;++c) if(sq[r][c]!=sq[c][r]) return;
            puts(""); for (int i=0; i<N; ++i) puts(sq[i]); //output if fine
            ++found;
    }else{ //O(n) traversing WL & then O(1) looking up reversations in W
        for(unsigned i=0; i<WC; ++i){ //unparallelizable memory stingy design
            sq[lvl]=WL[i]; //direct to the word for that level and position
            if(sq[lvl][0]!=sq[0][lvl]) continue; //acrostic upper left corner
            // not Unicode safe
            if(N%2&&sq[lvl][N/2]!=sq[N/2][lvl]) continue; //center column
            rev(sq[lvl],sq[N-1-lvl]); //reverse row #lvl to row #lvl from bottom
            if(!has(sq[N-1-lvl])) continue; //check if reversed thing is real
            search(lvl+1,sq,N); //next level
        }
    }
}

void square(int N) {
    if (N < 1) return;
    unsigned c = 0;
    char **sq=calloc(N,sizeof(char*));
    //  0   1  N/2  N-2  N-1
    //  0   1   2   N/2  N-2  N-1
    for(int i=N/2+N%2;i<N;++i) sq[i]=calloc(N+1,sizeof(char));
    if(N%2){ //top half needn't to be allocated if using existing WL pointers
        for (c = 0; c < WC; ++c) {
            int pal = 1;
            for(int i=0;i<N/2;i++) if(WL[c][i]!=WL[c][N-1-i]){pal=0;break;}
            if(!pal) continue; // not Unicode safe
            sq[N/2]=WL[c];
            search(0,sq,N);
        }
    }else{
        search(0,sq,N);
    }
    for(int i=N/2+N%2;i<N;++i) free(sq[i]);
    free(sq);
}

int main(int c,char**v){
 if(c<3){fprintf(stderr,"\nUsage: %s <wordlist> <size>\n",v[0]);return 1;}
 unsigned L=atoi(v[2]);
 if(L<1||L>MAXL){fprintf(stderr,"\nSize must be 1 to %d.\n",MAXL);return 1;}
 FILE*f=fopen(v[1],"r");
 if(!f){fprintf(stderr,"\nCouldn't open file.\n");return 1;}
 WL=calloc(MAXW,sizeof*WL); // 2 MB
 if(!WL){fprintf(stderr,"\nCouldn't allocate wordlist.\n");return 1;}
 char b[MAXL*2];
 while(fgets(b,sizeof b,f)){
  b[strcspn(b,"\r\n")]=0;
  if(strlen(b)==L){
   for(char*p=b;*p;p++)*p=toupper(*p);
   WL[WC++]=strdup(b);ins(b);
  }
  if(WC>=MAXW){fprintf(stderr,"\nWord limit reached.\n");break;}
 }
 fclose(f);
 printf("\nLoaded %u words of length %u.\n",WC,L);
 square(L);
 printf("\nFound %u Sator squares of size %u.\n",found,L);
 for(int i=0;i<HSIZE;++i){W*w=H[i];while(w){W*n=w->n;free(w->w);free(w);w=n;}}
 for(unsigned i=0;i<WC;++i)free(WL[i]);
 free(WL);
 return 0;
}


No comments:

Post a Comment

Barely anyone comments, so I don't moderate. Free advertising, I guess.