ใน Clojure ฉันจะแปลงสตริงเป็นตัวเลขได้อย่างไร


130

ฉันมีสตริงหลายแบบเช่น "45" บางอันเช่น "45px" ฉันจะแปลงทั้งสองอย่างนี้เป็นหมายเลข 45 ได้อย่างไร


33
ฉันดีใจที่มีคนไม่กลัวที่จะถามคำถามพื้นฐาน
octopusgrabbus

4
+1 - ส่วนหนึ่งของความท้าทายคือบางครั้งเอกสาร Clojure ไม่ได้ตอบคำถาม "พื้นฐาน" เหล่านี้ที่เรายอมรับในภาษาอื่น (ฉันมีคำถามเดียวกันในอีก 3 ปีต่อมาและพบสิ่งนี้)
Glenn

3
@octopusgrabbus - ฉันสนใจที่จะรู้ว่า "ทำไม" คนถึงกลัวการถามคำถามพื้นฐาน?
appshare.co

1
@Zubair ควรมีการอธิบายสิ่งพื้นฐานบางอย่างอยู่แล้วดังนั้นคุณอาจมองข้ามบางสิ่งบางอย่างไปและคำถามของคุณจะถูกลงคะแนนว่า "ไม่มีความพยายามในการวิจัย"
อัล.

1
สำหรับผู้ที่มาที่นี่จาก Google มองหาที่จะแปลง"9"ลงในนี้เป็นสิ่งที่ดีที่สุดที่ทำงานสำหรับฉัน:9 (Integer. "9")
weltschmerz

คำตอบ:


79

สิ่งนี้จะใช้ได้กับ10pxหรือpx10

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

มันจะแยกวิเคราะห์หลักต่อเนื่องแรกเท่านั้นดังนั้น

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

คำตอบที่ดี! สิ่งนี้ดีกว่าการใช้ read-string ในความคิดของฉัน ฉันเปลี่ยนคำตอบให้ใช้เทคนิคของคุณ ฉันได้ทำการเปลี่ยนแปลงเล็กน้อยเช่นกัน
Benjamin Atkin

สิ่งนี้ให้ฉันException in thread "main" java.lang.ClassNotFoundException: Integer.,
maazza

83

คำตอบใหม่

ฉันชอบคำตอบของ snrobot ดีกว่า การใช้เมธอด Java นั้นง่ายกว่าและแข็งแกร่งกว่าการใช้ read-string สำหรับกรณีการใช้งานแบบธรรมดานี้ ฉันทำการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ สองสามอย่าง เนื่องจากผู้เขียนไม่ได้ตัดจำนวนลบออกฉันจึงปรับให้เป็นจำนวนลบ ฉันทำมันด้วยดังนั้นมันจึงต้องใช้ตัวเลขที่จะเริ่มต้นที่จุดเริ่มต้นของสตริง

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

นอกจากนี้ฉันพบว่าจำนวนเต็ม / parseInt แยกวิเคราะห์เป็นทศนิยมเมื่อไม่มีการกำหนดรัศมีแม้ว่าจะมีศูนย์นำหน้าก็ตาม

คำตอบเก่า

ขั้นแรกให้แยกวิเคราะห์เพียงจำนวนเต็ม (เนื่องจากเป็น Hit ใน Google และเป็นข้อมูลพื้นฐานที่ดี):

คุณสามารถใช้ผู้อ่าน :

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

คุณตรวจสอบได้ว่าเป็นตัวเลขหลังจากอ่านแล้ว:

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

ฉันไม่แน่ใจว่าการป้อนข้อมูลของผู้ใช้สามารถเชื่อถือได้โดยผู้อ่าน clojure คุณสามารถตรวจสอบก่อนที่จะอ่านได้เช่นกัน:

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

ฉันคิดว่าฉันชอบวิธีสุดท้ายมากกว่า

และตอนนี้สำหรับคำถามเฉพาะของคุณ ในการแยกวิเคราะห์สิ่งที่เริ่มต้นด้วยจำนวนเต็มเช่น29px:

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

