16 Jul 2013, 02:25
Dave_gnome_head_isolated_pragsmall

Dave Thomas (338 posts)

  • Pragmatic Bookshelf has offices in Texas (TX) and North Carolina (NC), so we have to charge sales tax on orders shipped to these states. The rates can be expressed as a keyword list1

    tax_rates = [ NC: 0.075, TX: 0.08 ]
    

    Here’s a list of orders:

    orders = [
        [ id: 123, ship_to: :NC, net_amount: 100.00 ],
        [ id: 124, ship_to: :OK, net_amount:  35.50 ],
        [ id: 125, ship_to: :TX, net_amount:  24.00 ],
        [ id: 126, ship_to: :TX, net_amount:  44.80 ],
        [ id: 127, ship_to: :NC, net_amount:  25.00 ],
        [ id: 128, ship_to: :MA, net_amount:  10.00 ],
        [ id: 129, ship_to: :CA, net_amount: 102.00 ],
        [ id: 120, ship_to: :NC, net_amount:  50.00 ] ]
    

    Write a function that takes both lists and returns a copy of the orders, but with an extra field, total_amount which is the net plus sales tax. If a shipment is not to NC or TX, there’s no tax applied.

A Possible Solution
defmodule Tax do

  def orders_with_total(orders, tax_rates) do
    orders |> Enum.map(add_total_to(&1, tax_rates))
  end

  def add_total_to(order = [id: _, ship_to: state, net_amount: net], tax_rates) do
    tax_rate = Keyword.get(tax_rates, state, 0)
    tax      = net*tax_rate
    total    = net+tax
    Keyword.put(order, :total_amount, total)
  end

end


tax_rates =  [ NC: 0.075, TX: 0.08 ]

orders = [
  [ id: 123, ship_to: :NC, net_amount: 100.00 ],
  [ id: 124, ship_to: :OK, net_amount:  35.50 ],
  [ id: 125, ship_to: :TX, net_amount:  24.00 ],
  [ id: 126, ship_to: :TX, net_amount:  44.80 ],
  [ id: 127, ship_to: :NC, net_amount:  25.00 ],
  [ id: 128, ship_to: :MA, net_amount:  10.00 ],
  [ id: 129, ship_to: :CA, net_amount: 102.00 ],
  [ id: 120, ship_to: :NC, net_amount:  50.00 ] 
]

IO.inspect Tax.orders_with_total(orders, tax_rates)


  1. I wish it were that simple….

10 Sep 2013, 15:59
Felipe_avatar_pragsmall

Felipe Coury (2 posts)

My naïve implementation was:

defmodule Taxes do
  def process_orders([], _tax_rates), do: []
  def process_orders([ head | tail ], tax_rates) do
    state = head[:ship_to]
    net = head[:net_amount]

    tax = tax_rates[state]

    if tax do
      tax_percent = 1 + tax
      total = net * tax_percent
    else
      total = net
    end

    new_head = Dict.put head, :total_amount, total
    [ new_head | process_orders(tail, tax_rates) ]
  end
end

tax_rates = [ NC: 0.075, TX: 0.08 ]
orders = [
    [ id: 123, ship_to: :NC, net_amount: 100.00 ],
    [ id: 124, ship_to: :OK, net_amount:  35.50 ],
    [ id: 125, ship_to: :TX, net_amount:  24.00 ],
    [ id: 126, ship_to: :TX, net_amount:  44.80 ],
    [ id: 127, ship_to: :NC, net_amount:  25.00 ],
    [ id: 128, ship_to: :MA, net_amount:  10.00 ],
    [ id: 129, ship_to: :CA, net_amount: 102.00 ],
    [ id: 120, ship_to: :NC, net_amount:  50.00 ] ]

IO.inspect Taxes.process_orders(orders, tax_rates)
25 Sep 2013, 02:35
Casual - sqr_pragsmall

Ryan Cromwell, Sr (4 posts)

My implementation using lots of little functions composed. This helped me learn the & operator. Some interesting discussion on the Elixir mailing list from this can be found here.

tax_rates = [ NC: 0.075, TX: 0.08 ]

orders = [ [ id: 123, ship_to: :NC, net_amount: 100.00 ],
            [ id: 124, ship_to: :OK, net_amount: 35.50 ],
            [ id: 125, ship_to: :TX, net_amount: 24.00 ],
            [ id: 126, ship_to: :TX, net_amount: 44.80 ],
            [ id: 127, ship_to: :NC, net_amount: 25.00 ],
            [ id: 128, ship_to: :MA, net_amount: 10.00 ],
            [ id: 129, ship_to: :CA, net_amount: 102.00 ],
            [ id: 120, ship_to: :NC, net_amount: 50.00 ] 
         ]

taxrateforstate = & Keyword.get tax_rates, &1, 0
taxratefororder = & (&1[:ship_to] |> taxrateforstate.())
taxfororder = & (&1[:net_amount] * taxratefororder.(&1))
totalfororder = & (&1[:net_amount] + &1[:tax])

orderwithtaxes = & Keyword.put &1, :tax, taxfororder.(&1)
orderwithtotal = & Keyword.put &1, :total, totalfororder.(&1)

orders |>
  Stream.map(orderwithtaxes.(&1)) |>
  Stream.map(orderwithtotal.(&1)) |>
  Enum.each( IO.inspect &1 )
24 Dec 2013, 16:12
Generic-user-small

Thomas Powell (1 post)

Looks like the code in the “possible solution”

    orders |> Enum.map(add_total_to(&1, tax_rates))

