Author Archives: Phở Code

Ruby – Nhập xuất

Trong phần này chúng ta sẽ tìm hiểu về hệ thống nhập xuất trong Ruby. Khi chạy một chương trình thì chương trình có thể nhận các dòng dữ liệu đi vào, có thể là từ bàn phím, file hoặc từ chính một chương trình khác. Tương tự, chương trình cũng có thể xuất các dòng dữ liệu đi ra ngoài, thường là sẽ đi đến màn hình, đi vào một file hoặc đi vào một chương trình khác.

Xuất dữ liệu ra

Xuyên suốt series này chúng ta đã thực hiện việc xuất dữ liệu rất nhiều lần bằng các phương thức có trong Ruby. Các phương thức này tồn tại trong module Kernel.

Ví dụ 1:

print "Apple "
print "Apple\n"

puts "Orange"
puts "Orange"

Hai phương thức printputs là 2 phương thức xuất dữ liệu là các chuỗi text ra màn hình, trong đó phương thức puts sẽ in chuỗi rồi xuống dòng còn phương thức print thì không.

print "Apple\n"

Nếu muốn phương thức print có xuống dòng thì chúng ta thêm kí tự \n vào.

Apple Apple
Orange
Orange

Ví dụ 2:

p "Lemon"
p "Lemon"

printf "There are %d apples\n", 3

putc 'K'
putc 0xA

Chúng ta tiếp tục tìm hiểu một số phương thức in dữ liệu khác.

p "Lemon"

Phương thức p sẽ gọi phương thức inspect của đối tượng. Phương thức inspect là một phương thức của lớp Object, thường thì phương thức này sẽ in ra tên lớp cùng các thông tin liên quan.

printf "There are %d apples\n", 3

Phương thức printf in chuỗi và cho phép truyền tham số vào trong chuỗi.

putc 'K'
putc 0xA

Phương thức putc chỉ đơn giản là in một kí tự ra màn hình. Giá trị 0xA là giá trị của kí tự xuống dòng trong bảng mã ASCII. Tức thay vì ở đây chúng ta dùng putc "\n" thì chúng ta có thể in 0xA thay thế.

"Lemon"
"Lemon"
There are 3 apples
K

Đọc dữ liệu vào

Ví dụ 1:

print "Enter your name: "
name = gets
puts "Hello #{name}"

Để đọc dữ liệu vào thì chúng ta dùng phương thức gets.

name = gets

Phương thức gets sẽ nhận một chuỗi nhập vào từ bàn phím. Khi chạy chương trình, đến đoạn gọi phương thức gets thì chương trình sẽ dừng lại chờ chúng ta gõ một chuỗi nào đó rồi bấm ENTER thì chuỗi đó sẽ được truyền vào biến name.

Enter your name: Pho Code
Hello Pho Code

Ví dụ 2:

print "Enter a string: "
str = gets

puts "String size: #{str.size}"

Phương thức size sẽ trả về số lượng kí tự có trong chuỗi

Enter a string: abc
String size: 4

Kết quả in ra là 4 trong khi chuỗi của chúng ta nhập vào chỉ là "abc" tức là chỉ có 3 chữ cái thôi, lý do là vì phương thức gets tính luôn cả kí tự ENTER mà chúng ta gõ phía sau nữa nên thành 4.

Để loại bỏ kí tự ENTER này ra khỏi chuỗithì chúng ta dùng phương thức chomp.

print "Enter a string: "
str = gets.chomp

puts "String size: #{str.size}"

Như thế phương thức size sẽ cho ra đúng số lượng kí tự đã được nhập vào.

Enter a string: abc
String size: 3

File

Tất cả các công việc nhập dữ liệu vào và xuất dữ liệu ra đều được quản lý bởi một lớp có tên là IO, ngoài ra Ruby còn có các lớp con khác kế thừa từ lớp IO nhằm mục đích thực hiện việc nhập xuất trên các dòng dữ liệu khác nhau, trong đó có lớp File dùng để nhập xuất file.

Ví dụ 1:

f = File.open('C:\\output.txt', 'w')
f.puts "The Ruby tutorial"
f.close

Trong ví dụ này chúng ta thực hiện ghi dữ liệu lên file.

f = File.open('C:\\output.txt', 'w')

Để ghi dữ liệu vào file thì trước tiên chúng ta phải mở file đó đã bằng phương thức File.open(), phương thức này nhận vào tham số là đường dẫn đến file và chế độ mở, ở đây tên file là output.txt và chế độ mở là (write) tức là mở file để ghi.

f.puts "The Ruby tutorial"

Phương thức open sẽ trả về một đối tượng stream, nói cho đơn giản thì bạn cứ hình dung stream chính là những thứ như Kernel vậy, nếu bạn đã từng làm việc với C++ thì stream ở đây giống như đối tượng std::cout ấy. Và ở đây chúng ta có thể dùng đối tượng stream đó để ghi dữ liệu lên file bằng phương thức puts. 

f.close

Sau khi đã làm việc với file xong thì chúng ta đóng file lại bằng phương thức close.

Ví dụ 2:

File.open('C:\\output.txt', 'w') do |f|
   
    f.puts "Ruby"
    f.write "Java\n"
    f << "Python\n"
    
end

Nếu chúng ta mở file trong khối lệnh File.open()...end thì sau khi kết thúc chúng ta không cần phải đóng file nữa mà Ruby sẽ tự đóng cho chúng ta.

f.puts "Ruby"
f.write "Java\n"
f << "Python\n"

Ngoài ra ở đây chúng ta dùng thêm phương thức write và toán tử << để in chuỗi ra màn hình.

Ví dụ 3:

puts File.exists? 'C:\\tempfile'

f = File.new 'C:\\tempfile', 'w'
puts File.mtime 'C:\\tempfile'
puts f.size

File.rename 'C:\\tempfile', 'C:\\tempfile2'

f.close

Lớp File ngoài việc hỗ trợ nhập xuất còn có một số phương thức khác.

puts File.exists? 'C:\\tempfile'

Phương thức exists? kiểm tra xem một file có tồn tại hay không.

f = File.new 'C:\\tempfile', 'w'

Phương thức new sẽ tạo một file mới trên đĩa cứng.

puts File.mtime 'C:\\tempfile'

Phương thức mtime lấy thời gian chỉnh sửa file lần cuối cùng.

puts f.size

Phương thức size trả về kích thước của file.

File.rename 'C:\\tempfile', 'C:\\tempfile2'

Phương thức rename sẽ đổi tên file.

false
2011-11-05 16:19:36 +0100
0

Ví dụ 4:

f = File.open("C:\\input.txt")

while line = f.gets do
    puts line
end

f.close

Trong ví dụ này chúng ta sẽ đọc một file.

f = File.open("C:\\input")

Để đọc một file thì trước tiên chúng ta cũng phải mở file đó đã, ở đây chúng ta không đưa vào tham số chế độ mở là vì mặc định Ruby sẽ mở file theo chế độ đọc.

while line = f.gets do
    puts line
end

Phương thức gets sẽ đọc từng dòng dữ liệu trong file, chúng ta lưu dữ liệu đó trong biến line, vòng lặp while có tác dụng kiểm tra xem biến line có rỗng hay không, tức là trong khi file vẫn còn dữ liệu để đọc thì chúng ta thực hiện đoạn code bên trong vòng lặp.

Ruby
Java
Python

Ngoài phương thức gets có chức năng đọc từng dòng thì còn có phương thức readlines sẽ đọc tất cả các dòng trong file và lưu vào một mảng. Cũng vì thế nên bạn lưu ý đối với các file có kích thước lớn.

file = 'C:\\input'

File.readlines(file).each do |line|
    puts line
end
Ruby
Java
Python

Thư mục

Ruby có lớp Dir hỗ trợ làm việc với thư mục.

Dir.mkdir "tmp"
puts Dir.exists? "tmp"

puts Dir.pwd
Dir.chdir "tmp"
puts Dir.pwd

Dir.chdir '..'
puts Dir.pwd
Dir.rmdir "tmp"
puts Dir.exists? "tmp"

Trong ví dụ trên chúng ta làm việc với 4 phương thức của lớp Dir.

Dir.mkdir "tmp"

Phương thức mkdir sẽ tạo một thư mục mới.

puts Dir.exists? "tmp"

Phương thức exists? kiểm tra xem một thư mục có tồn tại hay không.

puts Dir.pwd

Phương thức pwd in ra đường dẫn đến thư mục hiện tại. Đây là thư mục chứa file code Ruby mà chúng ta đang viết.

Dir.chdir '..'

Phương thức chdir thay đổi thư mục hiện tại thành thư mục khác. Ở đây dấu ".." tức là lùi về thư mục cha.

Dir.rmdir "tmp"
puts Dir.exists? "tmp"

Phương thức rmdir sẽ xóa một thư mục.

true
C:/Program Files (x86)/Notepad++
C:/Program Files (x86)/Notepad++/tmp
C:/Program Files (x86)/Notepad++
false

Chuyển hướng dòng nhập xuất

Như bình thường chúng ta dùng phương thức puts, print... để in dữ liệu lên màn hình. Trong các bài trước chúng ta đã biết phương thức puts là phương thức của module Kernel, tức là gọi Kernel.puts "Ruby" sẽ tương đương với puts "Ruby". Ngoài ra Ruby còn có một số biến toàn cục tham chiếu tới các module đó nữa, biến $stdout là một ví dụ, đây là biến tham chiếu tới module Kernel, tức là bây giờ chúng ta có 3 cách dùng phương thức puts khác nhau là Kernel.puts “”, $stdout.puts "" hoặc puts "".

Chính vì biến $stdout tham chiếu tới module Kernel nên phương thức puts gọi từ biến này sẽ xuất dữ liệu lên màn hình nhưng chúng ta cũng có thể chuyển hướng để biến này xuất dữ liệu ra nơi khác.

$stdout = File.open "C:\\output.log", "a"

puts "Ruby"
puts "Java"

$stdout.close
$stdout = STDOUT

puts "Python"

Trong ví dụ trên chúng ta chuyển hướng dòng dữ liệu xuất ra từ màn hình sang file output.log.

$stdout = File.open "C:\\output.log", "a"

Như chúng ta đã biết, phương thức File.open sẽ trả về một đối tượng stream, chúng ta gán stream đó vào biến $stdout.

puts "Ruby"
puts "Java"

Khi chúng ta gọi phương thức puts, dữ liệu xuất ra sẽ được tự động ghi vào file thay vì ghi lên màn hình như trước.

$stdout = STDOUT

puts "Python"

Nếu chúng ta chuyển hướng lại vào hằng số STDOUT thì dòng dữ liệu xuất ra từ các phương thức puts, print... sẽ lại đi vào màn hình chứ không đi vào file nữa.

 

Ruby – Biểu thức chính quy Regex

Biểu thức chính quy là một công cụ hỗ trợ thực hiện tìm kiếm chuỗi hoặc các thao tác phức tạp với chuỗi, thường được tích hợp trong các công cụ soạn thảo văn bản, ngôn ngữ lập trình… và tất nhiên là Ruby cũng không ngoại lệ.

Thành phần chủ chốt của biểu thức chính quy là các chuỗi tìm kiếm (tiếng Anh là search pattern hoặc pattern không cũng được) dùng để thực hiện so sánh trên các chuỗi thật. Các chuỗi tìm kiếm này được xây dựng dựa trên các kí tự bình thường và các kí tự đặc biệt.

Đây là danh sách các kí tự đặc biệt:

. Tìm bất kì ký tự nào
* Tìm kí tự đứng trước đó 0 hoặc nhiều lần
[ ] Tìm bất kì kí tự đứng trong cặp dấu []
[^ ] Tìm bất kì kí tự nào không nằm trong cặp dấu []
^ Tìm tại điểm bắt đầu của chuỗi
$ Tìm tại điểm kết thúc của chuỗi
| Toán tử OR

Chúng ta sẽ lần lượt đi vào tìm hiểu các kí tự trên.

Ví dụ:

re = Regexp.new 'Jane'
p "Jane is a girl".match re
p "Jane is a girl" =~ /Jane/
p "Jane is a girl".match %r{Jane}

Để tìm xem chuỗi tìm kiếm có khớp với một chuỗi nào đó không thì chúng ta có 3 cách.

re = Regexp.new 'Jane'

Để tạo các chuỗi tìm kiếm thì chúng ta dùng lớp Regexp, ở dòng trên chúng ta tạo một đối tượng Regexp với chuỗi tìm kiếm là “Jane”.

p "Jane is hot".match re
p "Jane is hot" =~ /Jane/
p "Jane is hot".match %r{Jane}

Để tìm xem chuỗi tìm kiếm có khớp với một chuỗi nào đó hay không thì chúng ta có thể dùng phương thức match của lớp String hoặc toán tử =~. Tham số của phương thức match và toán tử =~ là một đối tượng Regexp hoặc một chuỗi tìm kiếm nằm trong bộ kí tự %r{}, hoặc một chuỗi tìm kiếm nằm trong cặp dấu //. Trong các ví dụ bên dưới chúng ta sẽ làm việc chủ yếu với cặp dấu //.

#<MatchData "Jane">
0
#<MatchData "Jane">

Phương thức match sẽ trả về một đối tượng MatchData nếu tìm thấy hoặc nil nếu không tìm thấy, còn toán tử =~ sẽ trả về chỉ số của chuỗi con được tìm thấy đầu tiên hoặc nil nếu không tìm thấy, ở ví dụ trên toán tử =~ trả về 0 vì chuỗi “Jane” được tìm thấy nằm ở đầu chuỗi gốc.

Tìm bất kì ký tự nào

Như đã mô tả trong bảng các kí tự đặc biệt ở trên, kí tự dấu chấm “.” sẽ tìm bất kì một kí tự nào. Ví dụ:

p "PhoCode".match /.Code/
p "Code".match /.Code/
p "MiCode".match /.Code/
p "Phode".match /.Code/

Trong đoạn code trên, chuỗi tìm kiếm là .Code, tức là khi tìm thì Ruby sẽ tìm bất kì kí tự nào theo sau bởi chuỗi “Code”. Nếu tìm thấy thì in ra chuỗi đó, không thì trả về đối tượng nil.

#<MatchData "oCode">
nil
#<MatchData "iCode">
nil

Ví dụ 2:

p "PhoCode".match /.Code/
p "Code".match /.?Code/
p "MiCode".match /.Code/
p "Phode".match /.Code/

Chúng ta có thể thêm dấu chấm hỏi “?” sau kí tự chấm “.” để báo cho Ruby biết rằng kí tự đó có thể có hoặc không có cũng được.

p "Code".match /.?Code/

Trong ví dụ trước, đoạn code trên không có dấu chấm hỏi, tức là Ruby sẽ hiểu là phải tìm xem có chuỗi “Code” nào có 1 kí tự bất kì ở phía trước không, còn trong ví dụ này có dấu chấm hỏi tức là tìm xem có chuỗi “Code” nào không và có thể có hoặc không có 1 kí tự đứng trước nó.

#<MatchData "oCode">
#<MatchData "Code">
#<MatchData "iCode">
nil

Một số biến đặc biệt

puts "Her name is Jane" =~ /name/

p $`
p $&
p $'

Khi chúng ta tìm kiếm chuỗi thì các chuỗi có liên quan đến quá trình tìm kiếm sẽ được lưu trong một số biến đặc biệt có sẵn.

puts "Her name is Jane" =~ /name/

Ở ví dụ này chúng ta tìm kiếm chuỗi “name” trong chuỗi gốc “Her name is Jane”. Như đã nói ở trên, toán tử =~ sẽ trả về vị trí đầu tiên của chuỗi được tìm thấy, ở đây là vị trí số 4.

p $`

Ngoài ra Ruby còn có biến $`, biến The $` lưu chuỗi nằm phía trước chuỗi được tìm thấy. Tức là trong chuỗi “Her name is Jane” thì chuỗi “Her “ đứng trước chuỗi “name” nên sẽ được lưu trong biến $`.

p $&

Biến $& lưu chính chuỗi được tìm thấy, ở đây là “name”.

p $'

Biến $’ ngược lại với $` là lưu chuỗi nằm phía sau chuỗi được tìm thấy. Ở đây là ” is Jane”.

