# Background

In Boggle, a round is scored by adding up the points for each unique word a player has found (i.e. any word that more than one player has found is worth 0 points). The points are calculated based on the number of letters in each word, as follows:

3 letters: 1 point

4 letters: 1 point

5 letters: 2 points

6 letters: 3 points

7 letters: 5 points

8 or more letters: 11 points

# Challenge

In this challenge, write a program or function that takes in a list of lists of strings representing each player's words and outputs a list of the players' scores. You can assume that there will be at least 2 players and all words will be 3 or more letters and will all be lowercase (or all uppercase if you would prefer). You may also assume that each player will only use each word once; that is, no player's list will contain duplicates. This is code golf, so shortest answer in bytes wins.

# Rules

Input can be taken in any reasonable format. Examples include a list of lists of strings, a list of comma-separated strings, a comma separated string on each line of input, etc. Output can be in the form of a list of integers (or your language's equivalent) or you can print the values to stdout using a separator of your choice (such as a newline).

# Test Cases

Input => Output

[["cat","dog","bird","elephant"],
["bird","dog","coyote"],
["dog","mouse"]]                 => [12,3,2]

[["abc","def","ghi"],
["ghi","def","abc"]]             => [0,0]

[["programming","puzzles"],
["code","golf"],
[]]                              => [16,2,0]

• The longest (theoretically) possible word in Boggle is 16 letters; do we need to be able to handle words longer than this? Feb 13, 2021 at 17:14

# Husk, 2120 19 bytes

-2 bytes thanks to Zgarb

Idea taken from A055228

ṠṀöṁ(⌈√Π-3▼8L)fε#Σ


Try it online!

### Explanation (Of older version)

            ṠṀ-oṠ-uΣ   Remove duplicated words
Σ   Concatenate
u    Remove duplicates
oṠ-     Remove those unique elements from the list (give list of elements that appear more than once)
ṠṀ-        Remove those words from each list in the input
m                      For each list
ṁ(                    Map then sum
L)           Length
▼8             Min(8,x)
-3               Minus 3
Π                 Factorial
√                  Square root
⌈                   Ceiling

• 20 bytes Sep 19, 2017 at 19:16
• Nice, I won't rearrange though, to save on the explanation. Sep 19, 2017 at 19:18
• 19 bytes using f instead of - Sep 20, 2017 at 7:12
• Thanks, I couldn't quite get that method to work myself. Sep 20, 2017 at 7:49
• Gah! Thought I'd managed to tie you, hadn't noticed you're down to 19 now. Sep 20, 2017 at 21:23

# R, 142126121 117 bytes

function(L)sapply(lapply(L,setdiff,(l=unlist(L))[duplicated(l)]),function(x)sum(c(1,1,2,3,5,11)[pmin(6,nchar(x)-2)]))


Try it online!

Takes L as a list of vectors of strings; returns the values.

First, it unlists the words, finds the duplicates, then removes them from the players' word lists. Then it takes these unique word lists and computes the scores of each, using pmin to ensure that words longer than 8 get scored as 11.

• You can shorten it to 108 bytes if you only take away the duplicates in the sapply inner function: function(L)sapply(L,function(x)sum(c(1,1,2,3,5,11)[pmin(6,nchar(x[!x%in%(l=unlist(L))[duplicated(l)]])-2)])) Sep 20, 2017 at 12:57

# JavaScript (ES6), 92 bytes

a=>a.map(b=>b.reduce((s,v)=>s+(a.filter(b=>b.includes(v))[1]?0:+"11235"[v.length-3]||11),0))


Somewhat similar to Rick Hitchcock's answer but created mostly independently; I used a different method of summing (reduce) and different method for checking for repeated terms (filter + includes). Credit to him for the idea of checking for item [1] instead of checking .length>1, though.

## Test Cases

let f=
a=>a.map(b=>b.reduce((s,v)=>s+(a.filter(b=>b.includes(v))[1]?0:+"11235"[v.length-3]||11),0))

;[[["cat","dog","bird","elephant"],["bird","dog","coyote"],["dog","mouse"]],[["abc","def","ghi"],["ghi","def","abc"]],[["programming","puzzles"],["code","golf"],[]]]
.forEach(test=>O.innerHTML+=JSON.stringify(test)+" -> "+JSON.stringify(f(test))+"\n")
<pre id=O></pre>

• You should be able to save a byte by using s+= and removing the parentheses around the ternaries. And 3 more by using map instead of reduce: tio.run/##NY/… Sep 20, 2017 at 7:39
• Well done. Your use of reduce and includes makes your answer quite different from mine. Sep 20, 2017 at 14:49

# JavaScript (ES6), 106 93 bytes

[Saved 13(!) bytes, thanks to Arnauld, Shaggy, and JollyJoker.]

a=>a.map(b=>b.map(c=>x+=(a+'').split,.filter(d=>d==c)[1]?0:+'11235'[c.length-3]||11,x=0)|x)


Test cases:

let f=

a=>a.map(b=>b.map(c=>x+=(a+'').split,.filter(d=>d==c)[1]?0:+'11235'[c.length-3]||11,x=0)|x)

console.log(f([["cat","dog","bird","elephant"],
["bird","dog","coyote"],
["dog","mouse"]
])
)  // [12,3,2]

console.log(f([["abc","def","ghi"],
["ghi","def","abc"]
])
) // [0,0]

console.log(f([["programming","puzzles"],
["code","golf"],
[]
])
) // [16,2,0]

• I think you can replace c[7]?11:c[6]?5:c[5]?3:c[4]?2:1 with '00011234'[c.length]||11. Sep 19, 2017 at 22:28
• That gives [15,2,0] instead of [16,2,0] for the last test case, but that's probably easily fixed. Will work some more after dinner, unless you post a genius answer (as you usually do). Thanks! : ) Sep 19, 2017 at 22:31
• Ah yes, sorry, that should be '00011235'. Sep 19, 2017 at 22:35
• I think you can save another few bytes on top of @Arnauld's suggestion like so. Sep 19, 2017 at 23:28
• There are at least 3 letters, '11235'[c.length-3]||11, right? Sep 20, 2017 at 7:17

{@^a.map:{sum .map:{@a.Bag{$_}>1??0!!(1,1,2,3,5)[.comb-3]||11}}}  Try it online! # Pyth, 26 bytes Uses H.PWiz's formula. m+Fm.E@.!a3hS,8lk2fq1/sQTd  Verify all the test cases. The initial version, 33 bytes: m+Fm*h+++*6>lk7y>lk6>lk5glk3q1/sQ  Verify all the test cases. # Explanation m+Fm*h+++*6>lk7y>lk6>lk5>glk3q1/sQ Full program. m Map over the input. m Map over each sublist. >lk3 Is the length higher than 2? 1 if True and 0 if False. + >lk5 Plus "is length higher than 5?". + y>lk6 Plus "is length higher than 6?", doubled. +*6>lk7 Plus "is length higher than 7?", times 6. h Increment. q1/sQ Count the occurrences of the element in the flattened input, and check if it equals 1. 0 if False, 1 if True. * Multiplication. +F Sum each sublist.  # Haskell, 76 75 bytes f l=[sum[fst.last$zip[0,0,1,1,2,3,5,11]w|w<-p,[w]==(filter(==w)=<<l)]|p<-l]


Try it online!

# Japt, 2925242321 20 bytes

Ëx@èøX ¥1©3nXÊm8)Ê¬c


