# C, 2765 (optimal)

### Edit

Now all in a single C file. This just finds all the optimal solutions. They all must have 6 words of 15 letters and one 10-letter word consisting of 8 letters of value 1 and two blanks. For that I only need to load a fraction of the dictionary and I don't have to look for 15 letter words with blanks.
The code is a simple exhaustive depth-first search.

    #include <stdio.h>
    #include <stdint.h>
    #include <string.h>
    struct w {
        struct lc { uint64_t hi,lo; } lc;
        char w[16];
    } w15[6000], w10[40000];
    int n15,n10;
    struct lc pool = { 0x12122464612, 0x8624119232c4229 };
    int pts[27] = {0,1,3,3,2,1,4,2,4,1,8,5,1,3,1,1,3,10,1,1,1,1,4,4,8,4,10};
    int f[27],fs[26], w15c[27],w15l[27][6000];
    int count(struct lc a, int l) { return (l < 16 ? a.lo << 4 : a.hi) >> 4*(l&15) & 15; }
    int matches_val(uint64_t a, uint64_t b) {
        uint64_t mask = 0x1111111111111111ll;
        return !((a - b ^ a ^ b) & mask);
    }
    int matches(struct lc all, struct lc a) { return matches_val(all.hi,a.hi) && matches_val(all.lo,a.lo); }
    int picks[10];
    void try(struct lc cur, int used, int level) {
        int c, i, must;
        if (level == 6) {
    	for (i = 0; i<27; i++) if (count(cur, i) && pts[i]>1) return;
    	for (i = 0; i < n10; i++) if(!(used & (1 << (w10[i].w[0] & 31))) && matches(w10[i].lc, cur)) {
    	    for (c = 0; c<level; c++) printf("%s ",w15[picks[c]].w);
    	    printf("%s\n",w10[i].w);
    	}
    	return;
        }
        for (i = 0; i < 26;i++) if (count(cur,fs[i])) break;
        must = fs[i];
        for (c = 0; c < w15c[must]; c++) { i = w15l[must][c]; if(!(used & (1 << (w15[i].w[0] & 31))) && matches(cur, w15[i].lc)) {
    	struct lc b = { cur.hi - w15[i].lc.hi, cur.lo - w15[i].lc.lo };
    	picks[level] = i;
    	try(b, used + (1 << (w15[i].w[0] & 31)), level+1);
        }}
    }
    int cmpfs(int *a, int *b){return f[*a]-f[*b];}
    void ins(struct w*w, char *s, int c) {
        int i;
        strcpy(w->w,s);
        for (;*s;s++)
    	if (*s&16) w->lc.hi += 1ll << 4*(*s&15); else w->lc.lo += 1ll << 4*(*s&15) - 4;
        if (c) for (i = 0; i < 27;i++) if (count(w->lc,i)) f[i]++, w15l[i][w15c[i]++] = w-w15;
    }
    int main() {
        int i;
        char s[20];
        while(scanf("%s ",s)>0) {
    	if (strlen(s) == 15) ins(w15 + n15++,s,1);
    	if (strlen(s) == 10) ins(w10 + n10++,s,0);
        }
        for (i = 0; i < 26;i++) fs[i] = i+1;
        qsort(fs, 26, sizeof(int), cmpfs);
        try(pool, 0, 0);
    }

Usage:

    $time ./scrab <sowpods.txt
    cc -O3    scrab.c   -o scrab
    JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LAURUSTINE
    JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LUXURIATED
    JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS LUXURIATES
    JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS ULTRAQUIET
    JUXTAPOSITIONAL DEMISEMIQUAVERS ACKNOWLEDGEABLY WEATHERPROOFING CONVEYORIZATION FEATHERBEDDINGS UTRICULATE
    JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LAURUSTINE
    JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LUXURIATED
    JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS LUXURIATES
    JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS ULTRAQUIET
    JUXTAPOSITIONAL DEMISEMIQUAVERS WEATHERPROOFING ACKNOWLEDGEABLY CONVEYORIZATION FEATHERBEDDINGS UTRICULATE
    OVERADJUSTMENTS QUODLIBETARIANS ACKNOWLEDGEABLY WEATHERPROOFING EXEMPLIFICATIVE HYDROGENIZATION RUBIACEOUS
    OVERADJUSTMENTS QUODLIBETARIANS WEATHERPROOFING ACKNOWLEDGEABLY EXEMPLIFICATIVE HYDROGENIZATION RUBIACEOUS
        
    real	0m1.754s
    user	0m1.753s
    sys	0m0.000s

Note every solution is printed twice because when adding a 15-letter 'W' word 2 orders are created because there are 2 'W' tiles.

The first solution found with the point breakdown:

    JUXTAPOSITIONAL 465
    DEMISEMIQUAVERS 480
    ACKNOWLEDGEABLY 465
    WEATHERPROOFING 405
    CONVEYORIZATION 480
    FEATHERBEDDINGS 390
    LAURUSTINE (LAURU?TI?E) 80
    no tiles left

### Edit: explanation

What makes searching the entire space possible? While adding a new word I only take into account words that have the rarest remaining letter. This letter need to be in some word anyway (and a 15 letter word since this will be a non 1-value letter, though I don't check for that). So I start with words containing `J, Q, W, W, X, Z` which have counts around `50, 100, 100, 100, 200, 500`. At lower levels I get more cutoff because some words are eliminated by the lack of letters. Breadth of the search tree at each level:

    0: 1
    1: 49
    2: 3046
    3: 102560
    4: 724040
    5: 803959
    6: 3469

Of course a lot of cutoff is gained by not checking non-optimal solutions (blanks in 15 letter words or shorter words). So it is lucky that 2765 solution can be achieved with this dictionary (but it was close, only 2 combinations of 15 letter words give a reasonable leftover). On the other hand it is easy to modify the code to find lower scoring combinations where not all the leftover 10 letters are 1-valued, though it would be harder to prove this would be an optimal solution.

Also the code shows classic case of premature optimization. This version of `matches` function makes the code only 30% slower:

    int matches(struct lc all, struct lc a) {
        int i;
        for (i = 1; i < 27; i++) if (count(a, i) > count(all, i)) return 0;
        return 1;
    }

I even figured out how to make the bit magic parallel comparison even shorter than in my original code (highest nibble cannot be used in this case, but this is not a problem, as I only need 26 out of 32 nibbles):

    int matches_val(uint64_t a, uint64_t b) {
        uint64_t mask = 0x1111111111111111ll;
        return !((a - b ^ a ^ b) & mask);
    }

But it gives zero advantage.

### Edit

Writing the explanation above I realized that most of the time is spent on scanning the list of words for those containing a specific letter not in the `matches` function. Calculating the lists upfront gave 10x speedup.