Trong Clojure, làm cách nào tôi có thể chuyển đổi Chuỗi thành số?


128

Tôi có nhiều chuỗi khác nhau, một số như "45", một số như "45px". Làm thế nào để tôi chuyển đổi cả hai thứ này sang số 45?


32
Tôi vui vì ai đó không ngại hỏi một số câu hỏi cơ bản.
bạch tuộc

4
+1 - một phần của thách thức là các tài liệu Clojure đôi khi không giải quyết được những câu hỏi "cơ bản" này mà chúng ta coi là ngôn ngữ khác. (Tôi đã có cùng một câu hỏi 3 năm sau và tìm thấy điều này).
Glenn

3
@octopusgrabbus - Tôi rất muốn biết "tại sao" mọi người sợ đặt câu hỏi cơ bản?
appshare.co

1
@Zubair người ta cho rằng những điều cơ bản đã được giải thích ở đâu đó nên bạn có thể bỏ qua điều gì đó và câu hỏi của bạn sẽ bị bỏ qua vì "không có nỗ lực nghiên cứu".
Al.G.

1
Đối với những người đến đây từ Google đang tìm cách chuyển đổi "9"thành 9, đây là điều tốt nhất phù hợp với tôi : (Integer. "9").
Weltschmerz

Câu trả lời:


79

Điều này sẽ làm việc trên 10pxhoặcpx10

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

nó sẽ phân tích các chữ số liên tục đầu tiên như vậy

user=> (parse-int "10not123")
10
user=> (parse-int "abc10def11")
10

Câu trả lời tốt đẹp! Điều này tốt hơn so với việc sử dụng chuỗi đọc theo ý kiến ​​của tôi. Tôi đã thay đổi câu trả lời của tôi để sử dụng kỹ thuật của bạn. Tôi đã thực hiện một vài thay đổi nhỏ là tốt.
Benjamin Atkin

điều này mang lại cho tôiException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza

83

Câu trả lời mới

Tôi thích câu trả lời của snrobot hơn. Sử dụng phương thức Java đơn giản và mạnh mẽ hơn so với sử dụng chuỗi đọc cho trường hợp sử dụng đơn giản này. Tôi đã thực hiện một vài thay đổi nhỏ. Vì tác giả không loại trừ số âm, tôi đã điều chỉnh nó để cho phép số âm. Tôi cũng đã làm cho nó để nó yêu cầu số bắt đầu ở đầu chuỗi.

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

Ngoài ra, tôi thấy rằng Integer / parseInt phân tích dưới dạng thập phân khi không có cơ số nào được đưa ra, ngay cả khi có các số 0 đứng đầu.

Câu trả lời cũ

Đầu tiên, để phân tích chỉ một số nguyên (vì đây là một điểm nhấn trên google và đó là thông tin cơ bản tốt):

Bạn có thể sử dụng trình đọc :

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

Bạn có thể kiểm tra xem đó có phải là số sau khi đọc không:

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

Tôi không chắc liệu đầu vào của người dùng có thể được người đọc clojure tin cậy hay không để bạn có thể kiểm tra trước khi đọc.

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

Tôi nghĩ rằng tôi thích giải pháp cuối cùng.

Và bây giờ, câu hỏi cụ thể của bạn. Để phân tích một cái gì đó bắt đầu bằng một số nguyên, như 29px:

(read-string (second (re-matches (re-pattern "(\\d+).*") "29px"))) ; => 29

Tôi thích câu trả lời của bạn nhất - quá tệ, điều này không được cung cấp bởi thư viện lõi clojure. Một phê bình nhỏ - về mặt kỹ thuật của bạn ifnên là một whenvì không có khối nào khác trong fns của bạn.
quux00

1
Vâng, xin đừng ngừng đọc sau đoạn mã thứ nhất hoặc thứ hai!
Benjamin Atkin

2
Một đầu lên trên các số với số không hàng đầu. read-stringgiải thích chúng là bát phân: (read-string "08")ném một ngoại lệ. Integer/valueOfcoi chúng là số thập phân: (Integer/valueOf "08")ước tính đến 8.
rubasov

Cũng lưu ý rằng read-stringsẽ ném một ngoại lệ nếu bạn cung cấp cho nó một chuỗi rỗng hoặc đại loại như "29px"
Ilya Boyandin

Như là nó phải như thế. Tôi đã trả lời câu hỏi trong tiêu đề và những gì mọi người mong đợi khi họ xem trang này, trước khi tôi trả lời câu hỏi trong phần thân câu hỏi. Đó là đoạn mã cuối cùng trong phần câu trả lời của tôi.
Benjamin Atkin