4
"Her "
"name"
" is Jane"

Anchor

Anchor là các kí tự tìm kiếm tại các vị trí đặc biệt.

Ví dụ 1:

sen1 = "Programming Ruby"
sen2 = "Ruby programming language"

p sen1.match /^Ruby/ 
p sen2.match /^Ruby/ 

p sen1.match /Ruby$/ 
p sen2.match /Ruby$/ 

Kí tự ^ sẽ tìm chuỗi con tại vị trí đầu trong chuỗi gốc, trong khi kí tự $ sẽ tìm chuỗi con bắt đầu từ cuối chuỗi.

sen1 = "Programming Ruby"
sen2 = "Ruby programming language"

Trong ví dụ này chúng ta có 2 chuỗi với chuỗi con “Ruby” nằm ở cuối chuỗi sen1 và đầu chuỗi sen2.

p sen1.match /^Ruby/ 
p sen2.match /^Ruby/

^Ruby tức là tìm xem có chuỗi “Ruby” nào nằm ở đầu chuỗi gốc hay không.

p sen1.match /Ruby$/ 
p sen2.match /Ruby$/  

Ngược lại Ruby$ tức là tìm chuỗi “Ruby” ở cuối chuỗi.

nil
#<MatchData "Ruby">
#<MatchData "Ruby">
nil

Ví dụ 2:

text = "The cat also known as the domestic cat is a small, 
usually furry, domesticated, carnivorous mammal."

p text.scan /cat/

Chúng ta có một chuỗi text và chúng ta tìm chuỗi con “cat” bằng phương thức scan.

p text.scan /cat/

Phương thức scan sẽ tìm tất cả những chuỗi con có nội dung là “cat” trong chuỗi gốc, ở đây phương thức này tìm thấy 3 chuỗi “cat”, chuỗi con “cat” thứ 3 nằm trong từ “domesticated”.

["cat", "cat", "cat"]

Nhưng đôi khi chúng ta lại không muốn tìm những chuỗi con nằm lẫn trong một từ khác như “domesticated” như trên mà chúng ta chỉ muốn tìm những chuỗi con đứng một mình như 2 chuỗi “cat” đầu tiên tìm được. Lúc đó chúng ta phải dùng đến kí tự \b.

Ví dụ 3:

text = "The cat also known as the domestic cat is a small, 
usually furry, domesticated, carnivorous mammal."

p text.scan /\bcat\b/

Bằng cách thêm kí tự \b vào trước và sau chuỗi tìm kiếm cần tìm, Ruby sẽ tìm chuỗi con đứng một mình chứ không tìm chuỗi con lẫn trong các chuỗi khác lớn hơn.

["cat", "cat"]

Gom nhóm các kí tự

Chúng ta có thể gộp các kí tự cần kiểm tra lại với nhau vào bên trong cặp dấu ngoặc vuông []. Ví dụ /[ab]/ sẽ tìm bất kì kí tự a hoặc b nào, còn /ab/ sẽ tìm bất kì kí tự ab nào, tức là /ab/ phải có cả kí tự a lẫn kí tự b, còn /[ab]/ chỉ là tìm xem có kí tự a hoặc b hay không thôi.

Ví dụ 1:

words = %w/ sit MIT fit fat lot pad /

pattern = /[fs]it/

words.each do |word|
   if word.match pattern
       puts "#{word} matches" 
   else
       puts "#{word} does not match" 
   end
end

Chúng ta có mảng words chứa các chuỗi. Chúng ta sẽ duyệt qua từng chuỗi và xem có chuỗi nào khớp với chuỗi tìm kiếm hay không.

pattern = /[fs]it/

chuỗi tìm kiếm có dạng /[fs]it/ tức là khớp với chuỗi fit hoặc sit.

sit matches
MIT does not match
fit matches
fat does not match
lot does not match
pad does not match

Kết quả chúng ta có 2 chuỗi khớp.

Ví dụ 2:

p "car".match %r{[abc][a][rs]}
p "car".match /[a-r]+/
p "23af 433a 4ga".scan /\b[a-f0-9]+\b/

Chúng ta kiểm tra 3 chuỗi tìm kiếm.

p "car".match %r{[abc][a][rs]}

Đoạn chuỗi tìm kiếm ở trên khá dễ hiểu, tìm một chuỗi có 3 kí tự, kí tự đầu tiên là a hoặc b hoặc c, kí tự thứ 2 là a, kí tự thứ 3 là r hoặc s.

p "car".match /[a-r]+/

Chúng ta có thể dùng dấu gạch nối “-“ để biểu diễn một khoảng giá trị. thay vì viết [abcdefghijklmnopqrstuvwxyz] để tìm một kí tự từ a đến z, thì ở đây chúng ta chỉ cần ghi là [a-z] là Ruby sẽ hiểu. Sau đó chúng ta có thể dùng dấu + để báo rằng kí tự đứng trước dấu cộng có thể lặp lại 1 hoặc nhiều lần.

p "23af 433a 4ga".scan /\b[a-f0-9]+\b/

Nếu muốn tìm một kí tự có nhiều khoảng giá trị khác nhau thì chúng ta cứ ghi chúng ra trong cặp dấu ngoặc vuông []. Ở dòng code trên [a-f0-9]+ nghĩa là tìm một kí tự có giá trị trong khoảng a-z hoặc từ 0-9 và kí tự này có thể lặp lại nhiều lần. Ngoài ra ở đây chúng ta còn dùng thêm kí tự \b để báo cho Ruby biết rằng chúng ta không tìm chuỗi con trong chuỗi khác mà chỉ tìm các chuỗi đứng một mình.

#<MatchData "car">
#<MatchData "car">
["23af", "433a"]

Ví dụ 3:

p "ABC".match /[^a-z]{3}/
p "abc".match /[^a-z]{3}/

Chúng ta có thể thêm dấu ^ để chỉ định cho Ruby tìm những kí tự không thuộc khoảng giá trị đó. Tức là ngược với ví dụ 2.

p "ABC".match /[^a-z]{3}/

Trong đoạn code trên [^a-z] tức là tìm một kí tự mà không thuộc khoảng giá trị từ a đến z. Ngoài ra [^a-z]{3} sẽ tìm một chuỗi có đúng 3 kí tự, thay vì dùng dấu + như trước là lặp lại vô số lần.

p "abc".match /[^a-z]{3}/

Chuỗi “ABC” ở trên khớp với mẫu vì ABC là các kí tự in hoa, còn chuỗi “abc” là các kí tự thường nên bị loại bỏ.

#<MatchData "ABC">
nil

Chỉ định số lượng kí tự cần tìm

Trong các ví dụ trên chúng ta đã biết là dấu + sẽ lặp lại 1 hoặc nhiều lần, hay {3} là tìm 3 kí tự. Chúng ta sẽ tìm hiểu thêm các cách chỉ định số lượng kí tự dưới đây.

 ?     - có hoặc không có
 *     - lặp lại 0 hoặc nhiều lần
 +     - lặp lại 1 hoặc nhiều lần
 {n}   - Xuất hiện chính xác n lần
 {n,}  - Xuất hiện n lần hoặc nhiều hơn
 {,n}  - Xuất hiện ít hơn hoặc bằng n lần
 {n,m} - Xuất hiện từ n đến m lần

Chúng ta sẽ tìm hiểu thêm qua các ví dụ ở dưới.

Ví dụ 1:

p "PhoCode open source is the future".scan /\w{3}/
p "PhoCode open source is the future".scan /\b\w{3}\b/

Trong ví dụ này, \w là tìm một kí tự chữ cái, tức là tương đương với [a-zA-Z], thêm {3} vô nghĩa là tìm chuỗi có 3 kí tự chữ cái. Dòng tiếp theo chúng ta có thêm \b tức là chỉ tìm những chuỗi con có đúng 3 chữ cái.

["Pho", "Cod", "ope", "sou", "rce", "the", "fut", "ure"]
["the"]

Ví dụ 2:

p "PhoCode open source is the future".scan /\b\w{2,4}\b/

Ví dụ này cũng tương đương ví dụ trên, ở đây chúng ta dùng {2, 4} tức là tìm các chuỗi chỉ chứa các kí tự chữ cái có từ 2 đến 4 kí tự.

["open", "is", "the"]

Ví dụ 3:


p "color colour colors colours".scan /colou?rs/
p "color colour colors colours".scan /colou?rs?/
p "color colour colors colours".scan /\bcolor\b|\bcolors\b|\bcolour\b|\bcolours\b/

Kí tự dấu chấm hỏi "?" cho biết kí tự đứng trước nó có thể có hoặc không có cũng được.

p "color colour colors colours".scan /\bcolor\b|\bcolors\b|\bcolour\b|\bcolours\b/

Hoặc nếu muốn dễ nhìn hơn chúng ta có thể dùng kí hiệu “|”, kí hiệu này có chức năng giống như toán tử OR vậy, tức là chuỗi tìm kiếm ở trên sẽ tìm những chuỗi con là color, colors, colour, hoặc colours.

["colors", "colours"]
["color", "colour", "colors", "colours"]
["color", "colour", "colors", "colours"]

Phân biệt chữ HOA-thường

p "Jane".match /Jane/
p "Jane".match /jane/
p "Jane".match /JANE/

p "Jane".match /jane/i
p "Jane".match /Jane/i
p "Jane".match /JANE/i

Trong các ví dụ từ đầu bài đến giờ chúng ta tìm kiếm kí tự chữ cái có phân biệt chữ hoa và chữ thường, nếu muốn Ruby không phân biệt chữ hoa và chữ thường thì chúng ta thêm tùy chọn i vào sau chuỗi tìm kiếm.

#<MatchData "Jane">
nil
nil
#<MatchData "Jane">
#<MatchData "Jane">
#<MatchData "Jane">

Email

Trong ví dụ này chúng ta sẽ thực hiện tạo chuỗi kiểm tra email. Đây là một trong những bài toán điển hình của biểu thức chính quy.

emails = %w/ admin@example.com jane@gmail.com ioah2423^as f3444@gmail.com /
    
pattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/

emails.each do |email| 

    if email.match pattern
        puts "#{email} matches"
    else
        puts "#{email} does not match"
    end
    
end

Chúng ta có một mảng emails lưu các chuỗi email mẫu để kiểm tra.

pattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9]+\.[a-zA-Z.]{2,5}$/

Trên đây là chuỗi Regex mà chúng ta dùng để kiểm tra. Chúng ta sẽ lần lượt tìm hiểu từng phần của chuỗi này.

[a-zA-Z0-9._-]+@

Đoạn code trên có nghĩa là tìm một chuỗi có nhiều kí tự có giá trị từ A đến Z, hoặc từ a đến z, hoặc từ 0 đến 9, hoặc đó là kí tự dấu chấm “.”, dấu gạch ngang “_” hoặc dấu gạch nối “-“. Tiếp theo sau đó là một kí tự @. Phần này khớp với phần đầu email, ví dụ admin@…

[a-zA-Z0-9]+\.

Sau kí tự @ chúng ta lại tìm một chuỗi con có giá trị từ A đến Z, hoặc từ a đến z hoặc từ 0 đến 9. Đoạn này khớp với phần tên nhà cung cấp email như gmail, yahoo…

Sau đó là kí tự \., theo nghĩa bình thường thì kí tự chấm có nghĩa là ở đó tồn tại bất kì kí tự gì như chúng ta đã nói ở gần đầu bài, nhưng ở đây có dấu “\” phía trước, tức là ở đây chúng ta cần tìm một kí tự dấu chấm “.” thật sự chứ không phải một kí tự nào khác.

[a-zA-Z.]{2, 5}

Cuối cùng là tìm một chuỗi con có giá trị từ a đến z hoặc từ A đến Z hoặc một dấu chấm “.”, và chuỗi này có từ 2 đến 5 kí tự, phần này tương ứng với com, info, net… lý do tại sao lại có dấu chấm sau cùng là vì có một số tên miền có 2 phần như com.vn, co.uk…

admin@example.com matches
jane@gmail.com matches
ioah2423^as does not match
f3444@gmail.com matches

Ruby – Module và Exception

Trong phần này chúng ta sẽ tìm hiểu về module và exception trong Ruby.

Module

Một module là một tập các phương thức, lớp, hằng số, do đó module cũng gần giống như lớp vậy, chỉ khác là module không thể tạo các đối tượng và không thể thừa kế.

Thường thì chúng ta sẽ gộp các lớp, phương thức và hằng số có liên quan với nhau vào một module để tránh xung đột tên. Nếu bạn đã từng làm việc với C# và Java thì có thể nói module trong Ruby tương đương với namespace trong C# và package trong Java vậy.

Ngoài ra chúng ta còn có thể sử dụng module trong Ruby để thực hiện đa thừa kế nữa.

Ví dụ 1:

puts Math::PI
puts Math.sin 2

Math là một module có sẵn trong Ruby, module này chứa các hằng số và phương thức hỗ trợ thực hiện các phép tính toán học. Ở đoạn code trên chúng ta in ra hằng số PI và dùng phương thức sin.

include Math

puts PI
puts sin 2

Nếu không muốn ghi tên module ra trực tiếp thì chúng ta có thể sử dụng từ khóa include Math để báo cho Ruby biết là chúng ta sẽ dùng module Math.

3.141592653589793
0.9092974268256817

Ví dụ 2:

module Forest
 
    class Rock ; end
    class Tree ; end
    class Animal ; end    
    
end

module Town
    
   class Pool ; end
   class Cinema ; end
   class Square ; end
   class Animal ; end
        
end


p Forest::Tree.new
p Forest::Rock.new
p Town::Cinema.new

p Forest::Animal.new
p Town::Animal.new

Để định nghĩa một module thì chúng ta dùng cặp từ khóa module…end, tên module được đặt sau từ khóa module.

module Forest
 
    class Rock ; end
    class Tree ; end
    class Animal ; end    
    
end

Thường thì chúng ta sẽ nhóm những thứ có liên quan với nhau lại vào trong một module. Như trong đoạn code trên chúng ta định nghĩa các lớp Rock (đá), Tree (cây) và Animal (động vật) vào trong module Forest (rừng).

p Forest::Tree.new
p Forest::Rock.new
p Town::Cinema.new

Để truy xuất một đối tượng trong một module thì chúng ta dùng toán tử ::

p Forest::Animal.new
p Town::Animal.new

Như bình thường thì chúng ta không thể định nghĩa 2 lớp có tên giống nhau trong một file được. Nhưng ở đây chúng ta sử dụng thêm cả module nữa nên có thể định nghĩa 2 lớp có tên giống nhau trong một file.

#<Forest::Tree:0x97f35ec>
#<Forest::Rock:0x97f35b0>
#<Town::Cinema:0x97f3588>
#<Forest::Animal:0x97f3560>
#<Town::Animal:0x97f3538>

Ví dụ 3:

Hướng đối tượng trong C++ có hỗ trợ đa thừa kế, trong Java cũng hỗ trợ đa thừa kế thông qua Interface, còn Ruby hỗ trợ đa thừa kế thông qua module.

module Device
    def switch_on ; puts "on" end    
    def switch_off ; puts "off" end
end

module Volume
    def volume_up ; puts "volume up" end    
    def vodule_down ; puts "volume down" end
end

module Pluggable
    def plug_in ; puts "plug in" end    
    def plug_out ; puts "plug out" end
end

class CellPhone
    include Device, Volume, Pluggable
   
    def ring
        puts "ringing"
    end    
end

cph = CellPhone.new
cph.switch_on
cph.volume_up
cph.ring

Đa thừa kế tức là một lớp có thể thừa kế từ nhiều lớp khác, chúng ta có thể sử dụng module của Ruby để thực hiện đa thừa kế.

Ở đây chúng ta định nghĩa một lớp, sau đó include các module bên trong lớp này và lớp đó sẽ có thể sử dụng những phương thức, hằng số… của các module đã được include.

class CellPhone
    include Device, Volume, Pluggable
   
    def ring
        puts "ringing"
    end    
end

Trong ví dụ này chúng ta có 3 module Device, VolumePluggable, mỗi module có một số phương thức in chuỗi. Chúng ta cũng định nghĩa lớp CellPhone và include cả 3 module trên vào.

cph = CellPhone.new
cph.switch_on
cph.volume_up
cph.ring

