Swift Ninja Programming Challenge: Winners Announced!

Check out the winners of our recent Swift NInja programming challenge! By Marin Todorov.

Leave a rating/review
Save for later
Share

We recently had a 2-part “Swift Ninja” programming challenge:

  • The first part of this series contains programming challenges involving default values in functions, variadic parameters, map/reduce, advanced switch statement features, and more.
  • The second part of the series contains programming challenges involving recursion, operator overloading, lazy evaluation, currying, and more.

Be sure to try the challenges if you haven’t already. They’re a great way to practice your new Swift skills, and you’ll learn a ton!

At the end of the second part, we had a special final challenge that was open to competition among readers for fame and fortune.

Well, we’ve had a ton of great entries, and today we’re going to announce the winner!

The Final Challenge

Let’s start by reviewing the final challenge:

Given the structures:

Write a function called countHand that takes in an array of Card instances and counts the total value of the cards given. The requirements for your solution are as follows:

  • The function returns the value of the cards in the hand as an Int.
  • Does not use loops or nested functions.
  • Any Ace preceded by 5 of Diamonds is worth 100 points.
  • Any odd numeric card (3, 5, 7, 9) of any suit’s worth the double of its rank value in points when immediately preceded in the hand by any card of the Hearts.
enum Suit {
    case Clubs, Diamonds, Hearts, Spades
}
 
enum Rank {
    case Jack, Queen, King, Ace
    case Num(Int)
}
 
struct Card {
    let suit: Suit
    let rank: Rank
}
enum Suit {
    case Clubs, Diamonds, Hearts, Spades
}
 
enum Rank {
    case Jack, Queen, King, Ace
    case Num(Int)
}
 
struct Card {
    let suit: Suit
    let rank: Rank
}

The Participants

A number of brave ninjas submitted their solutions to the final challenge. Feast your eyes upon their code and praise their skills:

I recommend you check out all solutions – they are way more diverse than I initially expected. Nice job everyone and thank you very much for participating!

I considered a number of aspects while choosing the winner so let me share some of my observations while I was evaluating the code.

Correctness

All provided solutions produced correct results – congratulations everyone! I know the solution requirements were a bit strange, but it was only because I wanted to challenge you into creatively solve how to keep track of the last card’s value.

Some of the participants figured out that the first card in the hand never brings any value to the result. Even though their solutions aren’t producing a “more correct” result, it’s a nice optimization catch – kudos to Dojomaroc, Epinaud, and Tomek.

Brevity

Most of the participants embraced the code style of the article and produced tight and clean solutions. I personally like the solutions by Dojomaroc and Pasil – both of them kept the function code to a single return statement where a single switch plays the main role. Nice job!

Marzapower provided the longest but most flexible solution (check it out, well laid down busines logic there). He bravely went against my “brevity” requirement and receives an honorable mention for that!

Use of Swift features

Even though use of advanced Swift doesn’t always result in the fastest or shortest solution, it’s always interesting to see what other programmers create with this amazing language.

Terkans used the most exotic structure from the Swift standard library (that I must admit haven’t heard before I saw his code): Zip2.

Zip2‘s init takes in two SequenceType values and combines them into a single Sequence made of the values bound into tuples. Cool! Check his solution for an example how to use Zip2. Terkans also used the + as the closure param to Array.reduce(), which I liked very much.

Readability

Dojomaroc‘s solution was clearly the most readable just because he included a ton of comments. This is the kind of code I personally would love to see if I jump in on a project mid-way. Thumbs up!

Performance

Dojomaroc gave me an interesting idea with his comment, namely to measure the performance of each solution. Interestingly no the same solution performs best when I tested the solutions on a debug build and on a release build. I tested all solutions with the same card hand and letting each solution roll 100,000 times.

Dojomaroc and Tomek didn’t use optionals so their solutions came out first in the debug build measures (Dojomaroc’s solution sometimes coming few milliseonds faster). However the races between release builds came out quite different (lower result is better):

results_speed

The fastest solution in the test clearly is Pasil‘s – thumbs up!

The Runner Up

It was extremely hard choosing between these solutions; I was very impressed with what everyone came up with.

In the end, it came down to two – and since it was so hard to decide, I wanted to give the runner up a prize as well.

The runner up is Pasil – congratulations! Ray will be in touch soon to deliver your prize – a free PDF of your choice from this site.

Here’s Pasil’s solution:

func countHand(cards: [Card]) -> Int {
  return cards.reduce((nil, 0)) { (prev: (card: Card?, sum: Int), card: Card) in
    if let prevCard = prev.card {
      switch (prevCard.suit, prevCard.rank, card.rank) {
      case (.Diamonds, .Num(5), .Ace):
        return (card, prev.sum + 100)
      case (.Hearts, _, .Num(let value)) where value % 2 == 1:
        return (card, prev.sum + 2 * value)
      default:
        return (card, prev.sum)
      }
    }
    return (card, 0)
  }.sum
}

And the winner is…

Dojomaroc is the ultimate Swift ninja!

Dojomaroc is the ultimate Swift ninja!

At long last, it’s time to announce the ultimate Swift Ninja: Dojomaroc!

It was a really close call but in the end Dojomaroc’s solution was just a tiny bit better overall.

The fact that he ignored the first card in the hand and thanks to that he didn’t have to use any optionals made his code clearer to read and fast both on a debug and release builds.

Congratulations Dojomaroc! Ray will be in touch soon to deliver your prize – a free copy of our three brand new Swift books coming out later this year.

Here’s Dojomaroc’s solution:

func countHand(cards: [Card]) -> Int {
  
  // If the number of cards is inferior to 2, the value of the hand is automatically 0
  // Note that the dropFirst() function doesn't mutate the cards array, 
  // so cards[0] can be passed as the initial value of "reduce()".
  // Only cards that have a preceding card need to be evaluated by "reduce()", 
  // that's why reduce() is applied to dropFirst(cards).
  // "previous" is a tuple that keeps track of (the total value of the hand, and the last card evaluated by "reduce()")
  
  return cards.count < 2 ? 0 : dropFirst(cards).reduce((0, cards[0])) { (previous: (totalValue: Int, card: Card), currentCard: Card) in
    
    // For the previous card, we are interested in its rank and suit. 
    // As for the current card we are only concerned with its rank.
    
    switch (previous.card.rank, previous.card.suit, currentCard.rank) {
      
      // Statistically speaking: to have an odd numeric card (3, 5, 7, 9) 
      // preceded by any rank of Hearts is more probable than
      // having an Ace preceded by 5 of Diamonds . 
      // That explains the value associated (100), and more importantly 
      // the order in which the first two cases are evaluated. 
      
      case ( _, .Hearts, .Num(let rank)) where rank % 2 == 1:
        return (previous.totalValue + 2 * rank, currentCard)
      
      case (.Num(5), .Diamonds, .Ace):
        return (previous.totalValue + 100, currentCard)
      
      default:
        return (previous.totalValue, currentCard)
      
    }}.totalValue // Finally, return .totalValue of the tuple "previous"
}