I worked out some code for a little interview-type puzzle question that requires one to implement code to solve a “Rock Paper Scissors” tournament. The task was to write a function that would accept a string of letters consisting of R, P, and S’s (denoting Rock, Paper, or Scissors) and return the letter of the winner or “None” if nobody won.
The rules are as follows:
- For each round, the 1st “player” (i.e. letter) competes with the 2nd, the 3rd with the 4th, the 5th with the 6th, and so on.
- If there’s an odd number of players, the odd-man-out advances to the next round
- If there’s a tie (e.g. RR), both are eliminated and do not advance to the next round.
- Keep advancing through rounds until there is one winner or return “None”.
I came up with code that meets the spec:
defmodule RockPaperScissors do
def find_winner(lineup) do
case _find_winner(String.codepoints(lineup), []) do
"" -> "None"
result -> result
end
end
# Nobody won
defp _find_winner([], []), do: ""
# Cases where only 1 player is left means we're done!
defp _find_winner([one_remaining], []), do: one_remaining
# Single player remaining (bc of odd number of players)
defp _find_winner([one_remaining], acc), do: _find_winner([], acc ++ [one_remaining])
# Advance to the next round!
defp _find_winner([], acc), do: _find_winner(acc, [])
defp _find_winner(["R", "R" | rest], acc), do: _find_winner(rest, acc)
defp _find_winner(["R", "P" | rest], acc), do: _find_winner(rest, acc ++ ["P"])
defp _find_winner(["R", "S" | rest], acc), do: _find_winner(rest, acc ++ ["R"])
defp _find_winner(["P", "R" | rest], acc), do: _find_winner(rest, acc ++ ["P"])
defp _find_winner(["P", "P" | rest], acc), do: _find_winner(rest, acc)
defp _find_winner(["P", "S" | rest], acc), do: _find_winner(rest, acc ++ ["S"])
defp _find_winner(["S", "R" | rest], acc), do: _find_winner(rest, acc ++ ["R"])
defp _find_winner(["S", "P" | rest], acc), do: _find_winner(rest, acc ++ ["S"])
defp _find_winner(["S", "S" | rest], acc), do: _find_winner(rest, acc)
end
For clarity, here are a few test cases:
assert RockPaperScissors.find_winner("R") == "R"
assert RockPaperScissors.find_winner("RR") == "None"
assert RockPaperScissors.find_winner("RRS") == "S"
assert RockPaperScissors.find_winner("RRRSRSPS") == "S"
This solution works, but I’m wondering about optimizations to this… appending to a list is expensive, but because the order of the players is important, prepending won’t work. So this ends up being O(n) complexity I think. I’m wondering if there are other ways to tackle this – using folding or captures or passing anonymous functions that would be more efficient so execution can happen more efficiently.
Any thoughts?