Như thế khi tạo đối tượng lớp CellPhone, chúng ta có thể gọi luôn cả các phương thức của những module mà lớp đó đã include chứ không cần gọi đến tên module nữa.

on
volume up
ringing

Exception

Trong lập trình có 3 loại lỗi là lỗi biên dịch, lỗi ngữ nghĩa và lỗi exception – lỗi ngoại lệ. Lỗi exception là lỗi xảy ra trong quá trình chương trình chạy (tiếng anh là run time error). Ví dụ như bạn viết một chương trình máy tính bỏ túi gồm các chức năng cộng trừ nhân chia. Thoạt nhìn có vẻ như rất đơn giản, chỉ cần yêu cầu người dùng nhập vào 2 số và phép tính, thực hiện phép tính rồi trả lại kết quả cho người dùng. Nhưng chương trình này sẽ báo lỗi nếu người dùng thực hiện một phép tính chia với số chia là 0. Đó là loại lỗi chỉ xảy ra trong khi chương trình chạy, để phòng ngừa loại lỗi này thì chỉ có một cách là các lập trình viên phải đoán trước các trường hợp lỗi ngoại lệ có thể xảy ra và xử lý chúng trước trong code của mình. Nhưng vì bạn chẳng phải nhà tiên tri, bạn không thể nào đoán trước tất cả mọi thứ được ? nên trên thực tế thì các phần mềm lớn vẫn thường có lỗi chứ không có phần mềm nào không có lỗi cả, công việc của chúng ta là cố gắng phát hiện lỗi và sửa lỗi thôi.

Trong Ruby các lỗi exception thường gặp được định nghĩa thành một lớp riêng, và tất cả chúng đều được kế thừa từ lớp Exception. Ngoài ra chúng ta cũng có thể định nghĩa lớp exception của riêng chúng ta.

Ví dụ 1:

x = 35
y = 0

begin
    z = x / y
    puts z
rescue => e
    puts "Error: #{e}"
end

Trong ví dụ này chúng ta sẽ thử thực hiện một phép chia với mẫu số là 0.

begin
    z = x / y
    puts z

Những câu lệnh mà có khả năng giải phóng exception sẽ được đặt trong cặp từ khóa begin...end.

rescue => e
    puts "Error: #{e}"
end

Để có thể bắt lỗi thì chúng ta dùng từ khóa rescue theo sau là dấu => rồi đến tên tham số đối tượng exception. Các đối tượng exception sẽ chứa một chuỗi mô tả về tên lỗi exception đã xảy ra. Chúng ta có thể dùng phương thức puts để in đoạn chuỗi đó ra.

Error: divided by 0

Ví dụ 2:

age = 17

begin
    if age < 18 
        raise "Under 18 is not allowed" 
    end 

    puts "Allowed" 
rescue => e
    puts e
    p e    
end

Chúng ta có thể tự giải phóng một lỗi exception bằng cách dùng từ khóa raise.

begin
    if age < 18
        raise "Under 18 is not allowed"
    end
 
    puts "Allowed"

Theo sau từ khóa raise chúng ta đưa vào tham số là một chuỗi, Ruby sẽ tạo một đối tượng RuntimeException và truyền vào đó chuỗi này. Khi một exception được giải phóng, chương trình sẽ bị ngắt ngay tại đó.

rescue => e
    puts e
    p e    
end

Nếu không có câu lệnh rescue để bắt lỗi thì chương trình sẽ thoát luôn, còn ở đây chúng ta có câu lệnh bắt lỗi nên chương trình sẽ tiếp tục thực thi những gì có trong câu lệnh này.

Nếu như phương thức puts chỉ in đoạn chuỗi mô tả lỗi thì phương thức p sẽ in cả tên lớp exception và đoạn chuỗi mô tả.

Under 18 is not allowed
#<RuntimeError: Under 18 is not allowed>

Ví dụ 3:

age = 17

begin
    if age < 18 
        raise "Under 18 is not allowed" 
    end 
    puts "Entry allowed" 
rescue => e
    puts e
    p e
ensure
    exit 0
end

Ruby còn có từ khóa ensure, những câu lệnh nằm sau từ khóa ensure sẽ được thực thi cho dù có xảy ra lỗi hay không. Từ khóa ensure có tác dụng giống như từ khóa finally trong các ngôn ngữ như C#, Java…

ensure
    exit 0
end

Chúng ta thực thi câu lệnh exit để thoát chương trình trong từ khóa ensure, khi chương trình chạy, cho dù có lỗi xảy ra hay không thì câu lệnh exit cũng sẽ được thực thi, mặc dù ở đây câu lệnh này cũng không có ý nghĩa mấy vì dù gì chương trình cũng tự thoát.

Ví dụ 4:

age = 17

class NotOver18Exception < StandardError
end

begin
    if age < 18 
        raise NotOver18Exception, "Under 18 is not allowed" 
    end 
    puts "Entry allowed" 
rescue => e
    puts e
    p e
ensure
    exit 0
end

Chúng ta có thể định nghĩa lớp exception của riêng chúng ta và giải phóng đối tượng thuộc lớp đó.

class NotOver18Exception < StandardError
end

Một lớp exception phải được kế thừa từ lớp StandardError, trong ví dụ này chúng ta định nghĩa lớp NotOver18Exception kế thừa từ lớp StandardError.

if age < 18
    raise NotOver18Exception, "Under 18 is not allowed"
end

Để giải phóng lỗi thì chúng ta đưa thêm tên lớp vào sau từ khóa raise, rồi đến đoạn chuỗi mô tả lỗi.

Under 18 is not allowed
#<NotOver18Exception: Under 18 is not allowed>

Ruby – Hướng đối tượng – Phần 2

Trong phần này chúng ta tiếp tục tìm hiểu về hướng đối tượng trong Ruby.

Truy xuất thuộc tính

Như đã nói ở bài trước, mặc định tất cả các thuộc tính trong Ruby đều là private, tức là chúng ta chỉ có thể truy xuất được thông qua phương thức của đối tượng. Trong thực tế thì khi thiết kế lớp, với mỗi thuộc tính chúng ta sẽ định nghĩa 2 phương thức là getter và setter, mục đích của 2 phương thức này là để truy xuất dữ liệu và chỉnh sửa chúng, việc định nghĩa 2 phương thức trên hầu như là công việc tuy không bắt buộc nhưng nhất định phải làm 🙂 Do đó trong một số IDE có sẵn chức năng tự tạo các phương thức getter và setter luôn. Đối với Ruby thì có sẵn 3 phương thức tương tự là attr_reader, attr_writerattr_accessor.

Phương thức attr_reader sẽ tạo các phương thức getter trong khi phương thức attr_writer sẽ tạo các phương thức setter. Phương thức attr_accessor sẽ tạo cả getter, setter và các biến instance.

Ví dụ 1:

class Car
 
    attr_reader :name, :price
    attr_writer :name, :price 
 
    def to_s
        "#{@name}: #{@price}"
    end

end


c1 = Car.new
c2 = Car.new

c1.name = "Porsche"
c1.price = 23500

c2.name = "Volkswagen"
c2.price = 9500

puts "#{c1.name}: #{c1.price}"

p c1
p c2

Trong ví dụ này chúng ta định nghĩa lớp Car. Trong lớp này chúng ta định nghĩa các phương thức getter và setter bằng cách sử dụng phương thức attr_readerattr_writer.

attr_reader :name, :price

Để tạo các phương thức getter thì chúng ta ghi tên phương thức đó dưới dạng Symbol phía sau phương thức attr_reader.

attr_writer :name, :price  

Tương tự với các phương thức setter, chúng ta cũng ghi sau phương thức attr_writer.

c1.name = "Porsche"
c1.price = 23500

Chúng ta gọi các phương thức setter như gọi các phương thức bình thường, rồi dùng toán tử gán "=" để thiết lập dữ liệu.

puts "#{c1.name}: #{c1.price}"

Tương tự, các phương thức getter cũng vậy.

Porsche: 23500
#<Car:0x2517d98 @name="Porsche", @price=23500>
#<Car:0x2517d80 @name="Volkswagen", @price=9500>

Ví dụ 2:

class Book
   attr_accessor :title, :pages    
end

b1 = Book.new
b1.title = "Hidden motives"
b1.pages = 255

p "The book #{b1.title} has #{b1.pages} pages"

Như đã nói ở trên, phương thức attr_accessor sẽ tạo luôn cả phương thức getter và setter.

"The book Hidden motives has 255 pages"

Hằng số lớp

class MMath

    PI = 3.141592
end


puts MMath::PI

Các hằng số được định nghĩa bên trong một lớp có tác dụng giống như biến class vậy, tức là chúng được dùng chung bởi tất cả các đối tượng thuộc lớp đó, chứ không có lớp nào có biến riêng cả. Trong ví dụ trên chúng ta định nghĩa lớp NMath có hằng PI.

PI = 3.141592

Hằng số được đặt tên bắt đầu bằng chữ cái in hoa.

puts MMath::PI

Để truy xuất giá trị của hằng số thì chúng ta dùng toán tử :: theo sau tên lớp.

3.141592

Phương thức to_s

Tất cả các đối tượng trong Ruby đều có phương thức to_s, phương thức này được kế thừa từ lớp Object gốc trong Ruby. Phương thức trả về một chuỗi string mô tả về đối tượng đó, khi chúng ta dùng phương thức puts để in ra một đối tượng thì phương thức puts sẽ gọi đến phương thức to_s của đối tượng đó.

class Being

    def to_s
        "This is Being class"
    end
end

b = Being.new
puts b.to_s
puts b

Trong ví dụ này chúng ta định nghĩa lớp Being có phương thức to_s.

def to_s
    "This is Being class"
end

Nếu chúng ta không định nghĩa lại phương thức to_s thì phương thức puts sẽ gọi phương thức to_s của lớp Object gốc, mà phương thức to_s gốc sẽ in ra địa chỉ bộ nhớ của đối tượng.

b = Being.new
puts b.to_s
puts b

Khi dùng chúng ta có thể gọi ra một cách rõ ràng hoặc không gọi cũng được.

This is Being class
This is Being class

Quá tải toán tử

Quá tải toán tử tức là một toán tử có thể dùng cho nhiều kiểu dữ liệu khác nhau, giống như chúng ta có nhiều phương thức và mỗi phương thức nhận nhiều tham số khác nhau vậy.

class Circle
   
    attr_accessor :radius
    
    def initialize r
        @radius = r
    end

    def +(other)
        Circle.new @radius + other.radius
    end
    
    def to_s
        "Circle with radius: #{@radius}"
    end
end


c1 = Circle.new 5
c2 = Circle.new 6
c3 = c1 + c2

p c3

Trong ví dụ này chúng ta định nghĩa lớp Circle có toán tử +.

def +(other)
    Circle.new @radius + other.radius
end

Để định nghĩa một toán tử thì chúng ta cũng dùng cặp từ khóa def...end giống như định nghĩa một phương thức với tên toán tử phía sau từ khóa def, sau đó đặt tham số trong cặp dấu ().

Ở đây lớp Circle có nghĩa là hình tròn, thuộc tính radius là bán kính. Toán tử + có chức năng cộng 2 bán kính của 2 hình tròn.

c1 = Circle.new 5
c2 = Circle.new 6
c3 = c1 + c2

Toán tử được dùng như cách dùng với các kiểu dữ liệu bình thường

Circle with radius: 11

Phương thức class

Các phương thức trong Ruby có 2 loại là phương thức class và phương thức instance, giống như biến cũng có biến class và biến instance vậy. Và chức năng của 2 loại phương thức này cũng giống hệt như đối với biến. Tức là phương thức instance là phương thức của riêng từng đối tượng, trong khi phương thức class là phương thức dùng chung, tất cả các đối tượng được tạo từ cùng một lớp sẽ dùng chung một phương thức class.

Phương thức class không thể truy xuất các biến instance mà chỉ có thể truy xuất các biến class.

Ví dụ:

class Circle
    
    def initialize x
        @r = x
    end
   
    def self.info
       "This is a Circle class" 
    end
    
    def area
        @r * @r * 3.141592
    end

end


p Circle.info
c = Circle.new 3
p c.area

Trong ví dụ này chúng ta định nghĩa lớp Circle có một phương thức class.

def self.info
    "This is a Circle class" 
end

Phương thức class được định nghĩa bằng cách thêm từ khóa self vào trước tên phương thức.

def area
    "Circle, radius: #{@r}"
end

Phương thức area là phương thức instance vì không chứa từ khóa self, vậy tức là các phương thức mà chúng ta đã định nghĩa từ trước tới giờ đều là phương thức instance.

p Circle.info

Để gọi phương thức class thì chúng ta gọi từ tên lớp chứ không gọi từ tên đối tượng.

c = Circle.new 3
p c.area

Để gọi phương thức instance thì chúng ta phải tạo một đối tượng rồi gọi từ tên đối tượng đó như trước giờ chúng ta vẫn làm. Ở đoạn code trên chúng ta tạo một đối tượng Circle có tên là và gọi phương thức instance area của đối tượng đó.

"This is a Circle class"
28.274328

Ví dụ 2:

Ngoài cách định nghĩa như trên chúng ta còn có 2 cách định nghĩa phương thức class khác.

class Wood
     
    def self.info
       "This is a Wood class" 
    end
end

class Brick
     
    class << self
        def info
           "This is a Brick class" 
        end
    end
end

class Rock
     
end

def Rock.info
   "This is a Rock class" 
end

p Wood.info
p Brick.info
p Rock.info

Trong ví dụ này chúng ta dùng 3 cách định nghĩa phương thức class khác nhau.

def self.info
    "This is a Wood class" 
end

Cách đầu tiên đã giới thiệu ở trên là dùng từ khóa self.

class << self
    def info
        "This is a Brick class" 
    end
end

Cách thứ 2 là khai báo phần định nghĩa trong khối class << self...end.

def Rock.info
   "This is a Rock class" 
end

Cách thứ 3 là hay vì dùng từ khóa self thì chúng ta dùng luôn tên lớp.

"This is a Wood class"
"This is a Brick class"
"This is a Rock class"

Đa hình

Tính đa hình là tính năng cho phép chúng ta thực thi toán tử hay phương thức với nhiều kiểu dữ liệu khác nhau. Khi một lớp thừa kế từ một lớp khác thì lớp con ngoài việc kế thừa lại những gì có ở lớp cha thì có thể định nghĩa lại hoặc mở rộng thêm nữa. Nói một cách tổng quát thì đa hình là tính năng cho phép định nghĩa lại các phương thức ở lớp con.

Ví dụ:

class Animal
    
    def make_noise 
        "Some noise"
    end

    def sleep 
        puts "#{self.class.name} is sleeping." 
    end
  
end

class Dog < Animal
    
    def make_noise 
        'Woof!'         
    end 
    
end

class Cat < Animal 
    
    def make_noise 
        'Meow!' 
    end 
end

[Animal.new, Dog.new, Cat.new].each do |animal|
  puts animal.make_noise
  animal.sleep
end

Trong ví dụ trên chúng ta có lớp cơ sở Animal, lớp dẫn xuất DogCat kế thừa từ lớp Animal. Cả 3 lớp này đều có phương thức make_noise nhưng kết quả của phương thức này ở 3 lớp là khác nhau.

[Animal.new, Dog.new, Cat.new].each do |animal|
  puts animal.make_noise
  animal.sleep
end

Việc định nghĩa lại phương thức được kế thừa ở lớp cha còn được gọi là override. Ở đây phương thức make_noise được override lại ở 2 lớp DogCat, trong khi ở lớp cơ sở có phương thức sleep nữa nhưng không được override lại.

Khi chúng ta gọi phương thức make_noise ở lớp con thì Ruby sẽ tìm trong lớp con đó có phương thức đó hay không, nếu có thì dùng phương thức ở lớp con, nếu không thì gọi lên phương thức đó ở lớp cha.

Some noise
Animal is sleeping.
Woof!
Dog is sleeping.
Meow!
Cat is sleeping.

Ruby – Hướng đối tượng – Phần 1

Chúng ta đã tìm hiểu sơ qua về đối tượng trong các bài trước, trong bài này chúng ta sẽ tìm hiểu sâu hơn.

