Konwertuj ciąg znaków Elixir na liczbę całkowitą lub zmiennoprzecinkową

98

Muszę przekonwertować ciąg na wartość zmiennoprzecinkową lub liczbę całkowitą. Nie było takiej metody jak:

string_to_integer
Lahiru
źródło

Odpowiedzi:

154

Sprawdź Integer.parse/1i Float.parse/1.

José Valim
źródło
37
Zauważ, że zwróci to krotkę (jeśli się powiedzie), a nie bezpośrednio liczbę całkowitą. Jeśli chcesz to zrobić, zobacz odpowiedź @Szymon Jeż zString.to_integer/1
6
Czy jest jakiś powód, aby używać Integer.parse/1over String.to_integer/1?
Ian Vaughan,
10
@IanVaughan Integer.parse/1zwraca :erroratom, jeśli się nie powiedzie. String.to_integer/1rzuca (FunctionClauseError).
Jonathan Soifer,
52

Oprócz funkcji Integer.parse/1i, Float.parse/1które zasugerował José, możesz również sprawdzić String.to_integer/1i String.to_float/1.

Wskazówka: Patrz również to_atom/1, to_char_list/1, to_existing_atom/1dla innych konwersji.

Szymon Jeż
źródło
28

Dzięki ludziom na tej stronie, upraszczam odpowiedź tutaj:

{intVal, ""} = Integer.parse(val)

ponieważ sprawdza, czy przeanalizowano cały ciąg (a nie tylko przedrostek).

Roozbeh Zabihollahi
źródło
Spowoduje to zgłoszenie błędu, jeśli val nie jest czystą liczbą całkowitą. Dodałem przypadek do wyniku, aby upewnić się, że konwersja się powiodła. Druga klauzula może być ogólna do wychwycenia: błąd lub niepusty drugi ciąg, ponieważ nie obchodzi cię, czy dane wejściowe to „x3” czy „3x”.
Sinc
14

Istnieją 4 funkcje do tworzenia liczby z łańcucha

  • String.to_integer, String.to_float
  • Integer.parse, Float.parse

String.to_integerdziała ładnie, ale String.to_floatjest twardszy:

iex()> "1 2 3 10 100" |> String.split |> Enum.map(&String.to_integer/1)
[1, 2, 3, 10, 100]

iex()> "1.0 1 3 10 100" |> String.split |> Enum.map(&String.to_float/1)
** (ArgumentError) argument error
    :erlang.binary_to_float("1")
    (elixir) lib/enum.ex:1270: Enum."-map/2-lists^map/1-0-"/2
    (elixir) lib/enum.ex:1270: Enum."-map/2-lists^map/1-0-"/2

Jak String.to_floatobsługuje tylko dobrze sformatowaną liczbę zmiennoprzecinkową, np .: 1.0, not 1(integer). To zostało udokumentowane w dokumencie String.to_float's

Zwraca liczbę zmiennoprzecinkową, której reprezentacją tekstową jest łańcuch.

ciąg musi być ciągiem reprezentującym wartość zmiennoprzecinkową, w tym kropką dziesiętną. Aby przeanalizować ciąg bez kropki dziesiętnej jako zmiennoprzecinkowy, należy użyć Float.parse / 1. W przeciwnym razie zostanie zgłoszony ArgumentError.

Ale Float.parsezwraca krotkę z 2 elementów, a nie żądaną liczbę, więc umieszczenie jej w potoku nie jest „fajne”:

iex()> "1.0 1 3 10 100" |> String.split \
|> Enum.map(fn n -> {v, _} = Float.parse(n); v end)

[1.0, 1.0, 3.0, 10.0, 100.0]

Użycie elemdo pobrania pierwszego elementu z krotki sprawi, że będzie on krótszy i słodszy:

iex()> "1.0 1 3 10 100" |> String.split \
|> Enum.map(fn n -> Float.parse(n) |> elem(0) end)

[1.0, 1.0, 3.0, 10.0, 100.0]
HVNSweeting
źródło
11

Możesz przekonwertować go na char_list, a następnie użyć Erlang to_integer/1lub to_float/1.

Na przykład

iex> {myInt, _} = :string.to_integer(to_char_list("23"))
{23, []}

iex> myInt
23
Jonas
źródło
Jak używać go w funkcjach? Moim najlepszym rozwiązaniem jest to, fn q -> {v, _} = Float.parse(q); v endco mi się nie podoba. Lubię go używać Enum.map, np. list |> Enum.map(&String.to_float/1)Ale string.to_float nie działa dla liczb całkowitych?
Zhomart,
5
Decimal.new("1") |> Decimal.to_integer
Decimal.new("1.0") |> Decimal.to_float
kangkyu
źródło
1
IMO to najlepsza odpowiedź.
Marcin Adamczyk
Mam ten błąd: ** (UndefinedFunctionError) funkcja Decimal.new/1 jest niezdefiniowana (moduł Decimal nie jest dostępny)
Daniel Cukier
4

Problem z użyciem Integer.parse/1polega na tym, że sparsuje każdą nieliczbową część łańcucha, o ile znajduje się na końcu. Na przykład:

Integer.parse("01") # {1, ""}
Integer.parse("01.2") # {1, ".2"}
Integer.parse("0-1") # {0, "-1"}
Integer.parse("-01") # {-1, ""}
Integer.parse("x-01") # :error
Integer.parse("0-1x") # {0, "-1x"}

Podobnie String.to_integer/1ma następujące wyniki:

String.to_integer("01") # 1
String.to_integer("01.2") # ** (ArgumentError) argument error :erlang.binary_to_integer("01.2")
String.to_integer("0-1") # ** (ArgumentError) argument error :erlang.binary_to_integer("01.2")
String.to_integer("-01") # -1
String.to_integer("x-01") # ** (ArgumentError) argument error :erlang.binary_to_integer("01.2")
String.to_integer("0-1x") # ** (ArgumentError) argument error :erlang.binary_to_integer("01.2")

Zamiast tego najpierw sprawdź poprawność ciągu.

re = Regex.compile!("^[+-]?[0-9]*\.?[0-9]*$")
Regex.match?(re, "01") # true
Regex.match?(re, "01.2") # true
Regex.match?(re, "0-1") # false
Regex.match?(re, "-01") # true
Regex.match?(re, "x-01") # false
Regex.match?(re, "0-1x") # false

Wyrażenie regularne może być prostsze (np. ^[0-9]*$) W zależności od przypadku użycia.

Nibir Bora
źródło
0

Jeśli chcesz przekonwertować ciąg na dowolny typ liczbowy w ciągu i usunąć wszystkie inne znaki, prawdopodobnie jest to przesada, ale zwróci wartość typu float, jeśli jest to float lub int, jeśli jest to int lub nil, jeśli łańcuch nie zawiera typ liczbowy.

@spec string_to_numeric(binary()) :: float() | number() | nil
def string_to_numeric(val) when is_binary(val), do: _string_to_numeric(Regex.replace(~r{[^\d\.]}, val, ""))
defp _string_to_numeric(val) when is_binary(val), do: _string_to_numeric(Integer.parse(val), val)
defp _string_to_numeric(:error, _val), do: nil
defp _string_to_numeric({num, ""}, _val), do: num
defp _string_to_numeric({num, ".0"}, _val), do: num
defp _string_to_numeric({_num, _str}, val), do: elem(Float.parse(val), 0)
jrichocean
źródło