now needs to be written

    orders |> Enum.map(&(add_total_to(&1, tax_rates)))
24 Dec 2013, 20:37
Snapshot_2008101_248x248_pragsmall

Rafael Rosa (2 posts)

My implementation:

defmodule Shipping do
  def tax_rates, do: [ NC: 0.075, TX: 0.08 ]

  def orders, do: [
    [ id: 123, ship_to: :NC, net_amount: 100.00 ],
    [ id: 124, ship_to: :OK, net_amount: 35.50 ],
    [ id: 125, ship_to: :TX, net_amount: 24.00 ],
    [ id: 126, ship_to: :TX, net_amount: 44.80 ],
    [ id: 127, ship_to: :NC, net_amount: 25.00 ],
    [ id: 128, ship_to: :MA, net_amount: 10.00 ],
    [ id: 129, ship_to: :CA, net_amount: 102.00 ],
    [ id: 120, ship_to: :NC, net_amount: 50.00 ] ]

  def tax_for_state(state) do
    found = Enum.filter Shipping.tax_rates, fn {s, _} -> s == state end
    found[state] || 0.0
  end

  def calculate do
    lc [id: id, ship_to: ship_to, net_amount: net_amount] inlist orders do
      tax = tax_for_state(ship_to) * net_amount
      total = net_amount + tax
      [id: id, ship_to: ship_to, net_ammount: net_amount, total_amount: total]
    end
  end
end

Shipping.calculate

[[id: 123, ship_to: :NC, net_ammount: 100.0, total_amount: 107.5],
 [id: 124, ship_to: :OK, net_ammount: 35.5, total_amount: 35.5],
 [id: 125, ship_to: :TX, net_ammount: 24.0, total_amount: 25.92],
 [id: 126, ship_to: :TX, net_ammount: 44.8, total_amount: 48.384],
 [id: 127, ship_to: :NC, net_ammount: 25.0, total_amount: 26.875],
 [id: 128, ship_to: :MA, net_ammount: 10.0, total_amount: 10.0],
 [id: 129, ship_to: :CA, net_ammount: 102.0, total_amount: 102.0],
 [id: 120, ship_to: :NC, net_ammount: 50.0, total_amount: 53.75]]
30 Dec 2013, 18:42
Hugo-oculos-escuro_pragsmall

Hugo Pessoa de Baraúna (3 posts)

My solution using pattern matching and some clauses:

defmodule Tax do
  @tax_rates [ NC: 0.075, TX: 0.08 ]

  def orders_with_total(orders) do
    Enum.map(orders, &order_with_total/1)
  end

  def order_with_total(order = [_, { :ship_to, :NC }, _]) do
    order ++ [total_amount: order[:net_amount] + order[:net_amount] * @tax_rates[:NC]]
  end

  def order_with_total(order = [_, { :ship_to, :TX }, _]) do
    order ++ [total_amount: order[:net_amount] + order[:net_amount] * @tax_rates[:TX]]
  end

  def order_with_total(order) do
    order ++ [total_amount: order[:net_amount]]
  end
end

17 Jan 2014, 16:19
Generic-user-small

Brian Ehmann (1 post)

I could not figure out how to eliminate the if statement without having to hard code the rates.

defmodule Taxes do
  def update(_, []), do: []
	
  def update(rates, [head | tail]) do
    if rates[head[:ship_to]] do
      [ Dict.put(head, :total_amount, head[:net_amount] * (1 + rates[head[:ship_to]])) | update(rates, tail) ]
    else
      [ Dict.put(head, :total_amount, head[:net_amount]) | update(rates, tail) ]
    end
  end
end
11 Mar 2014, 08:00
Generic-user-small

Lorenzo lopez (1 post)

A little refining on on Hugo solution


defmodule Tax do

  @tax_rates [ NC: 0.075, TX: 0.08 ]

  def totals(orders) do
    Enum.map orders, &_total/1
  end

  defp _total(order_line = [ _, { :ship_to, place }, _]) when place in [:NC, :TX] do
    order_line ++ [total_amount: order_line[:net_amount]*(1+@tax_rates[place])]
  end

  defp _total(order_line), do: order_line ++ [total_amount: order_line[:net_amount]]

end

04 Aug 2014, 07:54
Generic-user-small

Patrick Oscity (6 posts)

Here’s my solution:

defmodule Accounting do
  def apply_tax(orders, tax_rates) do
    for order <- orders do
      Dict.put order, :total_amount, total_amount(order, tax_rates)
    end
  end

  def total_amount(order, tax_rates) do
    order[:net_amount] * (1.0 + tax_rate(order[:ship_to], tax_rates))
  end

  def tax_rate(state, tax_rates) do
    tax_rates[state] || 0.0
  end
end

tax_rates = [NC: 0.075, TX: 0.08]
orders = [
  [id: 123, ship_to: :NC, net_amount: 100.00],
  [id: 124, ship_to: :OK, net_amount:  35.50],
  [id: 125, ship_to: :TX, net_amount:  24.00],
  [id: 126, ship_to: :TX, net_amount:  44.80],
  [id: 127, ship_to: :NC, net_amount:  25.00],
  [id: 128, ship_to: :MA, net_amount:  10.00],
  [id: 129, ship_to: :CA, net_amount: 102.00],
  [id: 120, ship_to: :NC, net_amount:  50.00]
]

IO.inspect Accounting.apply_tax(orders, tax_rates)
  You must be logged in to comment