21
\$\begingroup\$

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]
\$\endgroup\$
1
  • \$\begingroup\$ The longest (theoretically) possible word in Boggle is 16 letters; do we need to be able to handle words longer than this? \$\endgroup\$
    – pxeger
    Feb 13, 2021 at 17:14

19 Answers 19

7
\$\begingroup\$

Husk, 21 20 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
\$\endgroup\$
5
  • \$\begingroup\$ 20 bytes \$\endgroup\$
    – Zgarb
    Sep 19, 2017 at 19:16
  • \$\begingroup\$ Nice, I won't rearrange though, to save on the explanation. \$\endgroup\$
    – H.PWiz
    Sep 19, 2017 at 19:18
  • \$\begingroup\$ 19 bytes using f instead of - \$\endgroup\$
    – Zgarb
    Sep 20, 2017 at 7:12
  • \$\begingroup\$ Thanks, I couldn't quite get that method to work myself. \$\endgroup\$
    – H.PWiz
    Sep 20, 2017 at 7:49
  • \$\begingroup\$ Gah! Thought I'd managed to tie you, hadn't noticed you're down to 19 now. \$\endgroup\$
    – Shaggy
    Sep 20, 2017 at 21:23
3
\$\begingroup\$

R, 142 126 121 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.

\$\endgroup\$
1
  • \$\begingroup\$ 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)])) \$\endgroup\$
    – plannapus
    Sep 20, 2017 at 12:57
3
\$\begingroup\$

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>

\$\endgroup\$
2
  • \$\begingroup\$ 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/… \$\endgroup\$
    – Shaggy
    Sep 20, 2017 at 7:39
  • \$\begingroup\$ Well done. Your use of reduce and includes makes your answer quite different from mine. \$\endgroup\$ Sep 20, 2017 at 14:49
3
\$\begingroup\$

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]

\$\endgroup\$
6
  • 2
    \$\begingroup\$ I think you can replace c[7]?11:c[6]?5:c[5]?3:c[4]?2:1 with '00011234'[c.length]||11. \$\endgroup\$
    – Arnauld
    Sep 19, 2017 at 22:28
  • \$\begingroup\$ 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! : ) \$\endgroup\$ Sep 19, 2017 at 22:31
  • 1
    \$\begingroup\$ Ah yes, sorry, that should be '00011235'. \$\endgroup\$
    – Arnauld
    Sep 19, 2017 at 22:35
  • 1
    \$\begingroup\$ I think you can save another few bytes on top of @Arnauld's suggestion like so. \$\endgroup\$
    – Shaggy
    Sep 19, 2017 at 23:28
  • 1
    \$\begingroup\$ There are at least 3 letters, '11235'[c.length-3]||11, right? \$\endgroup\$
    – JollyJoker
    Sep 20, 2017 at 7:17
2
\$\begingroup\$

Perl 6, 64 bytes

{@^a.map:{sum .map:{@a.Bag{$_}>1??0!!(1,1,2,3,5)[.comb-3]||11}}}

Try it online!

\$\endgroup\$
2
\$\begingroup\$

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.
\$\endgroup\$
0
2
\$\begingroup\$

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!

\$\endgroup\$
2
\$\begingroup\$

Japt, 29 25 24 23 21 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.

\$\endgroup\$
2
\$\begingroup\$

Python 2, 106 105 88 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!

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

Jelly, 26 bytes

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

Try it online!

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ 114 bytes by combining our two approaches in the deduplication step. \$\endgroup\$
    – Giuseppe
    Sep 20, 2017 at 12:52
1
\$\begingroup\$

Java 10, 202 200 198 197 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
\$\endgroup\$
3
  • \$\begingroup\$ I love ternaries, and the only thing I hate about ScaLa is that they removed this ternary syntax. \$\endgroup\$ Sep 25, 2017 at 15:19
  • \$\begingroup\$ @V.Courtois Hmm, out of curiosity, how is the ternary syntax in Scala now? \$\endgroup\$ Sep 26, 2017 at 8:20
  • \$\begingroup\$ uh : if(bool1)exp1 else exp2 \$\endgroup\$ Sep 26, 2017 at 11:12
1
\$\begingroup\$

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)
\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$
0
\$\begingroup\$

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 :)

\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$
0
\$\begingroup\$

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
    }
}
\$\endgroup\$
0
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.