Ngôn ngữ lập trình được phân ra làm nhiều loại mô hình như mô hình lập trình hướng thủ tục, lập trình hướng hàm, lập trình hướng đối tượng… Ruby là ngôn ngữ lập trình hướng đối tượng.

Lập trình hướng đối tượng (Object-oriented programming – OOP) là mô hình lập trình sử dụng các đối tượng để thiết kế nên kiến trúc của chương trình ứng dụng.

OOP có các khái niệm cơ bản sau đây:

  • Trừu tượng (Abstraction)
  • Đa hình (Polymorphism)
  • Đóng gói (Encapsulation)
  • Thừa kế (Inheritance)

Khái niệm đối tượng

Đây là các thành phần cấu tạo nên một chương trình hướng đối tượng. Một đối tượng trong OOP chứa 2 thành phần là thuộc tính và phương thức, trong đó thuộc tính đơn giản chỉ là các biến chứa dữ liệu, phương thức chỉ là các hàm/thủ tục. Các đối tượng sẽ giao tiếp với nhau thông qua phương thức của chúng. Mỗi đối tượng có thể nhận/gửi thông điệp cho nhau và xử lý dữ liệu của chúng.

Để tạo một đối tượng thì chúng ta phải có lớp, lớp chính là một cái khuôn/bản vẽ/bản thiết kế… để tạo nên các đối tượng.

Ví dụ khi nói lớp Người thì chúng ta biết rằng một người bao gồm tên, tuổi, màu da, giới tính… có thể ăn, nói, nhảy múa… Còn khi nói đối tượng người thì chúng ta có đối tượng Jack, 23 tuổi, quốc tịch Mỹ, biết hát, đối tượng Jennifer 19 tuổi, quốc tịch Nauy, biết nấu ăn…

Ví dụ:

class Being
  
end

b = Being.new
puts b

Trong đoạn code trên chúng ta định nghĩa lớp và tạo một đối tượng từ lớp đó.

class Being
  
end

Để định nghĩa lớp thì chúng ta dùng cặp từ khóa class...end với tên lớp mà chúng ta muốn đặt sau từ khóa class. Hiện tại lớp này là lớp rỗng, không có gì cả.

b = Being.new

Để tạo một đối tượng thuộc lớp Being thì chúng ta ghi tên lớp rồi dùng phương thức new. Phương thức này sẽ trả về địa chỉ tham chiếu đến đối tượng vừa tạo, ở trên chúng ta lưu lại đối tượng này vào biến b.

puts b

Khi chúng ta gọi phương thức puts lên một đối tượng, phương thức này sẽ gọi phương thức to_s có trong mỗi đối tượng. Trong trường hợp của chúng ta thì do chưa định nghĩa phương thức to_s nên phương thức puts sẽ trả về địa chỉ tham chiếu đến đối tượng.

#<Being:0x9f3c290>

Phương thức khởi tạo

Phương thức khởi tạo là một phương thức đặc biệt, phương thức này từ động được gọi khi chúng ta tạo một đối tượng. Phương thức khởi tạo không trả về một giá trị nào cả. Mục đích chính của phương thức khởi tạo chỉ là thiết lập trạng thái cho đối tượng. Tất cả các phương thức khởi tạo trong Ruby đều có tên là initialize.

Ví dụ 1:

class Being

    def initialize
        puts "Being is created"
    end

end

Being.new

Trong ví dụ trên chúng ta định nghĩa phương thức initialize. Trong phương thức này chúng ta in một chuỗi string ra màn hình. Khi chúng ta gọi phương thức new, phương thức này sẽ tự động gọi phương thức initialize.

Để định nghĩa một phương thức thì chúng ta dùng cặp từ khóa def...end với tên phương thức nằm phía sau từ khóa def.

Being is created

Ví dụ 2:

class Person

    def initialize name
        @name = name
    end

    def get_name
        @name
    end

end

p1 = Person.new "Jane"
p2 = Person.new "Beky"

puts p1.get_name
puts p2.get_name

Thuộc tính của một đối tượng là các biến lưu trữ giá trị của đối tượng đó. Các biến này còn được gọi là biến instance. Mỗi đối tượng đều có thuộc tính của riêng nó, tức là các đối tượng thuộc cùng một lớp thì có các biến instance khác nhau.

class Person

    def initialize name
        @name = name
    end

Trong đoạn code trên, hàm khởi tạo initialize nhận vào một biến tham số có tên là name, chúng ta gán giá trị của tham số đó vào biến instance @name.

def get_name
    @name
end

Chúng ta định nghĩa phương thức get_name, phương thức này trả về giá trị của biến @name. Trong Ruby các biến instance chỉ có thể truy xuất trong các phương thức.

p1 = Person.new "Jane"
p2 = Person.new "Beky"

Để truyền tham số thì chúng ta ghi phía sau tên phương thức khi gọi. Trong đoạn code trên các chuỗi “Jane”, “Beky” sẽ được truyền vào hàm initialize.

Jane
Beky

Phương thức thành phần

Phương thức thành phần (hay gọi ngắn gọn là phương thức) là các hàm được định nghĩa bên trong một lớp, mục đích của phương thức là thực hiện một công việc nào đó. Thường thì phương thức sẽ làm việc với các thuộc tính của đối tượng. Phương thức rất quan trọng với tính năng đóng gói trong lập trình hướng đối tượng, đóng gói tức là chúng ta không quan tâm phương thức làm những gì mà chỉ quan tâm đến kết quả cuối cùng của phương thức mà thôi.

Ví dụ 1:

class Person

    def initialize name
        @name = name
    end

    def get_name
        @name
    end

end

per = Person.new "Jane"

puts per.get_name
puts per.send :get_name

Để gọi một phương thức thì chúng ta có 2 cách.

puts per.get_name

Cách đầu tiên và cũng là phổ biến nhất là ghi tên đối tượng, dấu chấm rồi đến tên phương thức.

puts per.send :get_name

Cách thứ hai là gọi phương thức send, theo sau là tên phương thức nhưng viết dưới dạng một Symbol, tức là thêm dấu 2 chấm ":" vào trước tên phương thức.

Ví dụ 2:

class Circle
   
    @@PI = 3.141592

    def initialize
        @radius = 0
    end

    def set_radius radius
        @radius = radius
    end

    def area
        @radius * @radius * @@PI
    end

end


c = Circle.new
c.set_radius 5
puts c.area

Trong đoạn code trên chúng ta định nghĩa lớp Circle có 2 phương thức.

@@PI = 3.141592

Trong lớp Circle chúng ta định nghĩa một biến class là @@PI, biến class là biến dùng chung cho tất cả các đối tượng.

def initialize
    @radius = 0
end

Chúng ta định nghĩa một biến instance là @radius.

def set_radius radius
    @radius = radius
end

Phương thức set_radius sẽ nhận một tham số đầu vào và gán giá trị đó cho biến @radius.

def area
    @radius * @radius * @@PI
end

Phương thức area sẽ tính diện tích hình tròn với biến @radius@@PI.

c = Circle.new
c.set_radius 5
puts c.area

Nếu như trong các ngôn ngữ khác, chúng ta có từ khóa return để trả về một giá trị của một phương thức thì trong Ruby giá trị này sẽ tự động được trả về ngầm bằng giá trị của câu lệnh cuối cùng được thực hiện trong phương thức. Trong đoạn code trên, phương thức puts c.area sẽ in ra giá trị được trả về là diện tích được tính bên trong phương thức area.

78.5398

Quyền truy cập

Quyền truy cập tức là phạm vi truy xuất các thuộc tính và phương thức của mỗi đối tượng. Ruby có 3 loại quyền truy cập là public, protectedprivate. Trong Ruby, tất cả các thuộc tính đều có quyền truy cập là private và không thể thay đổi được, còn các phương thức thì mặc định có quyền truy cập là public nhưng có thể thay đổi được.

Quyền truy cập loại public cho phép chúng ta truy cập thành phần của đối tượng ở bên trong lẫn bên ngoài lớp. Quyền protectedprivate giống nhau ở chỗ đều không cho phép truy cập thành phần của đối tượng ở bên ngoài lớp, khác nhau ở chỗ private không gọi được với từ khóa self, còn protected thì được.

Quyền truy cập đảm bảo an toàn cho dữ liệu không bị thay đổi cho dù là cố ý hay vô ý.

Ví dụ 1:

class Some
        
     def method1
         puts "public method1 called"
     end

    public
    
     def method2
         puts "public method2 called"  
     end
     
     def method3
         puts "public method3 called"
         method1
         self.method1
     end          
end

s = Some.new
s.method1
s.method2
s.method3

Trong ví dụ này chúng ta sử dụng quyền truy cập public.

def method1
    puts "public method1 called"
end

Phương thức method1 có quyền truy cập mặc định là public.

public

  def method2
      puts "public method2 called"  
  end

  ...

Ngoài ra chúng ta có thể chỉ rõ cho Ruby biết là phương thức nào có quyền public bằng cách ghi từ khóa public lên trước phần định nghĩa phương thức đó, trong trường hợp này cả method2method3 đều có quyền truy cập là public.

s = Some.new
s.method1
s.method2
s.method3

Chỉ có các phương thức public mới có thể truy cập ở bên ngoài phần định nghĩa lớp.

public method1 called
public method2 called
public method3 called
public method1 called
public method1 called

Ví dụ 2:

class Some
    
    def initialize
        method1
        # self.method1
    end

    private
    
     def method1
         puts "private method1 called"  
     end
           
end


s = Some.new
# s.method1

Để chỉ định phương thức nào là private thì chúng ta đặt từ khóa private lên trước định nghĩa phương thức đó. Các phương thức private chỉ có thể gọi được trong phần định nghĩa lớp nhưng không được sử dụng từ khóa self.

private method called

Ví dụ 3:

class Some
    
    def initialize
        method1
        self.method1
    end

    protected
    
     def method1
         puts "protected method1 called"  
     end
           
end


s = Some.new
# s.method1

Tương tự như private, để chỉ định phương thức protected thì chúng ta đặt từ khóa protected lên trước định nghĩa phương thức. Phương thức protected khác private ở chỗ là chúng có thể truy cập với từ khóa self, giống private ở chỗ là không thể truy cập được ở bên ngoài phần định nghĩa lớp.

Thừa kế

Thừa kế là tính năng cho phép định nghĩa các lớp dựa trên các lớp đã có. Lớp thừa kế từ một lớp khác được gọi là lớp dẫn xuất, lớp được lớp khác thừa kế lại được gọi là lớp cơ sở. Lớp dẫn xuất thừa hưởng các thành phần của lớp cơ sở và có thể có thêm các thành phần của riêng chúng. Tính năng thừa kế cho phép lập trình viên giảm thiểu sự phức tạp của chương trình.

Ví dụ 1:

class Being

    def initialize
        puts "Being class created"
    end
end

class Human < Being

   def initialize
       super
       puts "Human class created"
   end
end

Being.new
Human.new

Trong ví dụ trên chúng ta có 2 lớp là lớp Being và lớp Human, trong đó lớp Being là lớp cơ sở, lớp Human là lớp dẫn xuất. Tức là lớp Human kế thừa từ lớp Being.

class Human < Being

Để một lớp kế thừa từ một lớp khác thì chúng ta ghi dấu "<" vào sau tên lớp và ghi tên lớp dẫn xuất phía sau.

def initialize
    super
    puts "Human class created"
end

Phương thức super có tác dụng gọi đến hàm khởi tạo của lớp cha.

Being class created
Being class created
Human class created

Ví dụ 2:

Một lớp có thể có nhiều lớp cơ sở. Mỗi lớp trong Ruby mặc định có phương thức ancestors, phương thức này trả về danh sách các lớp cơ sở của lớp đó. Và mặc định tất cả các lớp trong Ruby đều kế thừa từ một lớp gốc có tên là Object và BasicObject trong module Kernel.

class Being 
end

class Living < Being 
end

class Mammal < Living 
end

class Human < Mammal 
end
    
    
p Human.ancestors

Trong ví dụ này chúng ta có bốn lớp là Human kế thừa từ Mammal, lớp Mammal lại kế thừa từ lớp Living và lớp Living kế thừa từ lớp Being.

p Human.ancestors

Phương thức ancestors sẽ in danh sách các lớp cơ sở.

[Human, Mammal, Living, Being, Object, Kernel, BasicObject]

Lưu ý là tính năng thừa kế trong Ruby hơi khác so với các ngôn ngữ như C++, C#, Java… ở chỗ là trong các ngôn ngữ khác thì các thành phần publicprotected đều được truyền lại từ lớp cha đến lớp con còn thành phần private thì không, nhưng trong Ruby thì cả 3 loại public, protectedprivate đều được truyền lại cho lớp con, tức là tính năng thừa kế trong Ruby không có liên quan gì đến quyền truy cập cả.

Phương thức super

Phương thức super có tác dụng gọi đến phương thức cùng tên ở lớp cha.

class Base
   
    def show x=0, y=0
        p "Base class, x: #{x}, y: #{y}"
    end
end

class Derived < Base

    def show x, y
        super
        super x
        super x, y
        super()
    end
end


d = Derived.new
d.show 3, 3

Trong ví dụ trên chúng ta có lớp Base và lớp Derived, trong đó lớp Derived kế thừa từ lớp Base. Cả 2 lớp này đều có phương thức show.

def show x, y
    super
    super x
    super x, y
    super()
end

Việc gọi super trong phương thức show ở lớp con sẽ gọi đến phương thức show ở lớp cha. Nếu phương thức ở lớp cha có nhận tham số mà chúng ta không truyền vào phương thức super thì phương thức này sẽ tự động nhận tham số của phương thức con, tức là các tham số truyền vào phương thức con sẽ tự động truyền vào trong lời gọi phương thức super luôn. Hoặc chúng ta có thể gọi super() để không truyền vào một tham số nào cả.

"Base class, x: 3, y: 3"
"Base class, x: 3, y: 0"
"Base class, x: 3, y: 3"
"Base class, x: 0, y: 0"

Ruby – Bảng băm

Bảng băm là một kiểu dữ liệu lưu trữ theo dạng tập hợp giống như mảng, nhưng các phần tử không được lưu chỉ số mà lưu theo khóa, tức là các phần tử của bảng băm có 2 thành phần là khóa và giá trị.

Khởi tạo bảng băm

Ví dụ 1:

Để tạo một đối tượng bảng băm thì chúng ta có thể dùng phương thức new với lớp Hash.

hash = Hash.new
hash[1] = "Jane"
hash.store(2, "Thomas")

puts hash

Trong đoạn code trên chúng ta tạo một bảng băm từ lớp Hash với phương thức new.

hash[1] = "Jane"
hash.store(2, "Thomas")

Để tạo các phần tử trong bảng băm thì chúng ta có thể dùng toán tử [] với tên khóa bên trong. Ở trên chúng ta dùng khóa là các số nguyên, nhưng nếu muốn chúng ta có thể dùng khóa là các kí tự. Ngoài ra lớp Hash còn có phương thức store để tạo các phần tử của bảng băm tương tự như toán tử [].

puts hash

Phương thức puts sẽ in các phần tử của bảng băm ra trong cặp dấu {}. Các phần tử của bảng băm sẽ được in theo dạng <khóa> => <giá trị>.

{1=>"Jane", 2=>"Thomas"}

Ví dụ 2:

hash = { "de" => "Germany",
    "sk" => "Slovakia",
    "hu" => "Hungary",
    "us" => "United States",
    "no" => "Norway" 
}

puts hash["de"]
puts hash["no"]

Chúng ta có thể tạo nhanh các phần tử của bảng băm bằng theo cú pháp {<khóa>=><giá trị>}.

hash = { "de" => "Germany",
    "sk" => "Slovakia",
    "hu" => "Hungary",
    "us" => "United States",
    "no" => "Norway" 
}

Trong ví dụ này chúng ta tạo các khóa và giá trị là các chuỗi string.

puts hash["de"]

Chúng ta có thể in từng giá trị của từng phần tử nhất định thông qua khóa của chúng với toán tử [].

Germany
Norway

Các thao tác trên bảng băm

Ví dụ 1:

hash = Hash.new

hash[1] = "Jane"
hash[2] = "Thomas"
hash[3] = "Robert"
hash[4] = "Julia"
hash[5] = "Rebecca"

