Di Clojure bagaimana saya bisa mengubah String ke nomor?

128

Saya memiliki berbagai string, beberapa seperti "45", beberapa seperti "45px". Bagaimana cara saya mengonversi keduanya ke nomor 45?

appshare.co
sumber
32
Saya senang seseorang tidak takut untuk menanyakan beberapa pertanyaan mendasar.
octopusgrabbus
4
+1 - bagian dari tantangannya adalah bahwa dokumen Clojure terkadang tidak menjawab pertanyaan "dasar" yang kami anggap remeh dalam bahasa lain. (Saya memiliki pertanyaan yang sama 3 tahun kemudian dan menemukan ini).
Glenn
3
@octopusgrabbus - Saya akan tertarik untuk mengetahui "mengapa" orang takut mengajukan pertanyaan mendasar?
appshare.co
1
@ Zubair seharusnya hal-hal dasar sudah dijelaskan di suatu tempat sehingga Anda kemungkinan besar mengabaikan sesuatu dan pertanyaan Anda akan diturunkan karena "tidak ada upaya penelitian".
Al.G.
1
Bagi mereka yang datang ke sini dari Google mencari untuk mengkonversi "9"ke 9, ini adalah hal terbaik yang bekerja untuk saya: (Integer. "9").
weltschmerz

Jawaban:

79

Ini akan bekerja pada 10pxataupx10

