22
\$\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\$

18 Answers 18

6
\$\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\$
  • \$\begingroup\$ 20 bytes \$\endgroup\$ – Zgarb Sep 19 '17 at 19:16
  • \$\begingroup\$ Nice, I won't rearrange though, to save on the explanation. \$\endgroup\$ – H.PWiz Sep 19 '17 at 19:18
  • \$\begingroup\$ 19 bytes using f instead of - \$\endgroup\$ – Zgarb Sep 20 '17 at 7:12
  • \$\begingroup\$ Thanks, I couldn't quite get that method to work myself. \$\endgroup\$ – H.PWiz Sep 20 '17 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 '17 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\$
  • \$\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 '17 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\$
  • \$\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 '17 at 7:39
  • \$\begingroup\$ Well done. Your use of reduce and includes makes your answer quite different from mine. \$\endgroup\$ – Rick Hitchcock Sep 20 '17 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\$
  • 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 '17 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\$ – Rick Hitchcock Sep 19 '17 at 22:31
  • 1
    \$\begingroup\$ Ah yes, sorry, that should be '00011235'. \$\endgroup\$ – Arnauld Sep 19 '17 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 '17 at 23:28
  • 1
    \$\begingroup\$ There are at least 3 letters, '11235'[c.length-3]||11, right? \$\endgroup\$ – JollyJoker Sep 20 '17 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\$
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\$
  • \$\begingroup\$ 105 bytes. \$\endgroup\$ – Jonathan Frech Sep 20 '17 at 8:13
  • \$\begingroup\$ 104 bytes \$\endgroup\$ – reffu Sep 21 '17 at 12:17
  • \$\begingroup\$ @reffu thanks, I've made some modifications to golf even further :3 \$\endgroup\$ – Rod Sep 21 '17 at 12:44
  • \$\begingroup\$ @Rod Nice, I forgot about the second parameter of sum trick. \$\endgroup\$ – reffu Sep 21 '17 at 13:08
1
\$\begingroup\$

Jelly, 26 bytes

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

Try it online!

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

Java 8, 202 200 198 bytes

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

OR (also 198 bytes)

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

Can definitely be golfed.. Unfortunately Java has no build-ins or short methods to remove all items of all lists that are present in multiple..

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
      i=0,j,               //  Index integers
      f;                   //  Flag integer (used as boolean)
  for(;i<q;i++)            //  Loop (1) over the input array
    for(String s:a[i]){    //   Inner loop (2) over the Strings of the current List
      for(j=f=0;           //    Reset the flag `f` and index `j` both to 0
                j<q;)      //    Inner loop (3) over the input list again
        f|=a[j].contains(s)//     If the current list (3) contains the current String (2)
           &!a[i].equals(a[j++])?
                           //     and the current list (3) is not the current list (1)
            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)
      if(f<1)              //    If the flag is still 0 (so the current String is unique)
        r[i]+=             //     Increase the current item in the result integer-array by:
              (j=s.length())<5?
                           //      If the length is below 5:
               1           //       By 1
              :j<6?        //      Else-if the length is below 6:
               2           //       By 2
              :j<7?        //      Else-if the length is below 7:
               3           //       By 3
              :j<8?        //      Else-if the length is below 8:
               5           //       By 5
              :            //      Else (above 7):
               11;         //       By 11
    }                      //   End of inner loop (2)
                           //  End of loop (1) (implicit / single-line body)
  return r;                //  Return the resulting integer-array
}                          // End of method
\$\endgroup\$
  • \$\begingroup\$ I love ternaries, and the only thing I hate about ScaLa is that they removed this ternary syntax. \$\endgroup\$ – V. Courtois Sep 25 '17 at 15:19
  • \$\begingroup\$ @V.Courtois Hmm, out of curiosity, how is the ternary syntax in Scala now? \$\endgroup\$ – Kevin Cruijssen Sep 26 '17 at 8:20
  • \$\begingroup\$ uh : if(bool1)exp1 else exp2 \$\endgroup\$ – V. Courtois Sep 26 '17 at 11:12
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\$
  • \$\begingroup\$ 114 bytes by combining our two approaches in the deduplication step. \$\endgroup\$ – Giuseppe Sep 20 '17 at 12:52
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, privacy policy and cookie policy

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