puts "Hash size: #{hash.size}"

puts hash.keys.inspect
puts hash.values.inspect

Trong ví dụ này chúng ta sử dụng một số phương thức cơ bản của bảng băm.

puts "Hash size: #{names.size}"

Phương thưc size sẽ trả về số lượng phần tử của bảng băm.

puts hash.keys.inspect
puts hash.values.inspect

Phương thức keys sẽ trả về danh sách các khóa trong khi phương thức values sẽ trả về danh sách các giá trị có trong bảng băm.

Hash size: 5
[1, 2, 3, 4, 5]
["Jane", "Thomas", "Robert", "Julia", "Rebecca"]

Ví dụ 2:

hash = Hash.new

hash[1] = "Jane"
hash[2] = "Thomas"
hash[3] = "Robert"
hash[4] = "Julia"
hash[5] = "Rebecca"

hash2 = hash.dup

puts hash.eql? hash2

puts hash.empty?
hash.clear
puts hash.empty?

Trong ví dụ này chúng ta sử dụng một số phương thức khác của bảng băm.

hash2 = hash.dup

Phương thức dup sẽ tạo một bảng băm khác có các phần tử giống như bảng băm gốc.

puts hash.eql? hash2

Phương thức eql? cho biết 2 bảng băm có các cặp khóa-giá trị giống nhau hay không.

puts hash.empty?

Phương thức empty? cho biết bảng băm có rỗng hay không, dòng code trên sẽ trả về false.

hash.clear
puts hash.empty?

Phương thức clear sẽ xóa toàn bộ bảng băm, do đó phương thức empty? ở sau sẽ trả về True.

true
false
true

Ví dụ 3:

hash = { :de => "Germany", 
    :sk => "Slovakia",
    :no => "Norway", 
    :us => "United States"
 }

puts hash.has_key? :de
puts hash.include? :no
puts hash.key? :me
puts hash.member? :sk

puts hash.has_value? "Slovakia"
puts hash.value? "Germany"

Trong ví dụ này chúng ta kiểm tra sự tồn tại của các phần tử. Ngoài ra ở đây chúng ta sử dụng các đối tượng Symbol để làm khóa vì Symbol dễ dùng hơn và cũng tốn ít bộ nhớ hơn.

puts hash.has_key? :de
puts hash.include? :no
puts hash.key? :me
puts hash.member? :sk

Phương thức has_key?, include?, key?member? đều kiểm tra xem một khóa nào đó có tồn tại trong bảng băm hay không.

puts hash.has_value? "Slovakia"
puts hash.value? "Germany"

Phương thức has_value?value? cho biết giá trị nào đó có tồn tại trong bảng băm hay không.

true
true
false
true
true
true

Ví dụ 4:

hash = { 1 => "Germany", 
    2 => "Norway", 
    3 => "United Kingdom", 
    4 => "United States"
 }

puts hash.fetch 1
puts hash[2]
puts hash.values_at 1, 2, 3

Trong ví dụ này chúng ta sử dụng các phương thức đọc dữ liệu từ bảng băm.

puts hash.fetch 1

Phương thức fetch nhận vào khóa và trả về giá trị.

puts hash[2]

Như chúng ta đã biết, toán tử [] sẽ trả về giá trị với khóa được chỉ định.

puts hash.values_at 1, 2, 3

Phương thức values_at sẽ trả về các phần tử có khóa được chỉ định.

Germany
Norway
Germany
Norway
United Kingdom

Duyệt bảng băm

Có rất nhiều cách để duyệt qua từng phần tử trong bảng băm.

hash = { 1 => "Germany", 
    2 => "Norway", 
    3 => "United Kingdom", 
    4 => "United States"
 }

hash.each { |k, v| puts "Key: #{k}, Value: #{v}" }
hash.each_key { |key| puts "#{key}" }
hash.each_value { |val| puts "#{val}" }
hash.each_pair { |k, v| puts "Key: #{k}, Value: #{v}" }

Trong ví dụ này chúng ta sử dụng 4 phương thức khác nhau để duyệt qua bảng băm.

hash.each { |k, v| puts "Key: #{k}, Value: #{v}" }

Phương thức each sẽ duyệt qua toàn bộ từng phần tử trong bảng băm, mỗi lần duyệt chúng ta thực thi đoạn lênh bên trong cặp dấu {}. Trong đó |k, v| đại diện cho khóa và giá trị, kv chỉ là những cái tên thay thế, chúng ta có thể dùng tên bất kì do chúng ta đặt như |key, value|...

hash.each_key { |key| puts "#{key}" }

Tương tự, phương thức each_key sẽ duyệt qua từng phần tử nhưng chỉ lấy khóa chứ không thể lấy được giá trị của từng khóa.

hash.each_value { |val| puts "#{val}" }

Phương thức each_value sẽ duyệt qua từng phần tử và lấy giá trị, không lấy khóa.

hash.each_pair { |k, v| puts "Key: #{k}, Value: #{v}" }

Phương thức each_pair duyệt qua từng phần tử giống hệt như phương thức each.

Key: 1, Value: Germany
Key: 2, Value: Norway
Key: 3, Value: United Kingdom
Key: 4, Value: United States
1
2
3
4
Germany
Norway
United Kingdom
United States
Key: 1, Value: Germany
Key: 2, Value: Norway
Key: 3, Value: United Kingdom
Key: 4, Value: United States

Xóa phần tử trong bảng băm

hash = Hash.new

hash[1] = "Jane"
hash[2] = "Thomas"
hash[3] = "Robert"
hash[4] = "Julia"
hash[5] = "Rebecca"

hash.delete 4
hash.shift

puts hash

Lớp Hash có một số phương thức để xóa các phần tử ra khỏi bảng băm.

hash.delete 4

Phương thức delete sẽ xóa phần tử có khóa được chỉ định.

hash.shift

Phương thức shift sẽ xóa phần tử ở vị trí đầu tiên.

{2=>"Thomas", 3=>"Robert", 5=>"Rebecca"}

Trộn bảng băm vào nhau

Chúng ta có thể trộn các bảng băm vào nhau để tạo thành một bảng băm mới.

hash1 = Hash.new

hash1[1] = "Jane"
hash1[2] = "Thomas"

hash2 = Hash.new

hash2[3] = "Robert"
hash2[4] = "Julia"

hash = hash1.merge hash2
puts hash

hash = hash1.update hash2
puts hash

Trong ví dụ này chúng ta sử dụng 2 phương thức là mergeupdate.

hash = hash1.merge hash2
puts hash

Cả 2 phương thức mergeupdate sẽ trộn 2 bảng băm vào nhau để tạo thành một bảng băm mới.

{1=>"Jane", 2=>"Thomas", 3=>"Robert", 4=>"Julia"}
{1=>"Jane", 2=>"Thomas", 3=>"Robert", 4=>"Julia"}

Ruby – Mảng

Mảng là một tập hợp các phần tử có thứ tự. Khác với biến thông thường là mỗi biến chỉ lưu một giá trị tại một thời điểm, mảng lưu nhiều giá trị khác nhau tại một thời điểm. Kiểu dữ liệu của mảng cũng là kiểu dữ liệu động, tức là một mảng có thể lưu cùng lúc một chuỗi string, một số nguyên hoặc cả một đối tượng khác. Các phần tử trong mảng ngoài giá trị ra còn được đánh số thứ tự, số thứ tự trong mảng được bắt đầu từ 0 giống như các ngôn ngữ khác.

Ví dụ:

arr = [1, 2, 3, 4, 5]

arr.each do |num|
    puts num
end

Chúng ta có một mảng tên là arr có 5 phần tử là 5 số nguyên. Chúng ta dùng phương thức each duyệt qua mỗi phần tử trong mảng rồi in giá trị ra màn hình.

1
2
3
4
5

Khởi tạo mảng

Mảng là một đối tượng, do đó chúng ta có thể sử dụng phương thức new để tạo mảng.

Ví dụ 1:

arr = Array.new

arr.push 1
arr.push 2
arr.push 3
arr.push 4
arr.push 5

puts arr

Trong đoạn code trên chúng ta tạo một mảng có 5 số nguyên.

arr = Array.new

Để tạo một mảng thì chúng ta dùng phương thức new của lớp Array.

arr.push 1

Phương thức push sẽ thêm một phần tử vào cuối mảng.

Ví dụ 2:

a1 = Array.new 
a2 = Array.new 3
a3 = Array.new 6, "coin"
a4 = Array.new [11]

puts [a1, a2, a3, a4].inspect

Phương thức new có thể nhận một vài tham số khi khởi tạo.

a1 = Array.new 

Nếu chúng ta chỉ gọi phương thức new thì Ruby sẽ tạo một mảng rỗng.

a2 = Array.new 3

Tham số đầu tiên mà phương thức này nhận là số lượng phần tử mảng, dòng code trên sẽ tạo một mảng có 3 phần tử là các đối tượng nil.

a3 = Array.new 6, "coin"

Tham số thứ 2 là giá trị mặc định, dòng code trên sẽ tạo một mảng có 6 phần tử với mỗi phần tử là một string có giá trị là “coin”.

a4 = Array.new [11]

Hoặc chúng ta có thể khởi tạo các phần tử và giá trị luôn bằng cặp dấu ngoặc vuông []. Dòng code trên tạo một mảng có một phần tử là số 11.

puts [a1, a2, a3, a4].inspect

Một mảng cũng có thể chứa các mảng khác, trong dòng trên chúng ta đưa 4 mảng a1, a2, a3 và a4 vào một mảng. Phương thức inspect sẽ trả về một chuỗi mô tả các phần tử của mảng nằm trong cặp dấu [].

[[], [nil, nil, nil], ["coin", "coin", "coin", "coin", "coin", "coin"], [11]]

Ví dụ 3:

Mỗi phần tử trong Ruby có thể mang bất kì kiểu dữ liệu nào, các phần tử cũng không cần phải có kiểu dữ liệu giống nhau.

class Empty
  
end

arr1 = [1, 2, 3, 4, 5]

arr2 = [1, -1, "big", 3.4, Empty.new, arr1, :two]

puts arr2.inspect

Trong ví dụ này chúng ta có mảng arr2 chứa số nguyên, chuỗi, số thực, một đối tượng lớp, một symbol và một mảng khác.

