Category Archives: Ruby

Rails – Tạo project Rails

Trong phần này chúng ta sẽ tìm hiểu cách tạo một project Rails.

Tạo project

Để tạo project thì bạn mở Command Prompt (cmd) lên rồi dùng lệnh cd để chuyển đến thư mục mà bạn muốn tạo, sau đó gõ lệnh rails new <tên project> là được, ví dụ:

C:\Project\Rails>rails new Example
    create 
    create READNE.rdoc
    create Rakefile
    create config.ru
    create .gitignore
    create Gemfile
    create app
    create app/assests/javascripts/application.js
    create app/assests/stylesheets/application.css
    create app/controllers/application_controller.rb
    create app/helpers/application_helper.rb
    ...
    ...
       run bundle install
Fetching gem metadata from http://rubygems.org/.........
Fetching version metadata from http://rubygems.org/........
Fetching dependency metadata from http://rubygems.org/........
Resolving dependencies......
Installing rake 11.3.0
...

Lệnh rails new Example sẽ tạo một thư mục có tên Example và tạo các file với thư mục con cần thiết trong đó, đây là các file và thư mục của một project Rails.

Nếu bạn chạy lệnh mà báo lỗi “The system cannot find the file specified” thì bạn vào thư mục Ruby2.2.0/bin trong thư mục cài đặt Rails, sau đó tìm trong tất cả các file có đuôi .bat dòng này:

@"C:\Users\emachnic\GitRepos\railsinstaller-windows\stage\Ruby2.2.0\bin\ruby.exe"

Và thay bằng dòng này:

@"%~dp0ruby.exe"

Sau đó bạn chạy lại lệnh rails new.

Ngoài ra còn có một lỗi khác cũng có thể xuất hiện, khi chạy lệnh rails new, bạn để ý các dòng đầu tiên là create <tên file> hoặc <tên thư mục>, sau đó sẽ là dòng run bundle install, dòng này có nghĩa là rails đang cài các thư viện cần thiết đi kèm, sau đó sẽ là các dòng Fetching…. nhưng nếu của bạn không có các dòng này mà thay bằng các dòng tương tự như:

Fetching source index from https://rubygems.org/
Retrying fetcher due to error (2/4): Bundler::Fetcher::CertificateFailureError Could 
not verify the  SSAL certificate for https://rubygems.org/
There is a chance you are experiencing a man-in-the-middle attack, but most likely 
your system doesn't have the CA certificates needed for verification. For information
about OpenSSL certificates, see http://bit.ly/ruby-ssl. To connect without using SSL, 
edit your Gemfile sources and change 'https' to 'http'

Đây là các dòng thông báo lỗi, bạn có thể sửa bằng một cách thủ công vào mở toàn bộ file có tên Gemfile (không có phần mở rộng) trong thư mục cài đặt Rails, rồi tìm dòng sources https://rubygems.org/ và đổi thành sources http://rubygems.org/ (bỏ kí tự ‘s’ sau ‘http’). Mặc dù cách này khá thủ công nhưng cách này chắc chắn 100% thành công.

Ngoài ra còn có một cách sửa khác nhưng sẽ không đảm bảo thành công ở thời điểm hiện tại, đó là bạn chạy lệnh gem update --system để cập nhật phần mềm gem là được, (gem là phần mềm quản lý gói của Rails), tuy nhiên khi chạy lệnh này bạn có thể gặp lỗi sau:

SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate 
verify failed

Bạn có thể sửa bằng cách lên trang https://rubygems.org/pages/download tải phiên bản gem mới nhất về, sau đó giải nén, bạn sẽ được một thư mục chứa bộ mã nguồn cài đặt gem, trong đó có một file tên là setup.rb, bạn mở Command Prompt lên trỏ đến thư mục đó (bằng lệnh cd) rồi chạy lệnh ruby setup.rb --help là xong.

Bây giờ bạn có thể chạy lại lệnh rails new để tạo project, tuy nhiên hiện tại một lần nữa nếu bạn vẫn còn thấy báo lỗi SSL thì lần này bạn chỉ còn có thể sửa bằng cách thay đổi đường dẫn https://rubygems.org/ thành http://rubygems.org/ như đã nói ở trên.

Chạy project