ฉันชอบคำตอบของคุณที่สุด - แย่จังสิ่งนี้ไม่ได้มีให้ในไลบรารีหลักของ clojure คำติชมเล็กน้อย - ในทางเทคนิคแล้วคุณifควรเป็นwhenเพราะไม่มีอะไรปิดกั้นใน fns ของคุณ
quux00

1
ใช่โปรดอย่าหยุดอ่านหลังจากข้อมูลโค้ดตัวแรกหรือตัวที่สอง!
Benjamin Atkin

2
แจ้งตัวเลขที่มีเลขศูนย์นำหน้า read-stringแปลความหมายเป็นฐานแปด: (read-string "08")แสดงข้อยกเว้น Integer/valueOfถือว่าเป็นทศนิยม: (Integer/valueOf "08")ประเมินเป็น 8
rubasov

โปรดทราบว่าจะread-stringมีข้อยกเว้นหากคุณให้สตริงว่างหรือบางอย่างเช่น "29px"
Ilya Boyandin

ตามที่ควร. ฉันตอบคำถามในชื่อเรื่องและสิ่งที่ผู้คนคาดหวังเมื่อพวกเขาเห็นหน้านี้ก่อนที่ฉันจะตอบคำถามในเนื้อหาคำถาม เป็นข้อมูลโค้ดสุดท้ายในเนื้อหาคำตอบของฉัน
Benjamin Atkin

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

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

ขอบคุณ สิ่งนี้มีประโยชน์ในการแบ่งผลิตภัณฑ์ออกเป็นลำดับตัวเลข
octopusgrabbus

3
เนื่องจากเราอยู่ใน Java land สำหรับคำตอบนี้โดยทั่วไปจึงแนะนำให้ใช้Integer/valueOfแทนที่จะใช้ตัวสร้าง Integer คลาสจำนวนเต็มแคชค่าระหว่าง -128 ถึง 127 เพื่อลดการสร้างอ็อบเจ็กต์ Integer Javadoc อธิบายสิ่งนี้เช่นเดียวกับโพสต์นี้: stackoverflow.com/a/2974852/871012
quux00

15

สิ่งนี้ใช้ได้ผลกับฉันตรงไปตรงมามากขึ้น

(อ่านสตริง "123")

=> 123


1
โปรดใช้ความระมัดระวังในการป้อนข้อมูลของผู้ใช้ read-stringสามารถรันโค้ดต่อเอกสาร: clojuredocs.org/clojure.core/read-string
jerney

เหมาะสำหรับการป้อนข้อมูลที่เชื่อถือได้เช่นปริศนาการเขียนโปรแกรม @jerney ถูกต้อง: ระวังอย่าใช้ในรหัสจริง
hraban

10

AFAIK ไม่มีวิธีแก้ปัญหามาตรฐานสำหรับปัญหาของคุณ ฉันคิดว่าสิ่งต่อไปนี้ซึ่งใช้clojure.contrib.str-utils2/replaceควรช่วย:

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

ไม่แนะนำ. มันจะทำงานได้จนกว่าจะมีคนขว้าง1.5มัน ... และมันก็ไม่ได้ใช้ประโยชน์จากclojure.string/replaceฟังก์ชั่นในตัว
tar

8

นี้ไม่ได้สมบูรณ์แบบ แต่นี่เป็นสิ่งที่มีfilter, และCharacter/isDigit Integer/parseIntมันจะใช้ไม่ได้กับตัวเลขทศนิยมและจะล้มเหลวหากไม่มีตัวเลขในอินพุตดังนั้นคุณควรทำความสะอาด ฉันหวังว่าจะมีวิธีที่ดีกว่าในการทำสิ่งนี้ซึ่งไม่เกี่ยวข้องกับ 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

ฉันอาจจะเพิ่มบางสิ่งในข้อกำหนด:

  • ต้องขึ้นต้นด้วยตัวเลข
  • ต้องทนต่อปัจจัยการผลิตที่ว่างเปล่า
  • ทนต่อการส่งผ่านวัตถุใด ๆ (toString เป็นมาตรฐาน)

อาจจะชอบ:

(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

จากนั้นอาจได้คะแนนโบนัสสำหรับการทำให้เป็นหลายวิธีที่อนุญาตให้ผู้ใช้กำหนดค่าเริ่มต้นนอกเหนือจาก 0


4

ขยายคำตอบของ snrobot:

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

เวอร์ชันนี้จะคืนค่าศูนย์หากไม่มีตัวเลขในอินพุตแทนที่จะเพิ่มข้อยกเว้น

คำถามของฉันคือสามารถย่อชื่อเป็น "str-> int" ได้หรือไม่หรือควรระบุอย่างครบถ้วนเสมอ


4

สำหรับใครก็ตามที่ต้องการแยกวิเคราะห์สตริงลิเทอรัลที่ธรรมดากว่าให้เป็นตัวเลขนั่นคือสตริงที่ไม่มีอักขระอื่นที่ไม่ใช่ตัวเลข นี่คือสองแนวทางที่ดีที่สุด:

การใช้ 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")

สิ่งนี้ช่วยให้คุณควบคุมประเภทที่คุณต้องการแยกวิเคราะห์ตัวเลขได้อย่างแม่นยำเมื่อสิ่งนั้นสำคัญกับกรณีการใช้งานของคุณ

การใช้เครื่องอ่าน Clojure EDN:

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

ซึ่งแตกต่างจากการใช้read-stringจากclojure.coreที่ไม่ปลอดภัยที่จะใช้ในการป้อนข้อมูลที่ไม่น่าเชื่อถือedn/read-stringมีความปลอดภัยในการทำงานในการป้อนข้อมูลที่ไม่น่าเชื่อถือเช่นการป้อนข้อมูลของผู้ใช้

สิ่งนี้มักจะสะดวกกว่าการทำงานร่วมกันของ Java หากคุณไม่จำเป็นต้องมีการควบคุมเฉพาะประเภท สามารถแยกวิเคราะห์ตัวเลขตามตัวอักษรที่ Clojure สามารถแยกวิเคราะห์ได้เช่น:

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

รายการทั้งหมดที่นี่: https://www.rubberducking.com/2019/05/clojure-for-non-clojure-programmers.html#numbers


3

นอกจากนี้การใช้(re-seq)ฟังก์ชันยังสามารถขยายค่าส่งคืนไปยังสตริงที่มีตัวเลขทั้งหมดที่มีอยู่ในสตริงอินพุตตามลำดับ:

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

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

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


3

คำถามจะถามเกี่ยวกับการแยกสตริงเป็นตัวเลข

(number? 0.5)
;;=> true

ดังนั้นจากทศนิยมข้างบนก็ควรจะแยกวิเคราะห์เช่นกัน

ตอนนี้อาจจะยังไม่ตอบคำถามอย่างตรงไปตรงมา แต่สำหรับการใช้งานทั่วไปฉันคิดว่าคุณคงต้องเข้มงวดว่าเป็นตัวเลขหรือไม่ (ไม่อนุญาตให้ใช้ "px") และให้ผู้โทรจัดการที่ไม่ใช่ตัวเลขโดยการคืนค่า nil:

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

และถ้า Floats เป็นปัญหาสำหรับโดเมนของคุณแทนที่จะFloat/parseFloatใส่bigdecหรืออย่างอื่น


2

สำหรับกรณีง่ายๆคุณสามารถใช้ regex เพื่อดึงสตริงหลักแรกออกมาตามที่กล่าวไว้ข้างต้น

หากคุณมีสถานการณ์ที่ซับซ้อนมากขึ้นคุณอาจต้องการใช้ไลบรารี 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"))))

นอกจากนี้เพียงแค่คำถามที่อยากรู้อยากเห็น: ทำไมคุณถึงใช้(t/refer-tupelo)แทนการรับใช้ที่จะทำ(:require [tupelo.core :refer :all])?
Qwerp-Derp

refer-tupeloได้รับการจำลองแบบrefer-clojureโดยที่ไม่รวมทุกอย่างที่เป็น(:require [tupelo.core :refer :all])เช่นนั้น
Alan Thompson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.