[1, -1, "big", 3.4, #<Empty:0x2591868>, [1, 2, 3, 4, 5], :two]

Ví dụ 4:

Các mảng có thể lồng vào nhau không giới hạn.

arr = [1, 2, 3, [2, 4, 6, [11, 12]]]

puts arr.length
puts arr[0], arr[1]

puts arr[3][0]
puts arr[3][1]

puts arr[3][3][0]
puts arr[3][3][1]

puts arr.flatten!.inspect

Trong ví dụ trên, mảng [11, 12] được lồng vào mảng [2, 4, 6], mảng [2, 4, 6] lại được lồng vào mảng [1, 2, 3].

puts arr.length

Phương thức length sẽ trả về 4 vì một mảng lồng trong một mảng khác cũng chỉ tính là một phần tử.

puts arr[0], arr[1]

Toán tử [] sẽ lấy giá trị của các phần tử mảng tương ứng. Trong dòng code trên chúng ta in ra giá trị của phần tử thứ 0 và thứ 1.

puts arr[3][0]
puts arr[3][1]

Để lấy giá trị của phần tử nằm trong mảng con bên trong thì chúng ta lại dùng thêm một toán tử [] khác nữa. Ở đây [3][0] tức là lấy phần tử đầu tiên của phần tử thứ 4 trong mảng [2, 4, 6, [11, 12]] (vì chỉ số được đánh từ 0).

puts arr[3][3][0]
puts arr[3][3][1]

Tương tự với các phần tử mảng nằm sâu bên trong, chúng ta chỉ cần dùng toán tử [] là lấy được.

puts arr.flatten!.inspect

Phương thức flatten! sẽ lấy các phần tử của mảng con nhập vào mảng cha và tạo thành một mảng mới.

4
1
2
2
4
11
12
[1, 2, 3, 2, 4, 6, 11, 12]

Xuất mảng ra màn hình

Có rất nhiều cách để in các phần tử mảng ra màn hình.

Trong ví dụ trên chúng ta in mảng ra bằng 3 cách.

puts arr

Cách đơn giản nhất là dùng phương thức puts hoặc print, 2 phương thức này sẽ in các phần tử mảng trên từng dòng.

puts arr.inspect

Phương thức inspect sẽ in các phần tử mảng trong cặp dấu [], mỗi phần tử ngăn cách nhau bởi dấu phẩy.

arr.each do |e|
    puts e
end

Phương thức each sẽ duyệt qua từng phần tử trong mảng, mỗi lần duyệt sẽ thực hiện các câu lệnh phía sau từ khóa do.

1
2
3
4
5
[1, 2, 3, 4, 5]
1
2
3
4
5

Tuy xuất các phần tử mảng

Các đối tượng mảng có một số phương thức dùng để lấy các phần tử tại bất kì vị trí nào.

Ví dụ:

alpha = %w{ a b c d e f g h}
puts alpha.first
puts alpha.last
puts alpha.at(3)

Trong ví dụ trên chúng ta dùng phương thức first để lấy phần tử đầu tiên, phương thức last lấy phần tử ở vị trí cuối cùng, phương thức at(i) lấy phần tử tại vị trí thứ i.

alpha = %w{a b c d e f g h}

Ngoài ra chúng ta còn có cách tạo nhanh một mảng chứa các phần tử là chuỗi theo cú pháp %w{}, theo cú pháp này %w{a b} tương đương với ['a', 'b'].

a
h
d

Ví dụ 2:

Thông thường chúng ta sẽ dùng toán tử [] để truy xuất phần tử mảng.

alpha = %w{ a b c d e f g h }

puts alpha[0]
puts alpha[-1]
puts alpha[0, 3].inspect
puts alpha[2..6].inspect
puts alpha[2...6].inspect

Trong ví dụ trên chúng ta có 5 cách sử dụng toán tử [] để truy xuất phần tử mảng.

puts alpha[0]
puts alpha[-1]

Để lấy từng phần tử thì chúng ta có thể đưa vào chỉ số mảng, chỉ số mảng trong Ruby được đánh số từ 0 và chúng ta cũng có thể đưa vào chỉ số âm để lấy phần tử từ cuối mảng.

puts alpha[0, 3].inspect

Chúng ta có thể sử dụng toán tử [] theo cú pháp [i, n] và Ruby sẽ lấy n phần tử từ vị trí i. Phương thức inspect chẳng qua là để in các phần tử mảng trong cặp dấu [] trên một dòng ngăn cách bằng dấu phẩy cho dễ đọc.

puts alpha[2..6].inspect
puts alpha[2...6].inspect

Như chúng ta đã biết, [n..i] sẽ lấy các phần tử từ vị trí n đến vị trí i, [n...i] sẽ lấy các phần tử từ vị trí n đến vị trí i - 1.

Ví dụ 3:

alpha = %w{ a b c d e f g h}

puts alpha.values_at(1..5).inspect
puts alpha.values_at(1, 3, 5).inspect
puts alpha.values_at(1, 3, 5, 6, 8).inspect
puts alpha.values_at(-1, -3).inspect

Chúng ta có thể sử dụng phương thức values_at để lấy nhiều phần tử tại những vị trí riêng biệt. Giá trị trả về của phương thức values_at là một mảng chứa các phần tử được lấy ra..

puts alpha.values_at(1..5).inspect

Dòng trên sẽ trả về các phần tử từ vị trí 1 đến vị trí 5.

puts alpha.values_at(1, 3, 5).inspect

Dòng trên lấy các phần tử ở vị trí 1, 3, và 5.

puts alpha.values_at(-1, -3).inspect

Chúng ta cũng có thể đưa vào các chỉ số âm như với toán tử [].

["b", "c", "d", "e", "f"]
["b", "d", "f"]
["b", "d", "f", "g", nil]
["h", "f"]

Ví dụ 4:

alpha = %w{ a b c d e f g h}

puts alpha.slice(0)
puts alpha.slice(-1)
puts alpha.slice(0, 3).inspect
puts alpha.slice(2..6).inspect
puts alpha.slice(2...6).inspect

Mảng trong Ruby có phương thức slice có chức năng và cách dùng y hệt như toán tử [].

a
h
["a", "b", "c"]
["c", "d", "e", "f", "g"]
["c", "d", "e", "f"]

Ví dụ 6:

alpha = %w{ a b c d e f g h}

puts alpha.sample
puts alpha.sample(3).inspect

Phương thức sample có tác dụng lấy một hoặc một số phần tử tại vị trí bất kì.

b
["c", "f", "d"]

Một số thao tác trên mảng

Ví dụ 1:

arr1 = [1, 2, 3, 4, 5]
arr2 = [6, 7, 8, 9, 10]

puts arr1 + arr2
puts arr1.concat arr2

Chúng ta có thể nối 2 mảng vào nhau bằng toán tử + hoặc dùng phương thức concat.

Ví dụ 2:

alpha = %w{ a b c d e f}

puts alpha.inspect
puts "Array size: #{alpha.length}"
puts "First element: #{alpha.first}"
puts "Last element: #{alpha.last}"

puts alpha.eql? alpha.dup
puts alpha.eql? alpha.dup.delete_at(0)

alpha.clear
puts alpha.inspect
puts alpha.empty?

Trong ví dụ trên chúng ta làm việc với một số phương thức mới.

puts "Array size: #{alpha.length}"

Chúng ta đã biết, phương thức length cho biết số lượng phần tử trong mảng.

puts "First element: #{alpha.first}"
puts "Last element: #{alpha.last}"

Phương thức firstlast lấy phần tử đầu tiên và phần tử cuối cùng.

puts alpha.eql? alpha.dup

Phương thức eql! so sánh 2 mảng và trả về True nếu 2 mảng bằng nhau. Trong đoạn code trên là trả về True vì ở đây chúng ta dùng phương thức dup để tạo ra một mảng mới copy từ mảng gốc.

puts alpha.eql? alpha.dup.delete_at(0)

Phương thức delete_at(i) sẽ xóa phần tử tại vị trí thứ i. Dòng trên sẽ trả về False vì 2 mảng không giống nhau.

alpha.clear

Phương thức clear sẽ xóa toàn bộ mảng.

puts alpha.empty?

Phương thức empty? cho biết mảng có rỗng hay không.

["a", "b", "c", "d", "e", "f"]
Array size: 6
First element: a
Last element: f
true
false
[]
true

Ví dụ 3:

alpha = %w{a b c d e}

alpha1 = alpha.reverse
puts alpha1.inspect
puts alpha.inspect

alpha1 = alpha.reverse!
puts alpha1.inspect
puts alpha.inspect

Khi sử dụng các phương thức mà có thay đổi giá trị phần tử mảng thì chúng ta phải thêm dấu chấm ! vào cuối tên phương thức.

Phương thức reverse sẽ đảo ngược các phần tử trong mảng, ví dụ phần tử đầu tiên đổi chỗ cho phần tử cuối cùng…

["e", "d", "c", "b", "a"]
["a", "b", "c", "d", "e"]
["e", "d", "c", "b", "a"]
["e", "d", "c", "b", "a"]

Ví dụ 4:

arr = [1, 2, 2, 2, 3, 4, 5, 8, 11]

puts arr.index 2
puts arr.index 11
puts arr.rindex 2

puts arr.include? 3
puts arr.include? 10

puts arr.join '-'
puts arr.uniq!.inspect

Trong ví dụ này chúng ta có thêm một số phương thức thao tác với mảng khác.

puts arr.index 2
puts arr.index 11

Phương thức index i trả về chỉ số của phần tử đầu tiên có giá trị i, ví dụ chúng ta có 3 phần tử có giá trị là 2 thì index 2 sẽ trả về phần tử đầu tiên có giá trị 2 là phần tử thứ 1.

puts arr.rindex 2

Phương thức rindex cũng có tác dụng như phương thức index nhưng tìm các phần tử từ phải qua trái.

puts arr.include? 3
puts arr.include? 10

Phương thức include? cho biết một giá trị có tồn tại trong mảng hay không, phương thức này trả về True nếu có và False nếu ngược lại.

puts arr.join '-'

Phương thức join sẽ tạo một chuỗi string có các phần tử ngăn cách nhau bởi kí tự được truyền vào, trong ví dụ trên là kí tự "-".

puts arr.uniq!.inspect

Phương thức uniq! sẽ loại bỏ các phần tử có giá trị giống nhau.

1
8
3
true
false
1-2-2-2-3-4-5-8-11
[1, 2, 3, 4, 5, 8, 11]

Thêm bớt phần tử

Ví dụ 1:

alpha = []

alpha.insert 0, 'E', 'F', 'G'
alpha.push 'H'
alpha.push 'I', 'J', 'K'
alpha << 'L' << 'M' 
alpha.unshift 'A', 'B', 'C'
alpha.insert(3, 'D')
 
puts alpha.inspect

Để thêm một phần tử vào mảng thì có rất nhiều cách.

alpha.insert 0, 'E', 'F', 'G'

Phương thức insert sẽ thêm các phần tử vào vị trí được chỉ định, trong đoạn code trên chúng ta thêm 3 kí tự E, F, G vào vị trí 0 nhưng vì mỗi vị trí chỉ có một phần tử nên F, G sẽ ở vị trí 1 và 2.

alpha.push 'H'
alpha.push 'I', 'J', 'K'

Phương thức push sẽ thêm phần tử vào cuối mảng. Chúng ta có thể thêm một lúc nhiều phần tử bằng ngăn cách nhau bởi dấu phẩy.

alpha << 'L' << 'M' 

Toán tử << cũng có tác dụng như phương thức push.

alpha.unshift 'A', 'B', 'C'

Phương thức unshift ngược lại với phương thức push là chèn các phần tử mới vào đầu mảng.

alpha.insert(3, 'D')

Phương thức insert() sẽ chèn phần tử vào vị trí được chỉ định.

["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M"]

Ví dụ 2:

alpha = %w{ a b c d e f g h}

alpha.pop
alpha.pop

puts alpha.inspect

alpha.shift
alpha.shift

puts alpha.inspect

alpha.delete_at(0)
alpha.delete('d')

puts alpha.inspect

puts alpha.clear
puts alpha.inspect

Để xóa các phần tử ra khỏi mảng cũng có nhiều cách.

alpha.pop

Phương thức pop xóa phần tử cuối cùng của mảng.

alpha.shift

Phương thức shift xóa phần tử đầu tiên của mảng.

alpha.delete_at(0)

Phương thức delete_at(i) xóa phần tử tại vị trí thứ i.

puts alpha.clear

Phương thức clear xóa toàn bộ mảng.

alpha.delete('d')

Phương thức delete(i) xóa phần tử có giá tị là i.

["a", "b", "c", "d", "e", "f"]
["c", "d", "e", "f"]
["e", "f"]
[]

Toán tử tập hợp

Tập hợp cũng là một danh sách các phần tử nhưng các phần tử không được phép trùng nhau.

A = [1, 2, 3, 4, 5]
B = [4, 5, 6, 7, 8]

union = A | B
isect = A & B
diff1  = A - B
diff2  = B - A
sdiff = (A - B) | (B - A)

puts "Union: #{union}"
puts "Intersection: #{isect}"
puts "Difference of A - B: #{diff1}"
puts "Difference of B - A: #{diff2}"    
puts "Symmetric: #{sdiff}" 

Ruby có các toán tử có chức năng thực hiện các phép toán tập hợp trên mảng.

union = A | B

Toán tử | sẽ thực hiện phép hợp, phần tử nào cũng được lấy.

isect = A & B

Toán tử ^ thực hiện phép giao, chỉ có các phần tử cùng nằm chung 2 mảng mới được lấy.

diff1  = A - B
diff2  = B - A

Toán tử "-" thực hiện phép hiệu, phép hiệu A – B sẽ lấy các phần tử thuộc tập A và các phần tử trong tập B mà cũng có trong tập A.

sdiff = (A - B) | (B - A)

Phép hiệu đối xứng sẽ lấy các phần tử của riêng từng tập hợp, phần tử nào có mặt trong cả 2 tập hợp thì không lấy. Thực ra trong toán học không có phép toán này, ở đây mình chỉ kết hợp phép hiệu với phép giao lại thôi.

Union of arrays: [1, 2, 3, 4, 5, 6, 7, 8]
Intersection of arrays: [4, 5]
Difference of arrays A - B: [1, 2, 3]
Difference of arrays B - A: [6, 7, 8]
Symmetric difference of arrays: [1, 2, 3, 6, 7, 8]

Sắp xếp mảng

Ruby có một số phương thức sắp xếp các phần tử mảng rất mạnh.

arr = %w{ Mercury Venus Earth Mars Jupiter
                Saturn Uranus Neptune Pluto }
               
puts "#{arr.sort}"                
puts "#{arr.reverse}"
puts "#{arr.shuffle}"

Phương thức sort sắp xếp các phần tử từ bé đến lớn, hoặc theo thứ thự bảng chữ cái nếu các phần tử là kiểu chuỗi.

Phương thức reverse thì sắp xếp theo thứ tự ngược lại.

Phương thức shuffle sẽ xáo trộn vị trí các phần tử một cách ngẫu nhiên.

["Earth", "Jupiter", "Mars", "Mercury", "Neptune", "Pluto", "Saturn", "Uranus", "Venus"]
["Pluto", "Neptune", "Uranus", "Saturn", "Jupiter", "Mars", "Earth", "Venus", "Mercury"]
["Venus", "Mercury", "Uranus", "Mars", "Neptune", "Earth", "Jupiter", "Pluto", "Saturn"]

Ruby – Luồng điều khiển

Các câu lệnh điều khiển chịu trách nhiệm điều khiển luồng chương trình chạy trong Ruby. Các câu lệnh điều khiển bao gồm 2 loại câu lệnh cơ bản là câu lệnh điều kiện và vòng lặp. Câu lệnh điều kiện là các câu lệnh có tác dụng thực hiện các câu lệnh khác nhau tùy theo từng điều kiện khác nhau. Vòng lặp là khối lệnh có tác dụng thực hiện các câu lệnh lặp đi lặp lại nhiều lần.

Khi một chương trình chạy thì các câu lệnh sẽ được thực hiện tuần tự từ trên xuống dưới.

Câu lệnh điều kiện if

Từ khóa if được dùng để kiểm tra xem một biểu thức có đúng hay không. Nếu đúng thì thực thi một câu lệnh hoặc một khối lệnh, một khối lệnh phải được đặt trước từ khóa end.

puts "Enter a number: "
num = gets.to_i

if num > 0 then

    puts "num variable is positive"
    puts "num variable equals to #{num}"
end

Phương thức gets.to_i sẽ đọc một chuỗi được nhập từ bàn phím và chuyển thành số nguyên rồi gán vào biến num, sau đó câu lệnh if kiểm tra xem nếu biến num lớn hơn 0 thì in các chuỗi text ra màn hình.

Enter a number: 4
num variable is positive
num variable equals to 4

Chúng ta có thể sử dụng từ khóa else để thực thi một khối lệnh khác nếu biểu thức trong if là sai:

age = 17

if age > 18
    puts "Adult"
else
    puts "Not Adult"
end

Trong ví dụ trên chúng ta có biến age là 17, câu lệnh if kiểm tra xem nếu biến age > 18 thì in ra chuỗi “Adult”, ngược lại thì in ra chuỗi "Not Adult".

Not Adult

Ngoài ra chúng ta còn có thể dùng từ khóa elseif để thực hiện kiểm tra nhiều biểu thức khác nhau:

print "Enter a number: "

num = gets.to_i

if num < 0 
    puts "#{num} is negative" 
elsif 
    num == 0 puts "#{num} is zero" 
elsif num > 0
   puts "#{num} is positive"
end

Trong ví dụ trên chúng ta cho nhận một số được nhập từ bàn phím và in ra chuỗi string tương ứng với từng trường hợp số đó bé hơn 0, bằng 0 hoặc lớn hớn 0.

Câu lệnh điều kiện case

Câu lệnh case có tác dụng lựa chọn các câu lệnh để thực thi dựa vào điều kiện đặt ra, do đó câu lệnh case cũng có tác dụng tương tự như câu lệnh if..elseif vậy. Cú pháp:

case <biến>                                                                                     when <giá trị>                                                                             else                                                                                 end

Bên trong khối lệnh case..end là các từ khóa whenelse, theo sau các từ khóa when là các giá trị, Khi chạy, Ruby sẽ so sánh biến theo sau từ khóa case với từng giá trị ở từng từ khóa when, nếu trùng tại đâu thì chạy câu lệnh phía sau từ khóa when đó, nếu không có từ khóa when nào khớp thì chạy câu lệnh mặc định sau từ khóa else.

print "Enter a domain: "

domain = gets.chomp

case domain
    when "us"
        puts "United States"
    when "de" 
        puts "Germany"
    when "no" 
        puts "Norway"
    when "hu" 
        puts "Hungary"
    else
        puts "Unknown"
end

Trong đoạn code trên chúng ta nhận một chuỗi được nhập từ bàn phím và gán vào biến domain, sau đó kiểm tra với từng giá trị trong câu lệnh case, nếu domain trùng với giá trị nào thì in một chuỗi string ra màn hình tương ứng với gái trị đó, nếu không trùng với giá trị nào thì in chuỗi “Unknown”.

domain = gets.chomp

Phương thức chomp sẽ nhận một chuỗi từ bàn phím, kể cá kí tự ENTER nhưng sau đó phương thức này sẽ tự loại bỏ kí tự ENTER ra khỏi chuỗi.

Enter a domain: no
Norway

Câu lệnh lặp while và until

Câu lệnh while cho phép một câu lệnh hoặc một khối lệnh được thực hiện lặp đi lặp lại nhiều lần trong điều kiện cho trước.

Theo sau từ khóa while là một biểu thức, nếu biểu thức này trả về true thì thực hiện câu lệnh theo sau nó rồi lặp lại biểu thức cho đến khi biểu thức trả về false thì thôi.

i = 0
sum = 0

while i < 10  do
   i = i + 1
   sum = sum + i
end

puts "Sum of 0..9 is #{sum}"

Đoạn code trên sẽ tính tổng của các số từ 0 đến 9.

Cứ mỗi lần lặp, các biến isum sẽ được tính lại giá trị mới cho đến khi điều kiện i < 10 sai thì dừng vòng lặp.

while i < 10  do
   ...
end

Từ khóa do là tùy chọn, không có cũng được.

Sum of 0..9 is 55

Câu lệnh until thì cũng có cấu trúc cú pháp giống như câu lệnh while, chỉ khác là until sẽ thực hiện các câu lệnh nếu điều kiện sai (false), nếu biểu thức sau until trả về true thì dừng vòng lặp.

hours_left = 12

until hours_left == 0
    
    if hours_left == 1
        puts "#{hours_left} hour left"
    else
        puts " #{hours_left} hours left"
    end

    hours_left -= 1
end

Trong ví dụ trên chúng ta có biến hours_left, biến này sẽ giảm dần theo từng vòng lặp và cứ mỗi lần lặp chúng ta in một đoạn string ra màn hình, vòng lặp sẽ dừng khi biến hours_left = 0.

12 hours left
11 hours left
10 hours left
9 hours left
8 hours left
7 hours left
6 hours left
5 hours left
4 hours left
3 hours left
2 hours left
1 hour left

Câu lệnh lặp for

Khác với câu lệnh whileuntil là số lần lặp không được biết trước, câu lệnh for sẽ chạy vòng lặp với số lần nhất định. Cũng giống các câu lệnh trên, câu lệnh for cũng kết thúc với từ khóa end và có thể có từ khóa do nếu muốn.

for i in 0..9 do

    puts "#{i}"
end

Đoạn code trên sẽ in các con số từ 0 đến 9. Biến i trong vòng lặp sẽ được thay thế bởi các số trong đoạn 0..9, toán tử .. sẽ tạo một biến danh sách các số nguyên từ 0 đến 9.

0
1
2
3
4
5
6
7
8
9

Thường chúng ta dùng vòng lặp for để duyệt qua một danh sách các phần tử.


planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter",
    "Saturn", "Uranus", "Neptune"]

for i in 0...planets.length

    puts planets[i]
end