30
(defn parse-int [s]
  (Integer. (re-find #"[0-9]*" s)))

user> (parse-int "10px")
10
user> (parse-int "10")
10

Cảm ơn. Điều này rất hữu ích trong việc tôi chia một sản phẩm thành một chuỗi các chữ số.
bạch tuộc

3
Vì chúng tôi đang ở trong vùng đất Java cho câu trả lời này, nên thường được sử dụng Integer/valueOf, thay vì hàm tạo Integer. Lớp Integer lưu trữ các giá trị trong khoảng từ -128 đến 127 để giảm thiểu việc tạo đối tượng. Integer Javadoc mô tả điều này giống như bài đăng này: stackoverflow.com/a/2974852/871012
quux00

15

Điều này làm việc thay thế cho tôi, thẳng hơn nhiều.

(chuỗi đọc "123")

=> 123


1
Hãy cẩn thận khi sử dụng với đầu vào của người dùng. read-stringcó thể thực thi mã theo các tài liệu: clojuredocs.org/clojure.core/read-opes
jerney

điều này rất tốt cho đầu vào đáng tin cậy, ví dụ như câu đố lập trình. @jerney đúng: cẩn thận không sử dụng nó trong mã thực tế.
hraban 17/03/19

10

AFAIK không có giải pháp chuẩn cho vấn đề của bạn. Tôi nghĩ một cái gì đó như sau, sử dụng clojure.contrib.str-utils2/replace, sẽ giúp:

(defn str2int [txt]
  (Integer/parseInt (replace txt #"[a-zA-Z]" "")))

Không được khuyến khích. Nó sẽ hoạt động cho đến khi ai đó ném 1.5vào nó ... và nó cũng không sử dụng clojure.string/replacechức năng tích hợp.
tar

8

Điều này không hoàn hảo, nhưng đây là một cái gì đó với filter, Character/isDigitInteger/parseInt. Nó sẽ không hoạt động đối với các số dấu phẩy động và nó sẽ thất bại nếu không có chữ số trong đầu vào, vì vậy bạn có thể nên xóa nó. Tôi hy vọng có một cách tốt hơn để làm điều này mà không liên quan đến quá nhiều Java.

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)

4

Tôi có thể sẽ thêm một vài điều vào các yêu cầu:

  • Phải bắt đầu bằng một chữ số
  • Phải chịu đựng đầu vào trống rỗng
  • Dung sai được thông qua bất kỳ đối tượng nào (toString là tiêu chuẩn)

Có lẽ một cái gì đó như:

(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

và sau đó có lẽ là điểm thưởng cho việc biến điều này thành một phương thức đa cho phép mặc định do người dùng cung cấp khác 0.


4

Mở rộng câu trả lời của snrobot:

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

Phiên bản này trả về nil nếu không có chữ số trong đầu vào, thay vì đưa ra một ngoại lệ.

Câu hỏi của tôi là liệu có thể chấp nhận viết tắt tên thành "str-> int" hay không, nếu những thứ như thế này phải luôn được chỉ định đầy đủ.


3

Ngoài ra, sử dụng (re-seq)hàm có thể mở rộng giá trị trả về thành một chuỗi chứa tất cả các số hiện có trong chuỗi đầu vào theo thứ tự:

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

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

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


3

Câu hỏi hỏi về phân tích một chuỗi thành một số.

(number? 0.5)
;;=> true

Vì vậy, từ các số thập phân ở trên nên được phân tích cú pháp là tốt.

Có lẽ không trả lời chính xác câu hỏi bây giờ, nhưng để sử dụng chung, tôi nghĩ rằng bạn sẽ muốn nghiêm ngặt về việc đó có phải là số hay không (vì vậy "px" không được phép) và để người gọi xử lý các số không bằng cách trả về nil:

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

Và nếu Floats có vấn đề cho tên miền của bạn thay vì Float/parseFloatđặt bigdechoặc một cái gì đó khác.


3

Đối với bất kỳ ai khác muốn phân tích một chuỗi ký tự bình thường hơn thành một số, nghĩa là một chuỗi không có các ký tự không phải là số khác. Đây là hai cách tiếp cận tốt nhất:

Sử dụng 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")

Điều này cho phép bạn kiểm soát chính xác loại bạn muốn phân tích số, khi đó là vấn đề quan trọng đối với trường hợp sử dụng của bạn.

Sử dụng trình đọc Clojure EDN:

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

Không giống như sử dụng read-stringtừ clojure.coređó không an toàn để sử dụng cho đầu vào không tin cậy, edn/read-stringan toàn để chạy trên đầu vào không tin cậy như đầu vào của người dùng.

Điều này thường thuận tiện hơn khi sử dụng Java nếu bạn không cần phải kiểm soát cụ thể các loại. Nó có thể phân tích bất kỳ số nào theo nghĩa đen mà Clojure có thể phân tích cú pháp như:

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

Danh sách đầy đủ tại đây: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers


2

Đối với các trường hợp đơn giản, bạn chỉ cần sử dụng biểu thức chính quy để lấy chuỗi chữ số đầu tiên như đã đề cập ở trên.

Nếu bạn có một tình huống phức tạp hơn, bạn có thể muốn sử dụng thư viện InstaPude:

(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"))))

Ngoài ra, chỉ là một câu hỏi tò mò: tại sao bạn sử dụng (t/refer-tupelo) thay vì bắt người dùng phải làm gì (:require [tupelo.core :refer :all])?
Qwerp-Derp

refer-tupelođược mô phỏng theo refer-clojure, trong đó nó không bao gồm mọi thứ theo cách (:require [tupelo.core :refer :all])đó.
Alan Thompson
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.