Well, after the mess my last project in Python ended up being, I’m committed this time to make the cleanest code possible.
Before I get any bad habits, I’d appreciate a second set of eyes on the code below.
If you see any bad directions I’m going down, or ways to make the code cleaner, I’d really appreciate being set on the right track now.
(See code in full context below.)
How would I make this code cleaner? (Pipeline operator? Recursion maybe?)
def calc_pyramid_prices_percents(prices, initial_percent) do
total_column_percent = calc_total_column_percent(prices, initial_percent)
total_pyramid_percent = calc_total_pyramid_percent(total_column_percent)
[ initial_price | _ ] = prices
total_distance = calc_total_distance(prices, initial_price)
Enum.map(prices, fn(price) -> calc_price_percent(price, initial_price, \
total_distance, initial_percent, total_pyramid_percent) end)
end
Is there a cleaner way to do this?
def calc_max_initial_percent(prices) do
inverted_prices = Enum.reverse(prices)
[d] = Enum.take((calc_pyramid_prices_percents(inverted_prices, 0)), -1) # get last price percent.
d # Return only the number.
end
Do you see any red flags or better coding practices that should have been followed in the code in full context?
defmodule PyramidCalculator do
@moduledoc """
Calculates a price percents adding up to 100 that form a pyramid shape
based on the relative distance of each price point from the initial price point.
If initial percent is greater then number of prices / 100, an inverted pyramid is returned.
## IMPORTANT
The first price in the price list must be the highest or lowest price in the list.
List is returned unsorted. Sort list ascending or descending order before invoking the pyramid calculator.
"""
@doc """
Returns a list of percents for each price point that would look like a pyramid
based on the relative distance of each price point from the initial price point.
## Examples:
## Normal pyramid example:
iex> prices = [1, 2, 3]
iex> initial_percent = 10
iex> PyramidCalculator.calc_pyramid_prices_percents(prices, initial_percent)
[10.0, 33.33333333333333, 56.666666666666664]
## Inverted pyramid example:
iex> prices = [1, 2, 3]
iex> initial_percent = 40
iex> PyramidCalculator.calc_pyramid_prices_percents(prices, initial_percent)
[40.0, 33.333333333333336, 26.666666666666668]
## Asymentrical pyramid price point distances example:
iex> prices = [1, 3, 7, 22]
iex> initial_percent = 5
iex> PyramidCalculator.calc_pyramid_prices_percents(prices, initial_percent)
[5.0, 10.517241379310345, 21.551724137931036, 62.93103448275862]
"""
def calc_pyramid_prices_percents(prices, initial_percent) do
total_column_percent = calc_total_column_percent(prices, initial_percent)
total_pyramid_percent = calc_total_pyramid_percent(total_column_percent)
[ initial_price | _ ] = prices
total_distance = calc_total_distance(prices, initial_price)
Enum.map(prices, fn(price) -> calc_price_percent(price, initial_price, \
total_distance, initial_percent, total_pyramid_percent) end)
end
@doc """
Calculates the maximum the initial percent can be so that the final
price point percent in the prices list is zero.
## Examples:
iex> prices = [1, 2, 3]
iex> PyramidCalculator.calc_max_initial_percent(prices)
66.66666666666666
iex> prices = [5, 10, 15, 20]
iex> PyramidCalculator.calc_max_initial_percent(prices)
50.0
"""
def calc_max_initial_percent(prices) do
inverted_prices = Enum.reverse(prices)
[d] = Enum.take((calc_pyramid_prices_percents(inverted_prices, 0)), -1) # get last price percent.
d # Return only the number.
end
@doc """
Calculates a perfect column-shaped price percents based on number of price points.
## Examples:
iex> prices = [1, 2, 3]
iex> PyramidCalculator.calc_column_percent(prices)
33.333333333333336
iex> prices = [5, 11, 25, 44]
iex> PyramidCalculator.calc_column_percent(prices)
25.0
"""
def calc_column_percent(prices), do: 100 / Enum.count(prices)
defp calc_total_column_percent(prices, initial_percent), do: Enum.count(prices) * initial_percent
# In case of an inverted pyramid, returns a negative total.
# Which means pyramid percent will be subtracted from the
# total column percent rather then added, on the final
# price percent calculation.
defp calc_total_pyramid_percent(total_column_percent), do: 100 - total_column_percent
defp calc_price_percent(price, initial_price, total_distance, \
initial_percent, total_pyramid_percent) do
ratio = calc_price_pyramid_ratio(initial_price, price, total_distance)
price_pyramid_percent = calc_price_pyramid_percent(ratio, total_pyramid_percent)
initial_percent + price_pyramid_percent
end
defp calc_price_pyramid_ratio(initial_price, price, total_distance), \
do: abs(price - initial_price) / total_distance
defp calc_price_pyramid_percent(ratio, total_pyramid_percent), do: ratio * total_pyramid_percent
defp calc_total_distance(prices, initial_price), do: _sum_distance(prices, initial_price, 0)
defp _sum_distance([], _initial_price, total_distance), do: total_distance
defp _sum_distance( [ price_head | price_tail ], initial_price, total_distance) do
_sum_distance(price_tail, initial_price, (total_distance + abs(price_head - initial_price)))
end
end