Trong ví dụ trên chúng ta có một biến mảng, chúng ta duyệt qua mảng này bằng vòng lặp for với số lần lặp được lấy từ phương thức length, phương thức này cho biết số phần tử có trong mảng.

for i in 0...planets.length

Mảng được đánh số từ 0 nên biến i sẽ có giá trị từ 0 đến n-1, với n là số phần tử của mảng. Do dó chúng ta sử dụng toán tử ... để tạo một danh sách các con số từ 0 đến length-1.

Mercury
Venus
Earth
Mars
Jupiter
Saturn
Uranus
Neptune

Phương thức each

Mảng trong Ruby có phương thức each cho phép chúng ta duyệt qua mảng một cách dễ dàng.

planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter",
    "Saturn", "Uranus", "Neptune"]

planets.each do |iter| 

    puts iter
end

Phương thức này nhận vào 2 tham số là tên mảng và một khối lệnh, khối lệnh sẽ thực hiện các câu lệnh trong mỗi lần duyệt qua mảng.

planets.each do |iter| 

    puts iter
end

Phía sau phương thức each là khối lệnh nằm trong cặp từ khóa do..end, sau từ khóa do là tên của biến lặp, mỗi lần lặp, biến này sẽ tham chiếu đến từng phần tử của mảng. Cặp từ khóa do..end cũng có thể được thay thế bằng cặp dấu {}.

Câu lệnh break, next

Câu lệnh break có tác dụng kết thúc vòng lặp while, for hoặc case cho dù vòng lặp đó có chạy xong hay chưa.

while true

    r = 1 + rand(30)
    print "#{r} "

    if r == 22
        break
    end
end

Trong ví dụ trên chúng ta có một vòng lặp chạy vô thời hạn vì điều kiện sau while luôn luôn là true, do đó bên trong vòng lặp này chúng ta đặt một câu lệnh if để kiểm tra điều kiện, ở đây trong mỗi lần lặp chúng ta tạo một số ngẫu nhiên rồi gán vào biến r, sau đó chúng ta kiểm tra nếu r = 22 thì dừng vòng lặp bằng câu lệnh break.

r = 1 + rand(30)
print "#{r} "

Phương thức rand(n) sẽ cho ra một số ngẫu nhiên từ 0..n.

27 30 9 8 27 25 29 30 24 17 16 20 27 13 7 5 13 28 22

Câu lệnh next có tác dụng bỏ qua vòng lặp hiện tại và thực hiện vòng lặp tiếp theo, các câu lệnh sau từ khóa next sẽ không được thực thi.

num = 0

while num < 100

    num += 1

    if (num % 2 == 0)
        next
    end

    print "#{num} " 
end    

Trong ví dụ trên chúng ta in ra những số lẻ từ 1 đến 99.

if (num % 2 == 0)
    next
end

Biểu thức num % 2 == 0 sẽ trả về true nếu biến num là số chẵn, ngược lại là true, nếu num là số chẵn thì câu lệnh next sẽ bỏ qua vòng lặp hiện tại, tức là câu lệnh print ở dưới sẽ không được thực hiện mà nhảy đến vòng lặp tiếp theo.

1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99 

Ruby – Biểu thức

Biểu thức bao gồm toán tử và toán hạng. Toán tử là một kí tự đặc biệt thực hiện các phép tính toán trên các toán hạng và trả về một giá trị nào đó. Hầu hết các toán tử trong lập trình đều bắt nguồn từ toán học. Các toán tử khác nhau có các mức độ ưu tiên khác nhau. Toán hạng là các tham số đầu vào cho toán tử, trong lập trình thì toán hạng chính là các biến hoặc các giá trị.

Đây là bảng danh sách các toán tử của Ruby:

Tên Kí hiệu
Lấy thuộc tính, phương thức :: .
Thao tác mảng [ ] [ ]=
Lấy lũy thừa **
Toán tử phủ định, đảo bit, tăng, giảm ! ~ + -
Toán tử nhân, chia, chia lấy phần dư * / %
Toán tử cộng, trừ + -
Toán tử dịch bit << >>
Toán tử thao tác bit AND &
Toán tử thao tác bit OR ^ |
Toán tử so sánh hơn kém > >= < <=
Toán tử so sánh bằng <=> == === != =~ !~
Toán tử logic AND &&
Toán tử logic OR ||
Toán tử tạo phạm vi .. ...
Toán tử điều kiện ?:
Toán tử gán = += -= *= **= /= %= &= |= ^= <<= >>= ||= &&=
Phủ định not
Toán tử logic OR, AND or and

Các toán tử trong cùng một dòng có độ ưu tiên như nhau. Các toán tử ở các hàng trên có độ ưu tiên cao hơn các hàng dưới. Các toán tử có thể làm việc với một, hai hoặc ba toán hạng. Tùy vào từng trường hợp mà Ruby sẽ chọn toán tử nào để dùng vì có một số toán tử có kí hiệu giống nhau nhưng có ý nghĩa khác nhau.

Dấu của số

Chúng ta có 2 toán tử thay đổi dấu của số là +-. Ví dụ:

puts +2
puts -2

Dấu cộng chỉ định số đó là số nguyên dương, dấu trừ là số nguyên âm, nhưng cũng giống như trong toán hocj, dấu + có thể bỏ.

a = 1

puts a
puts -(a)
puts -(-(a))

Cũng giống như toán học, 2 dấu trừ sẽ chuyển thành dấu cộng.

1
-1
1

Toán tử gán

Toán tử gán sẽ gán giá trị cho biến, như chúng ta đã biết biến có nhiệm vụ lưu trữ giá trị. Toán hạng nằm phía bên phải sẽ được gán cho toán hạng nằm bên trái.

x = 1
puts x 

Chúng ta gán biến x có giá trị 1.

x = x + 1
puts x # prints 2

Trong lập trình toán hạng cũng có thể là một phép toán khác, trong ví dụ trên biến x được gán giá trị là x + 1.

1 = x; # Error

Chúng ta không thể gán một biến vào một giá trị, như thế là không hợp lệ, dòng code trên sẽ báo lỗi.

Toán tử lấy thuộc tính, phương thức

Đây là các toán tử có mức độ ưu tiên cao nhất, tức là trong một biểu thức thì chúng sẽ được thực hiện đầu tiên.

Toán tử :: có ý nghĩa là lấy hằng số, module hoặc một lớp được định nghĩa bên trong một lớp khác:

class MyMath
    Pi = 3.1415926535    
end

module People
    Name = "People"
end 

puts MyMath::Pi
puts People::Name

Trong đoạn code trên chúng ta có một lớp và một module, để lấy hằng số từ một lớp (hoặc module) thì chúng ta ghi tên lớp, sau đó là dấu :: rồi tên hằng, như thế các hằng số có tên giống nhau nhưng được đặt trong các lớp khác nhau sẽ không bị xung đột.

3.1415926535
People

Toán tử . truy xuất thuộc tính và phương thức của một đối tượng:

class Person
    
   def initialize name, age
       @name = name
       @age = age       
   end
   
   def info
       "#{@name} is #{@age} years old"
   end
    
end

p = Person.new "Jane", 17
puts p.info

Trong ví dụ trên chúng ta định nghĩa lớp Person có 2 phương thức do chúng ta định nghĩa. Chúng ta sử dụng toán tử . để truy xuất 2 phương thức này.

p = Person.new "Jane", 17
puts p.info

Chúng ta gọi phương thức new và phương thức info, phương thức new sẽ gọi đến phương thức initialize mà chúng ta đã viết, phương thức info sẽ trả về một chuỗi.

Jane is 17 years old

Toán tử toán học

Bao gồm các toán tử sau đây.

Kí hiệu Tên
+ Cộng
- Trừ
* Nhân
/ Chia
% Chia lấy phần dư
** Lũy thừa

Ví dụ:

a = 1
b = 2
c = 3

puts a + b + c
puts c - a
puts a * b
puts c / 3
puts c % a
puts c ** a

Các toán tử toán học giống y hệt như các phép toán trong toán học vậy.

puts c % a

Toán tử % chia hai số rồi lấy phần dư, ví dụ 9 chia 4 được 2 dư 1 thì toán tử này trả về 1.

6
2
2
1
0
3

Ví dụ 2:

puts 5 / 2

puts 5 / 2.0
puts 5.0 / 2
puts 5.to_f / 2

Trong ví dụ này chúng ta thực hiện phép chia trên số nguyên và số thực.

puts 5 / 2

Khi chúng ta thực hiện phép chia 2 số nguyên thì kết quả trả về là số nguyên.

puts 5 / 2.0
puts 5.0 / 2
puts 5.to_f / 2

Nếu một trong 2 toán hạng là số thực thì kết quả trả về là một số thực, ngoài ra chúng ta có thể dùng phương thức to_f để chuyển một số nguyên thành một số thực.

2
2.5
2.5
2.5

Ví dụ 3:

puts 5.div 2.0
puts 5.fdiv 2
puts 5.quo 2
puts 5.0.quo 2.0

Ngoài toán tử / thì chúng ta còn có các phương thức div, fdivquo cũng thực hiện phép chia.

puts 5.div 2.0

Phương thức div thực hiện phép chia số nguyên và trả về số nguyên cho dù toán hạng có là số thực.

puts 5.fdiv 2

Phương thức fdiv thì lại trả về số thực mặc dù toán hạng có là số nguyên.

puts 5.quo 2
puts 5.0.quo 2.0

Phương thức quo sẽ thực hiện phép chia và trả về số hữu tỉ dưới dạng phân số nếu các toán hạng là số nguyên, nếu một trong hai toán hạng là số thực thì trả về số thực.

2
2.5
5/2
2.5

Toán tử logic

Ruby có các toán tử logic sau:

Kí hiệu Tên
&& AND
|| OR
! negation

Toán tử logic sẽ trả kết quả về là một đối tượng boolean, tức là chỉ có true hoặc false. Ngoài 3 toán tử &&, ||! thì còn có 3 toán tử có tác dụng tương tự nhưng độ ưu tiên thấp hơn là and, ornot.

Ngoài ra các toán tử so sánh hay quan hệ cũng trả về một giá trị boolean:

x = 1
y = 2

puts x == y
puts y > x

if y > x then
    puts "y is greater than x"
end

Trong đoạn code trên chúng ta sử dụng toán tử so sánh == và toán tử quan hệ >.

puts x == y
puts y > x

Cả 2 dòng trên sẽ trả về kết quả là một giá trị boolean.

if y > x then
    puts "y is greater than x"
end

Các toán tử quan hệ thường được dùng trong câu lệnh điều kiện if, trong đoạn code trên phương thức puts sẽ được thực thi nếu biểu thức trong if trả về true.

Ví dụ 1:

puts true && true
puts true && false
puts false && true
puts false && false

Toán tử && trả về true nếu cả 2 toán hạng đều có giá trị là true.

true
false
false
false

Ví dụ 2:

puts true || true
puts true || false
puts false || true
puts false || false

Toán tử || trả về true nếu một trong 2 toán hạng là true, ngược lại trả về false.

true
true
true
false

Ví dụ 3:

puts !0
puts !1
puts !true
puts !false

puts ! (4<3)
puts ! "Ruby".include?("a")

Toán tử ! sẽ đảo ngược giá trị true thành false và ngược lại.

false
false
false
true
true
true

Khi Ruby thực hiện toán tử ||&& thì các toán hạng sẽ lần lượt được đánh giá, nếu toán hạng đầu tiên đủ điều kiện để đưa ra kết quả thì dừng lại chứ không xem xét tới các toán hạng phía sau nữa. Ví dụ trong biểu thức true || false thì Ruby chỉ cần nhìn thấy toán hạng đầu tiên là true thì sẽ trả về true luôn chứ không xem xét toán hạng thứ hai là true hay false.

Toán tử so sánh

Đây là các toán tử so sánh lớn hơn, bé hơn:

Kí hiệu tên
< bé hơn
<= bé hơn hoặc bằng
> lớn hơn
>= lớn hơn hoặc bằng

Ví dụ:

p 1 < 2 p 2 > 3
p 3 >= 3

Trong đoạn code trên, phép toán 1 < 2 trả về true, phép toán 2 > 3 trả về false. Phép toán 3 >= 3 trả về true.

Toán tử thao tác bit

Con người chúng ta thường làm việc với hệ cơ số 10 vì mặc nhiên chúng ta có 10 ngón tay, 10 ngón chân nên rất dễ tính toán, máy tính thì lại không như vậy, máy tính chỉ hiểu được hệ cơ số 2 – tức hệ nhị phân. Bạn cần nắm rõ cách hoạt động của các phép toán thao tác trên bit trước khi đi vào tìm hiểu thêm vì ở đây mình sẽ chỉ giải thích sơ lược qua các ví dụ.

Trong Ruby có các toán tử thao tác bit sau:

Kí hiệu ý nghĩa
~ Đảo ngược bit
^ Phép XOR
& Phép AND
| Phép OR
<< Dịch trái 1 bit
>> Dịch phải 1 bit

Thường thì chúng ta cũng ít khi dùng các toán tử này.

Ví dụ:

puts ~ 7   # prints -8 
puts ~ -8  # prints 7

puts 6 & 3  # prints 2
puts 3 & 6  # prints 2

puts 6 ^ 3  # prints 5
puts 3 ^ 6  # prints 5

puts 6 | 3  # prints 7
puts 3 | 6  # prints 7

puts 6 << 1  # prints 12
puts 1 << 6 # prints 64 puts 6 >> 1  # prints 3
puts 1 >> 6  # prints 0

Phép đảo ngược bit ~ sẽ chuyển tất cả các bit từ 0 sang 1 và ngược lại. Trong ví dụ này, 7 trong hệ 10 là 0111 trong hệ 2,  đảo ngược lại sẽ là 1000, mặc định Ruby làm việc với số nguyên có dấu, mà bit đầu tiên là bit xác định có dấu hay không nên 1000 trong hệ 2 sẽ là -8 trong hệ 10.

puts ~ 7   # prints -8 
puts ~ -8  # prints 7

Toán tử & sẽ thực hiện phép AND 2 số, trong đó nếu có cả 2 bit đều là 1 thì kết quả là 1, ngược lại là 0. Trong ví dụ này 6 & 30110 & 0011 = 0010, tức là số 2 trong hệ 10.

puts 6 & 3  # prints 2
puts 3 & 6  # prints 2

Toán tử ^ thực hiện phép XOR, nếu 2 bit giống nhau thì kết quả là 0, khác nhau thì là 1. Trong ví dụ này 6 ^ 3 tương đương 0110 ^ 0011 = 0101 là 5 trong hệ 10.

puts 6 ^ 3  # prints 5
puts 3 ^ 6  # prints 5

Toán tử | thực hiện phép OR, nếu một trong 2 bit là 1 thì kết quả là 1, ngược lại nếu cả 2 bit là 0 thì kết quả là 0. Ở đây 6 | 3 tương đương 0110 | 0011 = 0111 là 7 trong hệ 10.

puts 6 | 3  # prints 7
puts 3 | 6  # prints 7

Toán tử << dịch 1 bit sang trái, toán tử >> dịch 1 bit sang phải, ví dụ 6 << 1 tức là dịch 0110 sang 1 bit, thành 1100 tức 12 trong hệ 10, nếu tiếp tục dịch 12 << 1 thì được 11000 tức là 24 trong hệ 10. Nghĩa là phép dịch bit trái sẽ nhân số đó lên 2 lần. Tương tự phép dịch bit phải chia số đó cho 2.

puts 6 << 1  # 12
puts 1 << 6 # 64 puts 6 >> 1  # 3
puts 1 >> 6  # 0

Toán tử hợp

Toán tử hợp ở đây nghĩa là gộp các toán tử với toán tử gán = để thực hiện tính toán và gán giá trị luôn.

Ví dụ:

a = 0

a = a + 1
a += 1
puts a


b = 0

b = b - 8
b -= 8
puts b

Toán tử += có nghĩa là cộng rồi gán, chẳng hạn a += 1 tức là a + 1 rồi gán vào biến a, phép toán này tương đương với a = a + 1.

Đây là các toán tử hợp có trong Ruby:

-=   *=  **=  /=   %=   &=   |=   <<= >>= 

Mức độ ưu tiên của các toán tử

