Elixir - 将字符串数字或空字符串转换为浮点数或零

Elixir - Convert a string number or empty string to a float or nil

我正在尝试将 price 字段(例如 "2.22""")转换为浮点数或 nil,然后将其添加到数据库中。

  def insert_product_shop(conn, product_id, shop_id, price) do
    priceFloat = nil
    if price not in [""] do
      price = elem(Float.parse(price), 0)
      priceFloat = price / 1
      IO.inspect(priceFloat)
    else
      priceFloat = nil
    end
    IO.inspect(priceFloat)
    changeset = Api.ProductShop.changeset(%Api.ProductShop{
      p_id: product_id,
      s_id: shop_id,
      price: priceFloat,
      not_in_shop_count: 0,
      is_in_shop_count: 0
      })
    errors = changeset.errors
    valid = changeset.valid?
    IO.inspect(changeset)
    case insert(changeset) do
      {:ok, product_shop} ->
        {:ok, product_shop}
      {:error, changeset} ->
        {:error, :failure}
    end
  end

输出是:

2.22
nil
#Ecto.Changeset<action: nil, changes: %{}, errors: [], data: #Api.ProductShop<>,
 valid?: true>

13:25:41.745 [debug] QUERY OK db=2.0ms
INSERT INTO "product_shops" ("is_in_shop_count","not_in_shop_count","p_id","s_id") VALUES (,,,) RETURNING "id" [0, 0, 40, 1]

如输出所示,priceFloat 变为零,我假设是因为当我将它设置为 2.22 时它超出了范围。也许我的代码太命令了。我如何重写它以将“2.22”转换为 2.22 而不使其为 nil,并允许将“”转换为 nil?

您可以结合使用模式匹配和方法重载来解决问题:

defmodule Example do
  def parsePrice(""), do: nil
  def parsePrice(price) when is_float(price), do: price
  def parsePrice(price) when is_binary(price) do
    {float, _} = Float.parse(price)
    float
  end
end

Example.parsePrice(2.22) |> IO.inspect
Example.parsePrice("2.22") |> IO.inspect

(等同于使用 case 语句实现)

如果您向此函数传递任何非二进制(字符串)或浮点数的内容,将导致模式不匹配错误。如果您有一些错误报告,这可能很好,这样您就可以检测到您的代码的意外使用。

为了获得更好的调试体验,我鼓励您通过 IEx.pry/0 使用内置调试器。

您可以使用 case 来评估 Float.parse 的返回值,并在 returns :error 时分配 nil,假设您的目的if是为了避免解析错误

def insert_product_shop(conn, product_id, shop_id, price) do
  priceFloat = case Float.parse(price) do
    {value, _remainder} -> value
    :error -> nil
  end
  ...
end

As the output shows, priceFloat becomes nil, I assume because when I set it to 2.22 it was out of scope.

差不多吧。而不是您尝试设置的变量超出范围,问题是您分配给 inside if 语句的变量 out of范围。它恰好与 if 语句外的变量同名。

解决方法是将if/else语句的结果赋值给变量。这是您的代码,改动很少:

price = "2.22"

priceFloat =
  if price not in [""] do
    elem(Float.parse(price), 0)
  else
    nil
  end

IO.inspect(priceFloat)

但是,它仍然不是很地道。您可以利用 Float.parse/1 returns :error 当输入为空字符串时像使用 case 表达式一样编写它:

priceFloat =
  case Float.parse(price) do
    {float, ""} -> float
    :error -> nil
  end

为了多样性,我会 post 另一种使用 with/1 特殊形式的方法。

with {f, ""} <- Float.parse("3.14"),
  do: f,
  else: (_ -> nil)

这里我们只显式匹配浮点数。任何拖尾的垃圾都将被丢弃。如果匹配成功,我们 return 浮点数,否则,我们 return nil.


小心Float.parse/1可能会被看起来像科学记数法的垃圾弄糊涂。

(with {f, ""} <- Float.parse("3e14"), do: f) == 300_000_000_000_000
#⇒ true

重要旁注:在if中分配priceFloat不会改变[=16的值=] 范围外的变量。 中的作用域非常重要,与大多数语言不同,不能将局部变量传播到最外层的作用域。

foo = 42
if true, do: foo = 3.14
IO.puts(foo)
#⇒ 42

好吧,在某种程度上,可以使用 var!/2 影响宏的最外层范围变量,而 if 确实是一个宏,但是这一切肯定远远超出了这个问题的范围.