今天的主題是 Data Types,雖然乍看之下可能會以為很無聊,但是內容還是很豐富唷!因為我還是會在過程之中介紹到該語言的一些特性。那就讓我們開始吧!
在每個語言定義三個不同 Type 的值,分別是 Integer、Decimal 還有 String,並且另外讓 User 各輸入一個相同 Type 的值,然後相加。這裡 String 的相加指的是把 String 串接起來。
object Solution {
def main(args: Array[String]) {
val i = 4
val d = 4.0
val s = "HackerRank "
println(i + scala.io.StdIn.readLine().toInt)
println(d + scala.io.StdIn.readLine().toDouble)
println(s + scala.io.StdIn.readLine())
}
}
val
,這在前一天有提過,如果是用 val
宣告便是 immutable,反之則用 var
,而這裡的 i
, d
, s
分別是 Int
, Double
以及 String
,沒有寫出來是因為 Scala 有 Type inference,可以自動從右邊推論出 Type,不然一般來說要寫 val i: Int = 4
。但既然是推論的,有時候難免會有非預期的結果,那麼就應該要自己把 Type 給寫出來囉!Any
是所有 Type 的 Supertype,分別被 AnyVal
和 AnyRef
給繼承, AnyVal
又分別有我們常見的 Value types 像是 Double
, Float
, Long
, Int
, Short
, Byte
, Char
, Unit
, and Boolean
,其中 Unit
只有一個唯一的值就是 ()
也就是不帶任何的內容,常用在當我們的 Function 只有 Side effect,但在 Scala 每個 Function 又必須要 Return 時,就會 Return Unit
。而 AnyRef
就是我們自己定義的 Class 或是一些 Scala 內建像是 List
, Option
等等。詳細一點的說明可以參考這裡 。scala.io.StdIn.readLine()
所得到的 User input 都是 String
,所以假使沒有加上 toInt
或是 toDouble
,會把輸入的內容直接當成字串直接印出來,例如整數的部分輸入 123
,那麼你會看到印出 4123
,而非相加的 127
。import scala.io.StdIn.readLine
,那麼我們就可以不用每次都寫 scala.io.StdIn.readLine()
,而只要寫 readLine()
了。Scala 的 Import 也沒有規定一定要在最外面,可以在 Object 甚至是 Function 裡面再 import 也行!Int
來說,其實 +
是 Int
這個 Class 的 Method,所以例如 a + b
其實是 a.+(b)
,然而可以寫 a + b
這是 Scala 提供的語法糖囉!i = 4
d = 4.0
s = 'HackerRank '
a = int(input())
b = float(input())
c = str(input())
print('{:d}\n{:.2f}\n{:s}'.format(a+i, b+d, s+c))
i
, d
, s
分別被賦予了 int
, float
, str
型態的值,這些是 Python 的內建型態 (除此之外還有 bool
)。因為 Python 比較麻煩的是,變數並沒有固定的 Type,是跟著被賦予值的型態做變換,但如果我們今天想知道這個變數目前是什麼 Type 或自定義的 Class 的話,可以利用 type
這個函式來幫助我們,例如 print(type(i))
就會得到 <class 'int'>
。a
, b
, c
則是分別透過 Invoke input()
來從 Standard input 讀取 User 所輸入的內容,並且做 Casting,也就是轉換型別。這裡假使不轉換型別的話,當例如字串和整數不小心相加的時候,就會產生 TypeError
。而且要注意的是,如果要轉換成例如 int
,但是卻輸入像是 abc
的字串,則也會因為無法轉換導致產生 Error 唷!如果要做 Error handling 可以用 try...except
來攔截 Exception 並且進行邏輯上的處理。format
。大括號的內容必須從 format
中所對應順序位置的結果而來,例如 {:d}
的值就是 a+i
(d
代表是 int
), {:.2f}
的值就是 b+d
,這裡的 .2
表示印出來的精準度是小數點後第二位 (f
是 float
), {:s}
的值是 s+c
。在 Python 的世界,把字串進行相加其實就是進行 concatenation,假設 c
是 is awesome
,那麼 s+c
就是 hackerRank is awesome
囉!package main
import (
"fmt"
"os"
"bufio"
"strconv"
)
func main() {
var i uint64 = 4
var d float64 = 4.0
var s string = "HackerRank "
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
a, _ := strconv.ParseUint(scanner.Text(),10,64)
scanner.Scan()
b, _ := strconv.ParseFloat(scanner.Text(), 64)
scanner.Scan()
c := scanner.Text()
fmt.Println(a+i)
fmt.Printf("%.1f\n",b+d)
fmt.Println(s+c)
}
strconv
這個 Package,他的主要功能是讓我們可以把一些基本的 Types 像是整數小數等等和 String 進行互轉。詳細用法與範例可參考這裡
bool
, string
, int
, int8
, int16
, int32
, int64
, float32
, float64
, uint8
, uint16
, uint32
, uint64
, float32
, float64
,complex64
, complex128
, byte
, rune
, uintptr
等等。我們可以看到像是 int
(整數), float
(浮點數), complex
(複數) 後面都跟著一些數字像是 32
, 64
,這些數字代表的是 bit 數,也就是這個值分別是用幾 bit 來儲存。當然 bit 數越大的,能存的數字也就越大囉!其中 byte
是 uint8
的 alias ( u
代表 unsigned) , rune
是 uint32
的 alias (代表一個 Unicode)bufio.NewScanner(os.Stdin)
,還記得之前我們用的是 NewReader
的 readLine
現在則是用 NewScanner
。首先我們看到官網在 readLine
裡頭提到 ReadLine is a low-level line-reading primitive. Most callers should use ReadBytes(‘\n’) or ReadString(‘\n’) instead or use a Scanner.
所以我們來試試看使用 Scanner 吧!bufio.NewScanner(os.Stdin)
得到一個 Scanner
,我們到目前為止可以看到不管是 NewReader
和 NewScanner
的參數都是 os.Stdin
,這到底是什麼呢?如果我們去看官方的文件 (這裡),可以發現 Stdin
是 os
Package 的一個 var
,而 Stdin
其實是透過 NewFile
得到一個 type 是 File
的 instance。咦?但是當我們看 NewScanner 的官方文件,需要的參數類型是 io.Reader 呀!於是我們再往下追 (參考這裡),可以看到 io.Reader
是一個 interface。在 Golang 的世界, interface 定義了一些方法,但並沒有實作。如果今天某個 Type 實作了某個 interface 定義的所有方法,例如這裡的 io.Reader
定義了 Read
這個方法,而我們也看到 File
確實實作了 Read
(參考這裡)( func (*File) Read
),所以我們就可以說 os.File
是 io.Reader
,就可以當作參數丟進去給 NewScanner
啦!如果讓我們再繼續深入一點點,可以發現到在 NewScanner
這個 function 裏頭 (定義在這裡),參數 r
會被賦予給 struct Scanner
內的 r
,所以我們可以預期之後應該會有 r.Read
被執行的情況,果不其然在這份 Source code 你可以搜尋到,在 Scan
這個方法裡頭,真的就可以看到 r.Read
囉!假使我們丟入的是 os.File
那麼真正被執行的就是其實作的 Read
也就是 func (*File) Read
。呼!scanner.Scan()
,每次執行會讀取一個 token,而 token 默認是讀取一整行,如同我們這邊想要得到的 User input,當然我們也可以自定義一次 token 要怎麼樣讀取 (可以參考這裡)。執行完 Scan()
之後,我們接下來可以看到 scanner.Text()
,這個就是把目前的 token 轉成 string 返回啦!因為每次 Scan()
默認是讀取一行,所以我們如果想要繼續讀下一行,就要再執行一次 Scan()
,這也是為什麼這邊會有三個 Scan()
了。var i
, var d
分別是 int
和 float
,所以我們要再經過一次轉換,這裡用到的是 strconv
這個 Package,而我們使用 ParseUint
可以我們把 scanner.Text()
回傳的 string,如果實際上是一個整數的樣子,就可以回傳給我們一個整數,在這裡我們指定 base 為 10
,且 bit 是 64
,所以我們得到的整數型態是 unit64
,就跟 i
一樣。 ParseFloat
也是相同的效果。得到所有我們想要的 User input 之後,就是透過 println
跟 printf
把他們相加的結果印出來啦! printf
是讓我們可以格式化輸出,例如這邊的 %.1f
就表示我們希望顯示的精準度是小數點後一位。而由於 printf
不會斷行,所以我們又給了一個 \n
。use std::io::stdin;
fn main() {
let i: i32 = 4;
let d: f32 = 4.0;
let s = "Hackerrack ";
let mut i_input = String::new();
let mut d_input = String::new();
let mut s_input = String::new();
stdin().read_line(&mut i_input)
.expect("Failed to read your input");
let i_input: i32 = match i_input.trim().parse(){
Ok(num) => num,
Err(_) => 0
};
stdin().read_line(&mut d_input)
.expect("Failed to read your input");
let d_input: f32 = match d_input.trim().parse(){
Ok(num) => num,
Err(_) => 0.0
};
stdin().read_line(&mut s_input)
.expect("Failed to read your input");
println!("{} + {}> {}", i, i_input, (i_input + i));
println!("{:.1} + {:.1}> {:.1}", d, d_input, (d_input + d));
println!("{} + {}> {} {}", s, s_input, s, s_input);
}
i8
, i16
, i32
, i64
, u8
(unsigned integer), u16
, u32
, u64
, f32
(浮點數), f64
,另外還有 bool
, char
, str
等等。i
, d
, s
也就是將來要被拿來相加的。接著我們分別定義了 i_input
, d_input
, s_input
要用來放待會要從 Standard input 得到的字串,記得這邊的 mut
表示我們明確指出這個變數是可以被修改的。而 String::new()
會幫我們產生一個新的空字串。stdin().read_line(&mut input)
,來從 Standard input 讀取字串並且放入 input
之中,為什麼這裡有 &mut
可以回去參考 day 0 的解釋,這是跟所有權以及可變性有關。然而這裡我們多了一個東西 expect("...")
,這是什麼呢?意義上是當 stdin().read_line(&mut input)
有 error 的時候,會印出 expect
中的字串。為什麼可以這樣?其實如果我們去看文件會發現, read_line
回傳的 Type 是 Result
,而 Result
其實是 Rust 裡頭的 Enum type
。Enum 就是列舉,但是在 Rust 的 Enum 特別的是,除了列舉不同的 variant 之外,每個 variant 又可以帶值,以 Result
為例 (參考這裡),Result
是一個泛型的 Enum,有兩個 variants, Ok
跟 Err
,而他們分別有 T
跟 E
Type 的值。更特別的是,在 Rust 的 Enum 還可以實作方法!而 Result
就實作了 expect
這個方法(這裡),當 Result
是 Err
的時候,就會印出 expect
中的字串並且結束程式。另外常用的 Enum 還有 Option
,我想我們以後就會遇到了。let i_input: i32 = match i_input.trim().parse()
這一段。這裡我們首先可以看到變數是可以重新綁定的,雖然我們上面說 i_input
是個字串,但在這邊我們將他重新綁定,且 type 是 i32
。注意,假使 i_input
還是個字串,但你卻想要賦予它一個整數,例如 i_input = 1
,這樣是不行的唷!再來我們看到出現了 match
這個字, match
可說是相當強大 (Scala 也有 match
),不只是可以發揮像是 switch 的功能,還是可以同時幫我們進行解構,這樣講有點抽象 (可參考這裡),我們直接來看這裡做了什麼事。首先 trim()
比較簡單,就是把字串前後的空白給去掉。接著這個字串又呼叫了 parse()
,其作用就是將字串 Parse 並轉換為變數所定義的型別,例如這裡 i_input
定義是 i32
,那麼 parse()
就會知道要 Parse 出整數且型別是i32
。而 parse()
所回傳的是 Result
,沒錯!就是上面所提過的。再來就換 match
登場啦!這裡 match
會幫我們判斷 Result
是 Ok
還是 Err
並且做相對應的事。假使 parse()
成功的話,那麼回傳的 Result
就是 Ok(parse出來的值)
,而在 match
中,我們透過給予Ok(num)
這個 Pattern 來把 Parse 出來的值用 num
來存取,你也可以寫 Ok(num) => num + 1
這表示我們把 Parse 出來的值加 1
後再賦予給 i_input
。而如果 Parse()
回傳 Err
,我們就給 i_input
一個 Default 值,在這裡是 0
。至於 Err(_)
的 _
表示我們並沒有要存取 Err
的值,所以就給一個 _
表示忽略。而 float 的部分也是相同的概念囉!println
來把結果印出來。其中 {:.1}
表示我們要印的精準度是小數點後第一位囉!今天雖然本來只是想要介紹資料型態,但是在過程之中,我們又認識了一些語言的特性,所以有時候事情可以看起來很簡單,但是其實很多有趣的東西在裡頭,就看我們願不願意花心思去挖掘囉!明天見!
(今日內容: 8729 個字)
其他文章:
[Day 0]經典的起手式!
您好,
Golang 部份似乎誤植了 day0 的 code 了....
謝謝您的好文章
已修正,感謝提醒!