Try it

## Explanation

Implicit input of array U.

Ëx@


Map over the array (Ë) and reduce each sub-array by addition (x) after passing its elements through the following function, where X is the current word.

èøX


Count (è) the elements in U that contain (ø) X.

¥1


Check if that is equal to 1.

©


Logical AND (&&).

3nXÊm8)


Subtract (n) 3 from the minimum of (m) 8 and the length (Ê) of X.

Ê¬c


Factorial, square root and round up, respectively.

# Python 2, 10610588 84 bytes

-1 byte thanks to Jonathan Frech
-1 (17) bytes thanks to reffu

lambda x:[sum(sum([1,0,1,1,2,6][:len(s)-2])*(x.count(s)<2)for s in l)for l in x]


Try it online!

• 105 bytes. Sep 20, 2017 at 8:13
• 104 bytes Sep 21, 2017 at 12:17
• @reffu thanks, I've made some modifications to golf even further :3
– Rod
Sep 21, 2017 at 12:44
• @Rod Nice, I forgot about the second parameter of sum trick. Sep 21, 2017 at 13:08

# Jelly, 26 bytes

ðċ@Ị¥ÐfL€«8ị“¡[żÇ’ḃ11¤Sµ€Ẏ


Try it online!

# R, 117 bytes

A completely different approach from the other R answer:

function(L)sapply(L,function(x)sum(c(0:3,5,11)[cut(nchar(x[x%in%names(which(table(unlist(L))<2))]),c(0,2,4:7,Inf))]))


Test cases:

> f=function(L)sapply(L,function(x)sum(c(0:3,5,11)[cut(nchar(x[x%in%names(which(table(unlist(L))<2))]),c(0,2,4:7,Inf))]))
> L=list(c("cat","dog","bird","elephant"),c("bird","dog","coyote"),c("dog","mouse"))
> f(L)
[1] 12  3  2
> L=list(c("abc","def","ghi"),c("ghi","def","abc"))
> f(L)
[1] 0 0
> L=list(c("programming","puzzles"),c("code","golf"),c())
> f(L)
[1] 16  2  0


Takes the names occurring only once in the list, convert their length to a factor based on the given cut-off points and translates that into scores that is then summed.

• 114 bytes by combining our two approaches in the deduplication step. Sep 20, 2017 at 12:52

# Java 10, 202200198197 194 bytes

a->{int q=a.length,r[]=new int[q],i=0,j,f;for(;i<q;i++)for(var s:a[i]){for(f=j=0;j<q;)f|=a[j].contains(s)&!a[i].equals(a[j++])?1:0;r[i]+=f<1?(j=s.length())<5?1:j<6?2:j<7?3:j<8?5:11:0;}return r;}


-1 byte thanks to @ceilingcat.

Explanation:

Try it here.

a->{                       // Method with ArrayList<String>[] parameter & int[] return-type
int q=a.length,          //  Length of the input-array
r[]=new int[q],      //  Result integer-array the size of the input-array,
//  initially filled with 0s by default
i=0,j,               //  Index integers
f;                   //  Flag integer (used as boolean)
for(;i<q;i++)            //  Loop i in the range [0, q):
for(var s:a[i]){       //   Inner loop over the Strings of the i'th List
for(f=j=0;           //    Reset the flag f and index j both to 0
j<q;)            //    Inner loop j in the range [0, q) again:
f|=a[j].contains(s)//     If the j'th list contains the current String
&!a[i].equals(a[j++])?
//     and the i'th and j'th list aren't the same:
1              //      Bitwise-OR the flag with 1 (0->1; 1->1)
:               //     Else:
0;             //      Bitwise-OR the flag with 0 (0->0; 1->1)
//    End of inner loop (3) (implicit / single-line body)
r[i]+=               //    Increase the i'th item in the result integer-array by:
f<1?           //     If the flag is still 0 (so the current String is unique):
(j=s.length())<5?
//      If the length of the String is below 5:
1            //       Increase by 1
:j<6?         //      Else-if the length is 5 instead:
2            //       Increase by 2
:j<7?         //      Else-if the length is 6 instead:
3            //       Increase by 3
:j<8?         //      Else-if the length is 7 instead:
5            //       Increase by 5
:             //      Else (the length is above 7):
11           //       Increase by 11
:              //     Else (the flag is 1 - thus the String is not unique):
0;            //      Leave it the same by increasing with 0
return r;}               //  Return the resulting integer-array

• I love ternaries, and the only thing I hate about ScaLa is that they removed this ternary syntax. Sep 25, 2017 at 15:19
• @V.Courtois Hmm, out of curiosity, how is the ternary syntax in Scala now? Sep 26, 2017 at 8:20
• uh : if(bool1)exp1 else exp2 Sep 26, 2017 at 11:12

# K (ngn/k), 34 bytes

{+/'(&7\5782020)'#''x^\:&1<#'=,/x}


Try it online!

• &1<#'=,/x get non-unique words (i.e. words that more than one player identified)
• x^\: remove the non-unique words from each player's submission
• #'' get the number of characters in each players' words
• (&7\5782020) a compressed version of 0 3 5 6 7 7 8 8 8 8 8 8
• (...)' do a binary search, returning the index of the first value in the left-side list not larger than each item in the right-side list (this translates word lengths to their scores)
• +/' get each player's point total (by summing the score of their individual words)

# Perl 5, 104 + 2 (-na) = 106 bytes

push@a,[@F];map$k{$_}++,@F}{map{$s=0;map$s+=(($l=y///c)<8?$l<7?$l<5?1:$l-3:5:11)*($k{$_}<2),@$_;say$s}@a


Try it online!

## Clojure, 102 bytes

#(for[p %](apply +(for[w p](if(next(filter #{w}(flatten %)))0(get{3 1 4 1 5 2 6 3 7 5}(count w)11)))))


next returns nil if there is only one word w :)

# PHP, 226 bytes

function x($a){foreach($a as$p){$q=call_user_func_array('array_diff',$a);array_push($a,array_shift($a));$x=0;array_map(function($b)use(&$x){$b=strlen($b);$x+=($b<5?1:($b==5?2:($b==6?3:($b==7?5:11))));},$q);$o[]=$x;}return $o;}  I think this could still be trimmed down quite a bit. Ungolfed: function x($a) {
foreach ($a as$p) {
$q = call_user_func_array('array_diff',$a);
array_push($a, array_shift($a));
$x = 0; array_map(function($b) use (&$x){$b = strlen($b);$x += ($b < 5 ? 1 : ($b == 5 ? 2 : ($b == 6 ? 3 : ($b == 7 ? 5 : 11))));
}, $q);$o[] = $x; } return$o;
}


Try it online!

# Scala, 242 bytes

The function takes, as a parameter a, a Seq[Set[String]] and returns an Array[Int]. I use an Array for it to be mutable (4-char loss).

var s=Seq("")
a.foreach(x=>x.foreach(y=>s:+=y))
var u:Array[Int]=Array()
var i= -1
a.foreach(t=>{i+=1
u:+=0
t.map(x=>{val l=x.length
if(s.count(_==x)<2){if(l>7)u(i)+=11
if(l==7)u(i)+=5
if(l==6)u(i)+=3
if(l==5)u(i)+=2
if(l>2&l<5)u(i)+=1}})})
u


Try it online!

Might be optimizable, since I did not even work on the

if(l>7)u(i)+=11
if(l==7)u(i)+=5
if(l==6)u(i)+=3
if(l==5)u(i)+=2
if(l>2&l<5)u(i)+=1


part. Thanks for this challenge!

# Swift 4, 164 bytes*

{$0.map{Set($0).subtracting(Dictionary(grouping:$0.flatMap{$0},by:{$0}).flatMap{$1.count != 1 ?$0:nil}).map{[0,1,1,2,3,5,11][min(max($0.count-2,0),6)]}.reduce(0,+)}}


The above expression is technically correct, pure Swift. However, the expression is so complex that, due to exponential blowup in the type inference system, can't be processed before the compiler gives up after some arbitrary timeout (like 15s or something).

To make this expression compilable with the current compiler, it can be broken down like this:

{
let n = Dictionary(grouping:$0.flatMap{$0},by:{$0}).flatMap{$1.count != 1 ?$0:nil} return$0.map{Set($0).subtracting(n).map{[0,1,1,2,3,5,11][min(max($0.count-2,0),6)]}.reduce(0,+)}
}


Test cases:

let f: (_ input: [[String]]) -> [Int] = {
let n = Dictionary(grouping:$0.flatMap{$0},by:{$0}).flatMap{$1.count != 1 ?$0:nil} return$0.map{Set($0).subtracting(n).map{[0,1,1,2,3,5,11][min(max($0.count-2,0),6)]}.reduce(0,+)}
}

let testcases: [(input: [[String]], expected: [Int])] = [
(input: [
["cat","dog","bird","elephant"],
["bird","dog","coyote"],
["dog","mouse"]
],
expected: [12,3,2]
),
(input: [
["abc","def","ghi"],
["ghi","def","abc"]
],
expected: [0,0]
),
(input: [
["programming","puzzles"],
["code","golf"],
[]
],
expected: [16,2,0]
),
]

for (caseNumber, testcase) in testcases.enumerated() {
let actual = f(testcase.input)
assert(actual == testcase.expected,
"Testcase #\(caseNumber) \(testcase.input) failed. Got \(actual), but expected \(testcase.expected)!")
print("Testcase #\(caseNumber) passed!")
}


Broken down:

let verboseF: (_ input: [[String]]) -> [Int] = { playerHands in
let allWords = playerHands.flatMap{$0} // demo data for first test case: // allWords: ["cat", "dog", "bird", "elephant", "bird", "dog", "coyote" "dog", "mouse"] let allWordsGroupedByThemselves = Dictionary(grouping: allWords, by: {$0})
/* allWordsGroupedByThemselves:
[
"cat": ["cat"],
"dog": ["dog", "dog", "dog"],
"bird": ["bird", "bird"],
"elephant": ["elephant"],
"coyote": ["coyote"], "mouse": ["mouse"]
]*/

let allWordsUsedMoreThanOnce = allWordsGroupedByThemselves.flatMap{$1.count != 1 ?$0:nil}
// allWordsUsedMoreThanOnce: ["dog", "bird"]

return playerHands.map{ hand in
// demo data for first hand of first test case:
// hand: ["cat","dog","bird","elephant"]

let uniqueWordsInHand = Set(hand)
// uniqueWordsInHand: ["cat","dog","bird","elephant"]

let uniqueWordsInHandNotUsedByOthers = uniqueWordsInHand.subtracting(allWordsUsedMoreThanOnce)
// uniqueWordsInHandNotUsedByOthers: ["cat", "elephant"]

let wordLengths = uniqueWordsInHandNotUsedByOthers.map{\$0.count}
// wordLengths: [3, 8]

let scores = wordLengths.map{ wordLength in
return [0,1,1,2,3,5,11][min(max(wordLength-2, 0), 6)] //A look up table that maps word length to word score
}
//scores: [1, 11]

let playerScore = scores.reduce(0,+)
// playerScore: 12

return playerScore
}
}


# ASP + Python, 137 bytes

u(P,W):-1{p(_,W)}1;p(P,W).s(P,S):-S=#sum{@v(W):u(P,W)};p(P,_).#script(python)
def v(w):return[1,1,2,3,5,11][min(len(w.string),8)-3]#end.


Expects data formatted as:

p(1,("cat";"dog";"bird";"elephant")).
p(2,("bird";"dog";"coyote")).
p(3,("dog";"mouse")).


Needs clingo 5.2.1 with python support.

Ungolfed:

unique(P,W):- 1 { player(_,W) } 1 ; player(P,W).
score(P,S):- S = #sum{@value(W): unique(P,W)} ; player(P,_).
#script (python)
def value(word):
return [1,1,2,3,5,11][min(len(word.string),8)-3]
#end.
`

The python function is heavily inspired from the python answer.