Trong thư mục của project có rất nhiều file, chúng ta sẽ tìm hiểu sau. Bây giờ để chạy một project thì bạn mở Command Prompt lên rồi chuyển đến thư mục đó, sau đó gõ lệnh rails server là được:

C:\Project\Rails\Example>rails server
=> Booting WEBrick
=> Rails 4.2.5.1 application starting in development on http://localhost:3000
=> Run 'rails server -h' for more startup options
=> Ctrl-C to shutdown server
[2016-10-25 09:58:49] INFO WEBrick 1.3.
[2016-10-25 09:58:49] INFO ruby 2.2.4 (2015-12-16) [i386-mingw32]
[2016-10-25 09:58:49] INFO WEBrick::HTTPServer#start: pid=14844 port=3000

Mỗi project rails sẽ tự chạy một web server cho riêng nó, khác với các công nghệ như PHP, ASP.NET, Java… là bạn sẽ phải cài một webserver riêng như Apache, NginX hay IIS…

Chúng ta đã chạy một web server trên cổng 3000, bạn có thể mở trình duyệt và trỏ đến URL http://localhost:3000 hoặc http://127.0.0.1:3000 để xem thông báo của server.

capture

Để tắt server thì bạn có thể dùng tổ hợp phím Ctrl+C hoặc đơn giản là tắt Command Prompt luôn 🙂

Tùy chỉnh project

Rails được xây dựng dựa trên mô hình MVC, về cơ bản thì khi chúng ta mở trình duyệt và nhập vào URL của server, thì trình duyệt sẽ gửi một yêu cầu HTTP đến server, server nhận được yêu cầu đó và sẽ gửi vào một Controller nào đó và gọi phương thức xử lý ở đó. Tiếp theo Controller sẽ gọi một View để trả về nội dung HTML cho trình duyệt. Chúng ta sẽ tìm hiểu thêm về kiến trúc này sau.

Bây giờ chúng ta sẽ tạo một Controller và một View mẫu và hiển thị dòng chữ Hello World.

Chúng ta mở một Command Prompt khác lên rồi chuyển đến thư mục của project, sau đó gõ lệnh rails generate controller để tạo một controller, lệnh này có cú pháp như sau:

rails generate controller <tên controller> <tên route 1> <tên route 2>....<tên route n>

Ví dụ:

C:\Project\Rails\Example>rails generate controller Say hello goodbye
    create app/controllers/say_controller.rb
     route get 'say/goodbye'
     route get 'say/hello'
    invoke erb
    create   app/views/say
    create   app/views/say/hello.html.erb
    create   app/views/say/goodbye.html.erb
    invoke test_unit
    create   test/controllers/say_controller_test.rb
    invoke helper
    create   app/helpers/say_helper.rb
    invoke   test_unit
    invoke assets
    invoke   coffee
    create     app/assets/javascripts/say.coffee
    invoke   scss
    create     app/assets/stylesheets/say.scss

Ở đây chúng ta tạo một controller có tên là Say, trong đó có 2 hàm route là hellogoodbye. Bạn có thể mở thư mục app\controllers trong thư mục gốc của project là sẽ thấy một file có tên say_controller.rb, trong file này có nội dung như sau:

class SayController < ApplicationController
    def hello
    end

   def goodbye
    end
end

Ở đây chúng ta có lớp SayController kế thừa từ lớp ApplicationController, lớp này có 2 phương thức là hellogoodbye. Nếu bạn chưa từng học qua mô hình MVC thì ở đây bạn có thể hiểu nôm na một Controller là một file .rb, trong đó có nhiều hàm route, hàm route là các hàm xử lý một đường dẫn URL cụ thể.

Nếu bạn đã từng làm việc với Django hay Node.js thì bạn sẽ cần phải gọi các hàm cấu hình tương ứng cho từng URL tới từng hàm route cụ thể. Trong Rails cũng vậy, mặc định khi tạo một controller thì Rails đã định nghĩa cách các URL được chuyển đến các hàm route rồi. Chẳng hạn ở đây chúng ta có controller là Say và hàm routing hellogoodbye trỏ đến các URL tương ứng là http://localhost:3000/say/hellohttp://localhost:3000/say/goodbye. Chúng ta sẽ tìm hiểu về cách routing sau.

capture