Mức độ ưu tiên toán tử cho biết trong một biểu thức thì toán tử nào sẽ được thực hiện trước.

3 + 5 * 5  # Result: 28

Ví dụ như trong biểu thức gồm có phép nhân và phép cộng thì phép nhân được thực hiện trước và phép cộng được thực hiện sau.

(3 + 5) * 5  # Result: 40

Để tăng độ ưu tiên thì chúng ta bọc biểu thức trong cặp dấu ngoặc tròn ()Các biểu thức trong cặp dấu ngoặc tròn sẽ được thực thi trước.

9 / 3 * 3   # Result: 9

Các toán tử có độ ưu tiên bằng nhau sẽ được thực hiện từ trái sang phải, giống hoàn toàn với toán học.

Toán tử phạm vi

Ruby có 2 toán tử phạm vi, dùng để tạo các đối tượng thuộc dạng danh sách trong một phạm vi nào đó, thường là phạm vi số hoặc kí tự.

p (1..3).to_a
p (1...3).to_a

p ('a' .. 'l').to_a

Toán tử i..j tạo danh sách các phần tử từ i tới j, bao gồm cả j, toán tử i...j tạo danh sách các phần tử từ i tới j nhưng không bao gồm j, tức là từ i tới j-1.

p ('a' .. 'l').to_a

Chúng ta có thể đưa vào dãy số hoặc dãy kí tự. Phương thức to_a sẽ chuyển danh sách này về kiểu mảng.

[1, 2, 3]
[1, 2]
["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]

Toán tử điều kiện

Toán tử này trả về một trong 2 biểu thức tùy thuộc vào giá trị biểu thức điều kiện mà chúng ta đề ra. Cú pháp:

biểu thức điều kiện ? biểu thức 1 : biểu thức 2

Có pháp trên có nghĩa là nếu biểu thức điều kiệntrue thì thực hiện biểu thức 1, nếu false thì thực hiện biểu thức 2. Ví dụ:

age = 32

adult = age >= 18 ? true : false

if adult then
    puts "Adult"
else
    puts "Not adult"
end

Trong ví dụ trên chúng ta in ra các string khác nhau tùy vào giá trị của biến adult.

adult = age >= 18 ? true : false

Biến adult sẽ có giá trị là true nếu biến age lớn hơn 18, ngược lại adultfalse.

Adult

Ruby – String

Trong phần này chúng ta sẽ tìm hiểu kỹ hơn về kiểu dữ liệu String.

String là một chuỗi các kí tự được bọc trong cặp dấu nháy đơn hoặc nháy kép.

puts 'Python'
puts "Ruby"

Trong ví dụ trên chúng ta sử dụng cả 2 loại dấu nháy để tạo string.

Python
Ruby

Nếu bạn muốn in ra cả ký tự dấu nháy thì có 2 cách:

puts "There are many stars" 
puts "He said, \"Which one is your favourite?\""

puts 'There are many stars'
puts 'He said, "Which one is your favourite?"'

Cách thứ nhất là đặt trước dấu nháy ký tự \ và Ruby sẽ in ra dấu nháy đó. Cách thứ 2 là trộn dấu nháy đơn với dấu nháy kép, chẳng hạn như chúng ta bọc string bằng cặp dấu nháy đơn và bên trong chúng ta cho in ra dấu nháy kép.

There are many stars.
He said, "Which one is your favourite?"

Ký tự thoát

Ký tự thoát là các ký tự đặc biệt dùng để điều khiển string chứ không được in ra màn hình.

puts "one\ntwo\nthree\nfour"
puts "bbb\raaa" 
puts "Joan\b\b\bane" 
puts "Towering\tinferno" 

Kí tự \n có nghĩa là xuống dòng, bất kì kí tự nào nằm sau \n đều được xuống dòng. Kí tự \r đưa dấu nháy trên màn hình về vị trí đầu dòng.  Kí tự \t cách một đoạn dài bằng một dấu tab giống như khi chúng ta gõ nút tab, kí tự \b xóa một kí tự đứng trước nó.

one
two
three
four
aabbb
Jane
Towering      inferno

Nếu muốn in ra cả ký tự \ thì chúng ta đưa vào là \\.

puts "Escape character \\"
Escape character \

Lấy từng phần tử của string

Chúng ta có thể lấy từng phần tử của string.

msg = "Ruby language"

puts msg["Ruby"]
puts msg["Python"]

puts msg[0]
puts msg[-1]

puts msg[0, 3]
puts msg[0..9]
puts msg[0, msg.length]

Để có thể lấy các phần tử của string thì chúng ta sử dụng cặp dấu ngoặc vuông [] vừa đưa vào bên trong đó chỉ số, khoảng hoặc một string khác.

puts msg["Ruby"]

Khi đưa vào một string bên trong cặp dấu [] thì Ruby sẽ tìm chuỗi đó trong chuỗi gốc, nếu tìm thấy thì in ra chuỗi, nếu không thì in ra chuỗi rỗng.

puts msg[0]

Nếu đưa vào một con số nào thì chúng ta được kí tự tại vị trí đó, chuỗi trong Ruby được đánh số từ 0.

puts msg[-1]

Ruby cũng hỗ trợ chỉ số âm, đưa vào số âm thì sẽ được kí tự từ cuối string.

puts msg[0, 3]

Khi đưa vào 2 chữ số cách nhau bởi dấu phẩy, ví dụ [i, n] thì chúng ta lấy được n kí tự từ vị trí i.

puts msg[0..9]

Chúng ta cũng có thể đưa vào một khoảng giá trị, [0..9] sẽ lấy về chuỗi kí tự từ vị trí 0 đến vị trí 9.

puts msg[0, msg.length]

Phương thức length trả về độ lớn của chuỗi nên dòng trên có nghĩa là lấy toàn bộ chuỗi.

Ruby

R
e
Rub
Ruby langu
Ruby language

Truyền biến vào string

Chúng ta có thể truyền giá trị của các biến khác vào string để hiển thị, biến được truyền vào có dạng #{<tên biến>}. Ví dụ:

#!/usr/bin/ruby

name = "Jane"
age = 17

puts "#{name} is #{age} years old"

Giá trị của biến nameage sẽ được hiển thị tương ứng.

Jane is 17 years old

Chúng ta cũng có thể thực hiện các biểu thức với các biến được truyền vào:

x = 5
y = 6

puts "#{x} x #{y} = #{x*y}"
5 x 6 = 30

Nối chuỗi

Nối chuỗi tức là tạo một chuỗi từ các chuỗi con.

lang = "Ruby" + " programming" + " languge"
puts lang

lang = "Python" " programming" " language"
puts lang

lang = "Perl" << " programming" << " language"
puts lang

lang = "Java".concat(" programming").concat(" language")
puts lang

Có rất nhiều cách để nối một chuỗi. Để nối một chuỗi thì chúng ta có thể dùng toán tử cộng +, viết 2 chuỗi đứng liền nhau, dùng toán tử <<,  hoặc dùng phương thức concat().

Ruby programming languge
Python programming language
Perl programming language
Java programming language

So sánh chuỗi

Ruby hỗ trợ một số toán tử và phương thức giúp so sánh chuỗi một cách dễ dàng.

puts "12" == "12"
puts "aa" == "ab"
puts "Ruby".eql? "Jan"

Chúng ta có thể so sánh chuỗi bằng toán tử == hoặc phương thức eql?. Kết quả trả về True nếu 2 chuỗi giống nhau và ngược lại.

true
false
false

Ngoài ra Ruby còn có toán tử <==> dùng để so sánh chuỗi, toán tử này không trả về True hay False mà trả về 1, 0, -1:

  • 1: chuỗi bên trái lớn hơn chuỗi bên phải
  • -1: chuỗi bên trái bé hơn chuỗi bên phải
  • 0: hai chuỗi bằng nhau

Chuỗi a được gọi là bé hơn chuỗi b nếu kí tự tương ứng của chuỗi a có thứ tự cao hơn kí tự tương ứng bên chuỗi b trong bảng mã ASCII. Ví dụ, khi so sánh "a" <==> "b" thì kí tự a nằm trước kí tự b trong bảng mã ASCII nên a sẽ lớn hơn b. Nếu 2 kí tự bằng nhau thì Ruby sẽ tiếp tục so sánh kí tự tiếp theo của 2 chuỗi cho đến hết.

puts "a" <==> "b"
puts "b" <==> "a"
puts "a" <==> "a"
-1
1
0

Các phương thức trong String

String cũng là một đối tượng trong Ruby do đó các đối tượng string có các phương thức hữu ích mà chúng ta có thể sử dụng.

Ví dụ 1:

word = "Methods"

puts "Size of #{word}: #{word.size}"

puts word.include? "tho"
puts word.include? "od"

puts

puts word.empty?
word.clear
puts word.empty?

Trong đoạn code trên chúng ta sử dụng 4 phương thức của đối tượng string.

puts "Size of #{word}: #{word.size}"

Phương thức size lấy số lượng kí tự có trong string.

puts word.include? "tho"

Đoạn code trên dùng phương thức include?, phương thức này cho biết chuỗi “tho” có nằm trong chuỗi “Methods” hay không.

puts word.empty?
word.clear

Phương thức empty? cho biết string có rỗng hay không, phương thức clear sẽ xóa toàn bộ string.

Size of Methods: 7
true
true

false
true

Ví dụ 2:

ruby = "Ruby"

puts ruby.upcase
puts ruby.downcase
puts ruby.capitalize
puts ruby.swapcase

Ruby có 4 phương thức để chuyển kí tự qua lại giữa chữ hoa với chữ thường. Phương thức upcase chuyển toàn bộ string thành in hoa. Phương thức downcase là chuyển thành chữ in thường. Phương thức capitalize viết hoa chữ cái đầu trong string, còn lại viết thường. Phương thức swapcase chuyển kí tự hoa thành thường và ngược lại.

RUBY
ruby
Ruby
rUBY

Ví dụ 3:

str1 = "ruby.com"
str2 = "python.com"

puts str1.start_with? "ruby"
puts str2.start_with? "ruby."

puts str1.end_with? ".com"
puts str2.end_with? ".com"

Trong ví dụ trên, phương thức start_with? cho biết chuỗi str1str2 có bắt đầu bằng chuỗi “ruby” hay không. Ngược lại, phương thức end_with? cho biết chuỗi có kết thúc bằng chuỗi “.com” hay không.

true
false
true
true

Ví dụ 4:

msg = "Ruby\nPython"

puts msg
puts msg.inspect

Phương thức inspect sẽ in các ký tự thoát ra luôn chứ không dùng để điều khiển chuỗi nữa.

Ruby
Python
"Ruby\nPython"

Định dạng chuỗi

Định dạng chuỗi là hiển thị chuỗi theo nhiều cách khác nhau bằng cách chèn các chuỗi đặc tả. Chuỗi đặc tả có kí tự bắt đầu là kí tự % được đặt bên trong cặp dấu nháy đơn hoặc nháy kép cùng với chuỗi gốc.

Chuỗi đặc tả có dạng sau đây:

%[cờ][độ lớn][độ chính xác]<tên đặc tả>

Trong đó cờ, độ lớnđộ chính xác là các tham số tùy chọn, không có cũng được. Tên đặc tả là kiểu hiển thị của dữ liệu. Chúng ta sẽ xem các ví dụ dưới đây.

Ví dụ 1:

puts "There are %d oranges in the basket." % 12
puts "There are %d oranges and %d apples in the basket." % [12, 10]
puts "Speed: %f km/h" % 62.1
puts "Name: %s" % "iPhone 5"

Khi chúng ta đặt %d bên trong chuỗi thì khi dịch Ruby sẽ hiểu là phải đưa một số nguyên vào vị trí đó thay thế cho chuỗi %d. Các tham số sẽ được đưa vào sau dấu % phía sau chuỗi. Ngoài ra chúng ta cũng có thể đưa vào nhiều tham số bằng cách đặt chúng trong cặp dấu [] và ngăn cách nhau bởi dấu phẩy.

Đặc tả %d hiển thị số nguyên, %f là số chấm động, %s là hiển thị một chuỗi.

There are 12 oranges in the basket.
There are 12 oranges and 10 apples in the basket.
Speed: 62.100000 km/h
Name: iPhone 5

Ví dụ 2:

puts "%d" % 300
puts "%x" % 300
puts "%o" % 300
puts "%b" % 300
puts "%e" % (5/3.0)

Kiểu số nguyên có thể được hiển thị trên nhiều hệ cơ số khác nhau. Chẳng hạn %d là hệ 10, %x là hệ 16, %o là hệ 8, %b là hệ nhị phân (hệ 2), %e là hiển thị theo kiểu số mũ.

300
12c
454
100101100
1.666667e+00

Tham số độ chính xác là một con số đứng giữa kí tự % và kí tự đặc tả, tham số này có nhiều ý nghĩa khác nhau với từng kiểu dữ liệu khác nhau.

Đối với số nguyên thì độ chính xác là số lượng chữ số được hiển thị, nếu giá trị không đủ thì Ruby sẽ tự động chèn thêm các chữ số 0 vào trước, mặc định thì tham số này là 1 tức là không chèn thêm vào.

Đối với số thực thì là số lượng chữ số được hiển thị nằm sau phần thập phân.

Đối với kiểu chuỗi thì độ chính xác có nghĩa là số lượng kí tự cần được hiển thị.

Ví dụ 3:

puts 'Height: %f %s' % [177.3, 'cm']
puts 'Height: %.1f %s' % [177.3, 'cm']

puts "%d" % 16
puts "%.5d" % 16

puts "%s" % "Ruby"
puts "%.5s" % "Python"

Trong ví dụ trên, %.1f tức là hiển thị 1 chữ số sau phần thập phân. Mặc định Ruby hiển thị 6 chữ số sau phần thập phân nên nếu không đủ thì Ruby sẽ tự động chèn thêm các số 0 vào trước.

Tương tự, mặc định Ruby hiển thị độ chính xác là 1 với số nguyên, nhưng nếu chúng ta thiết lập là %.5d thì Ruby sẽ tự chèn thêm các số 0 vào trước cho đủ 5 chữ số.

%.5s cũng giống 2 kiểu trên ở chỗ là sẽ chỉ cho phép hiển thị 5 chữ cái, nếu số lượng chữ cái quá nhiều thì các chữ cái sau cùng sẽ bị lược bỏ, nhưng nếu không đủ số lượng thì Ruby cũng chẳng chèn thêm kí tự nào vào.

Height: 177.300000 cm
Height: 177.3 cm
16
00016
Ruby
Pytho

Ví dụ 4: Tham số độ lớn quy định số lượng kí tự tối thiểu cần được hiển thị ra.

puts "%d" % 1
puts "%d" % 12
puts "%d" % 123
puts "%d" % 1234
puts "%d" % 12345

puts "%10d" % 1
puts "%10d" % 12
puts "%10d" % 123
puts "%10d" % 1234
puts "%10d" % 12345

Tham số độ lớn nằm trước dấu chấm trong chuỗi đặc tả. Nếu dữ liệu không đủ số lượng kí tự thì Ruby sẽ tự động chèn thêm các khoảng trống vào phía trước chuỗi, ngoài ra chúng ta cũng có thể ghi số âm và Ruby sẽ đưa các kí tự khoảng trống vào phía sau chuỗi.

1
12
123
1234
12345
         1
        12
       123
      1234
     12345

Tham số cờ quy định một số kiểu hiển thị khác mà chúng ta sẽ xem sau đây.

Ví dụ 5:

puts "%010d" % 1
puts "%010d" % 12
puts "%010d" % 123
puts "%010d" % 1234
puts "%010d" % 12345

puts "%-10d" % 1
puts "%-10d" % 12
puts "%-10d" % 123
puts "%-10d" % 1234
puts "%-10d" % 12345

Cờ 0 sẽ chèn thêm một số lượng số 0 vào trước số thay vì chèn khoảng trống. Cờ dấu trừ “-” sẽ canh lề trái các chữ số.

0000000001
0000000012
0000000123
0000001234
0000012345
1 
12 
123 
1234 
12345

Ngoài ra còn có các cờ khác như dấu *, +, #, b, d, u, x…. bạn có thể tìm hiểu thêm tại đây.