I just started learning Elixir and stumbled upon this challenge over on Programming Puzzles & Code Golf. It is a well-suited task for beginners, so I chose to give it a go (to be clear, give it a go means solve it normally, not golfing it). To keep this question self-contained, here is the task – citing the linked post:
For a given positive integer \$n\$:
- Repeat the following until \$n < 10\$ (until \$n\$ contains one digit).
- Extract the last digit.
- If the extracted digit is even (including 0) multiply the rest of the integer by \2ドル\$ and add \1ドル\$ ( \2ドルn+1\$ ). Then go back to
step 1
else move tostep 4
.- Divide the rest of the integer with the extracted digit (integer / digit) and add the remainder (integer % digit), that is your new \$n\$.
For example, \61407ドル\$ gives \5ドル\$ when ran through this mechanism. I've come up with the following code:
defmodule ExtractAndDivide do
def extract_and_divide(x) do
if x < 10 do x
else
head = div x, 10
tail = rem x, 10
case rem tail, 2 do
0 -> head * 2 + 1 |> extract_and_divide
1 -> div(head, tail) + rem(head, tail) |> extract_and_divide
end
end
end
end
I'm seeking general advice, but mainly focusing on the following:
- Naming and Syntax better practices (usage of parenthesises, variable names etc.)
- Usage of
|>
(pipe) in this context. Would you ever see it used the way I did it in production code? Should I switch to "normal" notation instead? - Less verbose or more elegant way to avoid the seemingly unaesthetic
if x < 10 do x ... else ... end
structure, perhaps usingcase
would be better here? - Is recursion the way to go? Should I stick to it or are there better, equivalent methods?
-
\$\begingroup\$ Since both clauses in your case end with the same pipe you could pipe the case. Have the 0 case just return head*2+1 and put the pipe after the case's end. \$\endgroup\$Sinc– Sinc2020年03月18日 21:30:28 +00:00Commented Mar 18, 2020 at 21:30
2 Answers 2
I do not have much to say about the pipe operator. It looks fine to me, although maybe some else has something to say...
As for the if-else
clause, you can use cond
. It is basically a stylized if
statement that looks like a case
statement. One of your conditions can be x < 10 ->
and the other default statement would be true ->
.
I am not entirely sure if this is the best practice since the wording is a bit ambiguous in the documentation. Under the use case for cond
it says the following:
This is equivalent to
else if
clauses in many imperative languages (although used way less frequently here).
Which I interpret to be else if
clauses are used less often (implying that cond
is often preferred).
(untested)
defmodule ExtractAndDivide do
def extract_and_divide(x) when x < 10, do: x
def extract_and_divide(x) do
head = div x, 10
tail = rem x, 10
up_or_down = rem tail, 2
go_up_or_down(head, tail, up_or_down)
end
defp go_up_or_down(head, tail, up_or_down) when up_or_down == 0 do
extract_and_divide(head * 2 + 1)
end
defp go_up_or_down(head, tail, _up_or_down_is_one) do
extract_and_divide(div(head, tail) + rem(head, tail))
end
end
The idea with having multiple conditional function clauses is twofold: it reduces nesting (and thus should increase readability), and - quite powerful - it allows to to easily test individual conditional parts. Whether to make the recursive call to extract_and_divide by piping the calculation through the function invocation or directly (like I did purely to show the alternative) is largely a matter of taste.
(note: skipped/circumvented potential nitpicking around naming and whether up_or_down
shouldn't be a boolean; I just wanted to illustrate the pattern of doing logic by using multiple function heads)
-
\$\begingroup\$ The separate head to handle the x<10 case is good. The first function head for go_up_or_down could be simpler:
defp go_up_or_down(head, tail, 0) do
. There is no need to use a when clause for an exact match. \$\endgroup\$Sinc– Sinc2020年03月18日 21:28:31 +00:00Commented Mar 18, 2020 at 21:28