(defn parse-int [s]
   (Integer. (re-find  #"\d+" s )))

itu hanya akan menguraikan digit kontinu pertama

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10
jhnstn
sumber
Jawaban bagus! Ini lebih baik daripada menggunakan read-string menurut saya. Saya mengubah jawaban saya untuk menggunakan teknik Anda. Saya membuat beberapa perubahan kecil juga.
Benjamin Atkin
ini memberi sayaException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza
83

Jawaban baru

Saya lebih suka jawaban snrobot. Menggunakan metode Java lebih sederhana dan lebih kuat daripada menggunakan read-string untuk kasus penggunaan sederhana ini. Saya memang membuat beberapa perubahan kecil. Karena penulis tidak mengesampingkan angka negatif, saya menyesuaikannya untuk memungkinkan angka negatif. Saya juga membuatnya sehingga membutuhkan nomor untuk memulai di awal string.

(defn parse-int [s]
  (Integer/parseInt (re-find #"\A-?\d+" s)))

Selain itu saya menemukan bahwa Integer / parseInt mem-parsing sebagai desimal ketika tidak ada radix yang diberikan, bahkan jika ada nol di depannya.

Jawaban lama

Pertama, untuk mengurai hanya integer (karena ini adalah hit di google dan itu informasi latar belakang yang bagus):

Anda dapat menggunakan pembaca :

(read-string "9") ; => 9

Anda dapat memeriksa apakah itu nomor setelah dibaca:

(defn str->int [str] (if (number? (read-string str))))

Saya tidak yakin apakah input pengguna dapat dipercaya oleh pembaca clojure sehingga Anda dapat memeriksa sebelum membacanya juga:

(defn str->int [str] (if (re-matches (re-pattern "\\d+") str) (read-string str)))

Saya pikir saya lebih suka solusi terakhir.

Dan sekarang, untuk pertanyaan spesifik Anda. Untuk mem-parsing sesuatu yang dimulai dengan integer, seperti 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29
Benjamin Atkin
sumber
Saya paling suka jawaban Anda - sayang sekali ini tidak disediakan oleh perpustakaan inti clojure. Satu kritik kecil - secara teknis Anda ifharus menjadi whenkarena tidak ada lagi blok di fns Anda.
quux00
1
Ya, tolong jangan berhenti membaca setelah cuplikan kode pertama atau kedua!
Benjamin Atkin
2
Angka-angka di depan dengan angka nol di depan. read-stringmenafsirkannya sebagai oktal: (read-string "08")melempar pengecualian. Integer/valueOfmemperlakukannya sebagai desimal: (Integer/valueOf "08")dievaluasi menjadi 8.
rubasov
Perhatikan juga bahwa read-stringmelempar pengecualian jika Anda memberinya string kosong atau sesuatu seperti "29px"
Ilya Boyandin
Seperti seharusnya. Saya menjawab pertanyaan dalam judul, dan apa yang orang harapkan ketika mereka melihat halaman ini, sebelum saya menjawab pertanyaan di badan pertanyaan. Ini cuplikan kode terakhir di badan jawaban saya.
Benjamin Atkin
30
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10
MayDaniel
sumber
Terima kasih. Ini sangat membantu dalam membagi produk menjadi beberapa urutan angka.
octopusgrabbus
3
Karena kita berada di tanah Jawa untuk jawaban ini, umumnya disarankan untuk digunakan Integer/valueOf, daripada konstruktor Integer. Kelas Integer menyimpan nilai antara -128 dan 127 untuk meminimalkan pembuatan objek. Integer Javadoc menjelaskan ini seperti halnya posting ini: stackoverflow.com/a/2974852/871012
quux00
15

Ini bekerja sebagai gantinya bagi saya, jauh lebih lurus ke depan.

(baca-string "123")

=> 123

f3rz_net
sumber
1
Hati-hati menggunakan ini dengan input pengguna. read-stringdapat mengeksekusi kode per dokumen: clojuredocs.org/clojure.core/read-string
jerney
ini bagus untuk input tepercaya, seperti misalnya teka-teki pemrograman. @ jerney benar: hati-hati untuk tidak menggunakannya dalam kode aktual.
hraban
10

AFAIK tidak ada solusi standar untuk masalah Anda. Saya pikir sesuatu seperti yang berikut ini, yang menggunakan clojure.contrib.str-utils2/replace, akan membantu:

(defn str2int [txt]
  (Integer/parseInt (replace txt #"[a-zA-Z]" "")))
skuro
sumber
Tidak direkomendasikan. Ini akan berfungsi sampai seseorang melemparnya 1.5... dan itu juga tidak menggunakan clojure.string/replacefungsi bawaan.
tar
8

Ini tidak sempurna, tapi ada sesuatu dengan filter, Character/isDigitdan Integer/parseInt. Ini tidak akan berfungsi untuk angka floating point dan gagal jika tidak ada digit di input, jadi Anda mungkin harus membersihkannya. Saya harap ada cara yang lebih baik untuk melakukan ini yang tidak melibatkan begitu banyak Jawa.

user=> (defn strToInt [x] (Integer/parseInt (apply str (filter #(Character/isDigit %) x))))
#'user/strToInt
user=> (strToInt "45px")
45
user=> (strToInt "45")
45
user=> (strToInt "a")
java.lang.NumberFormatException: For input string: "" (NO_SOURCE_FILE:0)
michiakig
sumber
4

Saya mungkin akan menambahkan beberapa hal ke persyaratan:

  • Harus dimulai dengan angka
  • Harus mentolerir input kosong
  • Toleransi melewati objek apa pun (toString adalah standar)

Mungkin sesuatu seperti:

(defn parse-int [v] 
   (try 
     (Integer/parseInt (re-find #"^\d+" (.toString v))) 
     (catch NumberFormatException e 0)))

(parse-int "lkjhasd")
; => 0
(parse-int (java.awt.Color. 4 5 6))
; => 0
(parse-int "a5v")
; => 0
(parse-int "50px")
; => 50

dan mungkin poin bonus untuk menjadikan ini metode multi yang memungkinkan untuk bawaan yang disediakan pengguna selain 0.

Tony K.
sumber
4

Memperluas jawaban snrobot:

(defn string->integer [s] 
  (when-let [d (re-find #"-?\d+" s)] (Integer. d)))

Versi ini mengembalikan nihil jika tidak ada digit di input, daripada meningkatkan pengecualian.

Pertanyaan saya adalah apakah boleh menyingkat nama menjadi "str-> int", atau apakah hal-hal seperti ini harus selalu ditentukan secara penuh.

Nick Vargish
sumber
3

Juga menggunakan (re-seq)fungsi dapat memperluas nilai kembali ke string yang berisi semua angka yang ada di string input agar:

(defn convert-to-int [s] (->> (re-seq #"\d" s) (apply str) (Integer.)))

(convert-to-int "10not123") => 10123

(type *1) => java.lang.Integer


sumber
3

Pertanyaannya adalah tentang mengurai string menjadi angka.

(number? 0.5)
;;=> true

Jadi dari desimal di atas harus diurai juga.

Mungkin tidak tepat menjawab pertanyaan sekarang, tetapi untuk penggunaan umum saya pikir Anda ingin menjadi ketat tentang apakah itu angka atau tidak (jadi "px" tidak diizinkan) dan biarkan penelepon menangani non-angka dengan mengembalikan nihil:

(defn str->number [x]
  (when-let [num (re-matches #"-?\d+\.?\d*" x)]
    (try
      (Float/parseFloat num)
      (catch Exception _
        nil))))

Dan jika Floats bermasalah untuk domain Anda, bukan Float/parseFloatput bigdecatau sesuatu yang lain.

Chris Murphy
sumber
3

Untuk orang lain yang ingin mengurai String literal yang lebih normal menjadi angka, yaitu string yang tidak memiliki karakter non numerik lainnya. Ini adalah dua pendekatan terbaik:

Menggunakan Java interop:

(Long/parseLong "333")
(Float/parseFloat "333.33")
(Double/parseDouble "333.3333333333332")
(Integer/parseInt "-333")
(Integer/parseUnsignedInt "333")
(BigInteger. "3333333333333333333333333332")
(BigDecimal. "3.3333333333333333333333333332")
(Short/parseShort "400")
(Byte/parseByte "120")

Ini memungkinkan Anda mengontrol dengan tepat jenis yang ingin Anda parsing nomornya, ketika itu penting untuk use case Anda.

Menggunakan pembaca Clojure EDN:

(require '[clojure.edn :as edn])
(edn/read-string "333")

Tidak seperti menggunakan read-stringdari clojure.coreyang tidak aman untuk digunakan pada input yang tidak dipercaya, edn/read-stringaman untuk dijalankan pada input yang tidak dipercaya seperti input pengguna.

Ini seringkali lebih nyaman daripada interop Java jika Anda tidak perlu memiliki kontrol khusus dari jenisnya. Itu dapat menguraikan sejumlah literal yang dapat diurai Clojure seperti:

;; Ratios
(edn/read-string "22/7")
;; Hexadecimal
(edn/read-string "0xff")

Daftar lengkap di sini: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers

Didier A.
sumber
2

Untuk kasus sederhana, Anda bisa menggunakan regex untuk menarik string digit pertama seperti yang disebutkan di atas.

Jika Anda memiliki situasi yang lebih rumit, Anda mungkin ingin menggunakan perpustakaan InstaParse:

(ns tst.parse.demo
  (:use tupelo.test)
  (:require
    [clojure.string :as str]
    [instaparse.core :as insta]
    [tupelo.core :as t] ))
(t/refer-tupelo)

(dotest
  (let [abnf-src            "
size-val      = int / int-px
int           = digits          ; ex '123'
int-px        = digits <'px'>   ; ex '123px'
<digits>      = 1*digit         ; 1 or more digits
<digit>       = %x30-39         ; 0-9
"
    tx-map        {:int      (fn fn-int [& args]
                               [:int (Integer/parseInt (str/join args))])
                   :int-px   (fn fn-int-px [& args]
                               [:int-px (Integer/parseInt (str/join args))])
                   :size-val identity
                  }

    parser              (insta/parser abnf-src :input-format :abnf)
    instaparse-failure? (fn [arg] (= (class arg) instaparse.gll.Failure))
    parse-and-transform (fn [text]
                          (let [result (insta/transform tx-map
                                         (parser text))]
                            (if (instaparse-failure? result)
                              (throw (IllegalArgumentException. (str result)))
                              result)))  ]
  (is= [:int 123]     (parse-and-transform "123"))
  (is= [:int-px 123]  (parse-and-transform "123px"))
  (throws?            (parse-and-transform "123xyz"))))
Alan Thompson
sumber
Juga, hanya pertanyaan yang aneh: mengapa Anda menggunakan (t/refer-tupelo)alih-alih membuat pengguna melakukannya (:require [tupelo.core :refer :all])?
Qwerp-Derp
refer-tupelodimodelkan setelah refer-clojure, dalam hal itu tidak termasuk segala cara (:require [tupelo.core :refer :all]).
Alan Thompson