Hiện tại hàm hello trong lớp SayController chẳng làm gì cả, trang web được hiển thị như hình trên là nhờ vào file View, đó là file hello.html.erb  trong thư mục app/views/say.

<h1>Say#hello</h1>
Find me in app/views/say/hello.html.erb

Về bản chất thì các file này chỉ chứa code HTML, CSS… và một loại code đặc biệt nữa của riêng Rails dùng để kết nối giữa controller và view. Để ví dụ thì chúng ta sửa lại đoạn code trên như sau:

<h1>Hello world</h1>
<p>
    Current time: <%= Time.now %>
</p>

Loại code mà chúng ta đang nói đến ở đây được gọi chung là template, bạn để ý trong đoạn code trên, ngoài các thẻ h1, p của HTML thì có một thẻ khác là <%= %>, những gì nằm trong cặp thẻ này sẽ được dịch bằng trình thông dịch Ruby, Ruby sẽ dịch đoạn code trong thẻ này và chuyển đổi thành các chuỗi bình thường. Chẳng hạn như <%= Time.now %> sẽ được dịch thành chuỗi dạng như sau: 2016-10-25 10:43:12 +0700

Chúng ta lưu lại file này sau đó tải lại trang /say/hello sẽ thấy kết quả dạng như sau:

capture

Tuy nhiên trên thực tế thì chúng ta không gọi hàm trực tiếp như vậy, ma thay vào đó là ở trong hàm route sẽ tính toán một biến nào đó, rồi trong file view sẽ tham chiếu đến giá trị của biến đó. Ví dụ chúng ta sửa đoạn code trong file say_controller.rb như sau:

class SayController < ApplicationController
    def hello
        @time = Time.now
    end

    def goodbye
    end
end

Ở đây chúng ta lấy thời gian hiện tại của máy và lưu vào biến @time, nếu bạn chưa biết thì ký tự @ chỉ định biến time là biến instance, biến này chỉ thấy được trong từng đối tượng, nói cho dễ hiểu thì bạn có thể nghĩ biến này có phạm vi truy xuất tương tự như private trong các ngôn ngữ hướng đối tượng khác.

Tiếp theo chúng ta sửa lại file hello.html.erb như sau:

<h1>Hello world</h1>
<p>
    Current time: <%= @time %>
</p>

Lần này thay vì gọi trực tiếp hàm Time.now, chúng ta gọi đến biến @time trong lớp controller tương ứng.

Rails – Giới thiệu

Ruby on Rails (viết ngắn gọn là Rails) là một web framework được viết bằng ngôn ngữ Ruby. Rails được giới thiệu lần đầu vào năm 2004 bởi một lập trình viên người Đan Mạch là David Heinemeier Hansson, và sau đó đã nhanh chóng phát triển trở thành một trong những web framework phổ biến. Một số công ty lớn sử dụng Rails là Airbnb, Base-camp, Github, Kickstarter, Shopify…

Rails có những đặc điểm sau:

  • Là một framework theo mô hình MVC
  • Fullstack, bạn có thể làm tất cả mọi thứ với Rails
  • Rails cho phép lập trình viên viết code ít hơn
  • Có 3 môi trường lập trình là môi trường phát triển, thử nghiệm và môi trường triển khai

Cài đặt Rails trên Windows

Để có thể dùng Rails thì bạn phải cài sẵn trước Ruby trên máy đã, nếu bạn chưa có thì lên trang http://rubyinstaller.org để tải và cài đặt.

Sau đó để cài đặt Rails trên Windows thì bạn tải trình Installer về và cài đặt tại địa chỉ http://railsinstaller.org/. Bạn lưu ý tải đúng phiên bản Rails hỗ trợ với phiên bản Ruby mà mình đã cài.

Sau đó trong quá trình cài thì bạn nhớ check tùy chọn thêm đường dẫn đến thư mục cài đặt Rails vào biến môi trường PATH để tiện sử dụng sau này.

capture

Xem phiên bản Rails

Sau khi đã cài xong, bạn có thể xem phiên bản Rails mà mình vừa cài cũng như kiểm tra xem Rails đã được cài đặt thành công chưa bằng cách mở Command Prompt (cmd) lên và gõ lệnh rails -v hoặc rails --version.

rail_version

 

Ở đây mình sử dụng phiên bản 4.2.5.1.

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