Skip to main content

Nghệ Thuật Tối Thượng về Mảng trong Ruby: Bí Kíp Chỉ Dành Cho Chuyên Gia

· 11 min read
Dang Quang Minh
Nhân viên oánh máy tính

Ruby Arrays Expert

Bạn đã thành thạo tất cả các kỹ thuật cơ bản, trung cấp và nâng cao về mảng trong Ruby? Đã đến lúc khám phá những bí kíp tối thượng, những kỹ thuật chỉ dành cho các chuyên gia Ruby thực thụ. Bài viết này sẽ đưa bạn đến những ranh giới xa nhất của việc làm việc với mảng trong Ruby, nơi hiệu suất, tính linh hoạt và sự thanh lịch của code được đẩy đến giới hạn.

🧵 Xử Lý Song Song với Threads

Tận dụng sức mạnh của đa luồng để xử lý mảng lớn một cách hiệu quả:

require 'thread'
require 'etc'

class Array
def parallel_map(pool_size: Etc.nprocessors)
return [] if empty?

queue = SizedQueue.new(pool_size)
results = Array.new(size)
mutex = Mutex.new

# Tạo nhóm worker threads
workers = pool_size.times.map do
Thread.new do
while (item = queue.pop rescue nil)
value, idx = item
result = yield(value)
mutex.synchronize { results[idx] = result }
end
end
end

# Đưa công việc vào hàng đợi
each_with_index { |elem, i| queue << [elem, i] }

# Đóng hàng đợi và chờ các worker hoàn thành
pool_size.times { queue.close }
workers.each(&:join)

results
end
end

# Ví dụ sử dụng
def xu_ly_phuc_tap(n)
sleep(0.1) # Giả lập xử lý tốn thời gian
n * n
end

# Xử lý tuần tự - mất khoảng 1 giây
start = Time.now
ket_qua_tuan_tu = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map { |n| xu_ly_phuc_tap(n) }
puts "Xử lý tuần tự: #{Time.now - start} giây"

# Xử lý song song - nhanh hơn nhiều
start = Time.now
ket_qua_song_song = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].parallel_map { |n| xu_ly_phuc_tap(n) }
puts "Xử lý song song: #{Time.now - start} giây"

🔒 Deep Freeze - Bảo Vệ Mảng Lồng Nhau Khỏi Thay Đổi

Tạo mảng bất biến hoàn toàn, bao gồm cả các mảng con và đối tượng bên trong:

class Array
def deep_freeze
each { |element| element.deep_freeze if element.respond_to?(:deep_freeze) }
freeze
end
end

class Hash
def deep_freeze
each_value { |value| value.deep_freeze if value.respond_to?(:deep_freeze) }
freeze
end
end

class Object
def deep_freeze
freeze
end
end

# Tạo cấu trúc dữ liệu bất biến hoàn toàn
cau_hinh = {
app_name: "Ruby Master",
version: "1.0.0",
settings: {
timeout: 30,
retries: 3,
endpoints: ["api.example.com", "backup.example.com"]
},
features: ["authentication", "reporting", "analytics"]
}.deep_freeze

# Thử thay đổi sẽ gây ra lỗi
# cau_hinh[:version] = "1.0.1" # => FrozenError
# cau_hinh[:settings][:timeout] = 60 # => FrozenError
# cau_hinh[:settings][:endpoints] << "new.com" # => FrozenError
# cau_hinh[:features][0] = "new-auth" # => FrozenError

🧠 Metaprogramming - Tạo Phương Thức Mảng Động

Sử dụng metaprogramming để tạo các phương thức xử lý mảng một cách động:

class Array
# Định nghĩa các phương thức tìm kiếm động
%w[first last min max].each do |method_prefix|
%w[even odd prime].each do |criteria|
method_name = "#{method_prefix}_#{criteria}"

define_method(method_name) do
case criteria
when 'even'
self.select(&:even?).public_send(method_prefix)
when 'odd'
self.select(&:odd?).public_send(method_prefix)
when 'prime'
require 'prime'
self.select { |n| Prime.prime?(n) }.public_send(method_prefix)
end
end
end
end

# Tạo phương thức tổng hợp động
def method_missing(method_name, *args, &block)
if method_name.to_s =~ /^(sum|product)_of_(.+)$/
operation, property = $1, $2

values = map { |item| item.is_a?(Hash) ? item[property.to_sym] : item.send(property) }

case operation
when 'sum'
values.sum
when 'product'
values.inject(1, :*)
end
else
super
end
end

def respond_to_missing?(method_name, include_private = false)
method_name.to_s =~ /^(sum|product)_of_(.+)$/ || super
end
end

# Sử dụng các phương thức được tạo động
so = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]

so.first_even # => 2
so.last_odd # => 13
so.max_prime # => 13

# Sử dụng với mảng đối tượng
san_pham = [
{ten: "Laptop", gia: 15000000, so_luong: 2},
{ten: "Điện thoại", gia: 8000000, so_luong: 5},
{ten: "Tai nghe", gia: 1500000, so_luong: 10}
]

san_pham.sum_of_gia # => 24500000
san_pham.product_of_so_luong # => 100

🧮 Memoized Inject - Tối Ưu Hóa Tính Toán Lặp Lại

Tạo một phiên bản tối ưu của inject với khả năng ghi nhớ kết quả trung gian:

class Array
def memoized_inject(initial = nil, memo_key = nil)
memo = {}
memo_key ||= ->(acc, item) { [acc, item].hash }

if block_given?
inject(initial) do |acc, item|
key = memo_key.call(acc, item)
memo[key] ||= yield(acc, item)
end
else
inject(initial)
end
end
end

# Tính giai thừa với memoization
def factorial(n)
return 1 if n <= 1
(1..n).memoized_inject(1) { |acc, i| acc * i }
end

# Tính số Fibonacci với memoization
def fibonacci_sequence(n)
return [] if n <= 0
return [0] if n == 1
return [0, 1] if n == 2

result = [0, 1]
memo = {}

(2...n).each do |i|
key = [result[i-1], result[i-2]].hash
memo[key] ||= result[i-1] + result[i-2]
result << memo[key]
end

result
end

puts fibonacci_sequence(100).last # Tính số Fibonacci thứ 100 rất nhanh

🔄 Enumerator Vô Hạn Lười Biếng

Tạo và làm việc với các dãy vô hạn mà không gây tràn bộ nhớ:

# Tạo dãy số Fibonacci vô hạn
fibonacci = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder << a
a, b = b, a + b
end
end.lazy

# Lấy 10 số Fibonacci đầu tiên
fibonacci.take(10).force # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# Lấy các số Fibonacci lớn hơn 1000 và nhỏ hơn 10000
fibonacci
.select { |n| n > 1000 }
.take_while { |n| n < 10000 }
.force # => [1597, 2584, 4181, 6765]

# Tạo dãy số nguyên tố vô hạn
require 'prime'
primes = Enumerator.new do |yielder|
Prime.each { |prime| yielder << prime }
end.lazy

# Tìm 5 số nguyên tố đầu tiên lớn hơn 1000000
primes
.select { |p| p > 1000000 }
.take(5)
.force # => [1000003, 1000033, 1000037, 1000039, 1000081]

🔌 Refinements - Mở Rộng Mảng An Toàn

Sử dụng refinements để mở rộng chức năng của mảng mà không ảnh hưởng đến code khác:

module ArrayExtensions
refine Array do
# Phương thức tìm phần tử phổ biến nhất
def most_frequent
group_by(&:itself)
.transform_values(&:count)
.max_by { |_, count| count }
&.first
end

# Phương thức tính trung bình có trọng số
def weighted_average(weights = nil)
return nil if empty?

if weights.nil?
sum.to_f / size
else
raise ArgumentError, "Weights array must have same size as source array" if weights.size != size

sum { |v| v * weights[index(v)] }.to_f / weights.sum
end
end

# Phương thức chia mảng thành các nhóm có kích thước bằng nhau
def split_into(num_groups)
return [] if empty?

groups = Array.new(num_groups) { [] }
each_with_index { |elem, i| groups[i % num_groups] << elem }
groups
end
end
end

# Sử dụng refinements
using ArrayExtensions

# Tìm phần tử phổ biến nhất
[1, 2, 3, 2, 2, 4, 5, 2, 6, 7, 2].most_frequent # => 2

# Tính trung bình có trọng số
diem = [8.5, 7.0, 9.5]
trong_so = [0.3, 0.2, 0.5]
diem.weighted_average(trong_so) # => 8.55

# Chia mảng thành các nhóm
(1..10).to_a.split_into(3) # => [[1, 4, 7, 10], [2, 5, 8], [3, 6, 9]]

📦 Binary Packing với pack/unpack

Sử dụng packunpack để chuyển đổi giữa mảng và dữ liệu nhị phân:

# Chuyển đổi mảng số nguyên thành dữ liệu nhị phân
so_nguyen = [1, 2, 3, 4, 5]
binary_data = so_nguyen.pack('C*') # 'C' = unsigned char (8-bit)

# Chuyển đổi ngược lại từ dữ liệu nhị phân thành mảng
so_nguyen_moi = binary_data.unpack('C*') # => [1, 2, 3, 4, 5]

# Đóng gói cấu trúc dữ liệu phức tạp
def serialize_point(x, y, z)
[x, y, z].pack('d3') # 'd' = double precision float (64-bit)
end

def deserialize_point(binary)
binary.unpack('d3') # => [x, y, z]
end

# Ứng dụng thực tế: Lưu trữ hiệu quả mảng lớn
def save_large_array(array, filename)
File.open(filename, 'wb') do |file|
# Lưu kích thước mảng
file.write([array.size].pack('N'))

# Lưu dữ liệu
file.write(array.pack('d*'))
end
end

def load_large_array(filename)
File.open(filename, 'rb') do |file|
# Đọc kích thước mảng
size = file.read(4).unpack('N')[0]

# Đọc dữ liệu
file.read(size * 8).unpack('d*')
end
end

🎲 Biểu Diễn Mảng Dưới Dạng BitSet

Sử dụng biểu diễn bit để tối ưu hóa bộ nhớ cho các tập hợp số nguyên:

class BitSet
def initialize(size)
@size = size
@bits = Array.new((size / 32.0).ceil, 0)
end

def [](index)
return nil if index >= @size

word_index = index / 32
bit_index = index % 32

(@bits[word_index] & (1 << bit_index)) != 0
end

def []=(index, value)
return nil if index >= @size

word_index = index / 32
bit_index = index % 32

if value
@bits[word_index] |= (1 << bit_index)
else
@bits[word_index] &= ~(1 << bit_index)
end
end

def to_a
(0...@size).select { |i| self[i] }
end

def union(other)
result = BitSet.new(@size)
@bits.each_with_index do |word, i|
result.instance_variable_get(:@bits)[i] = word | other.instance_variable_get(:@bits)[i]
end
result
end

def intersection(other)
result = BitSet.new(@size)
@bits.each_with_index do |word, i|
result.instance_variable_get(:@bits)[i] = word & other.instance_variable_get(:@bits)[i]
end
result
end
end

# Sử dụng BitSet để biểu diễn tập hợp hiệu quả
max_value = 1_000_000
bitset = BitSet.new(max_value + 1)

# Đánh dấu các số nguyên tố
require 'prime'
Prime.each(max_value) do |prime|
bitset[prime] = true
end

# Kiểm tra số nguyên tố nhanh chóng
bitset[997] # => true
bitset[998] # => false

# Tạo tập hợp các số nguyên tố trong khoảng
primes_under_100 = (0..100).select { |n| bitset[n] }

🧩 Tạo Mảng N Chiều

Tạo và làm việc với mảng đa chiều một cách linh hoạt:

class NDimensionalArray
def initialize(dimensions, default_value = nil, &block)
@dimensions = dimensions.dup

if @dimensions.empty?
@data = default_value
else
current_dim = @dimensions.shift
@data = Array.new(current_dim) do |i|
if block_given?
NDimensionalArray.new(@dimensions.dup, default_value) { |*coords| block.call(i, *coords) }
else
NDimensionalArray.new(@dimensions.dup, default_value)
end
end
end
end

def [](*indices)
return @data if indices.empty?

current_index = indices.shift
if indices.empty?
@data[current_index]
else
@data[current_index][*indices]
end
end

def []=(*indices, value)
if indices.size == 1
@data[indices[0]] = value
else
current_index = indices.shift
@data[current_index][*indices] = value
end
end

def each(&block)
if @data.is_a?(Array)
@data.each_with_index do |subarray, i|
if subarray.is_a?(NDimensionalArray)
subarray.each_with_coords do |value, *coords|
yield value, i, *coords
end
else
yield subarray, i
end
end
else
yield @data
end
end

def each_with_coords(&block)
if @data.is_a?(Array)
@data.each_with_index do |subarray, i|
if subarray.is_a?(NDimensionalArray)
subarray.each_with_coords do |value, *coords|
yield value, i, *coords
end
else
yield subarray, i
end
end
else
yield @data
end
end

def to_a
if @data.is_a?(Array)
@data.map { |item| item.is_a?(NDimensionalArray) ? item.to_a : item }
else
@data
end
end
end

# Tạo ma trận 3x3
matrix = NDimensionalArray.new([3, 3], 0)
matrix[0, 0] = 1
matrix[1, 1] = 5
matrix[2, 2] = 9

# Tạo tensor 3x3x3 với giá trị là tổng các chỉ số
tensor = NDimensionalArray.new([3, 3, 3]) { |i, j, k| i + j + k }

# Truy cập giá trị
tensor[1, 2, 0] # => 3 (1 + 2 + 0)

🔗 Chuỗi Enumerator Tùy Chỉnh

Tạo và kết hợp các enumerator tùy chỉnh để xử lý dữ liệu phức tạp:

module EnumeratorChaining
refine Enumerator do
def chain(other)
Enumerator.new do |yielder|
each { |value| yielder << value }
other.each { |value| yielder << value }
end
end

def zip_with(other)
Enumerator.new do |yielder|
loop do
yielder << [self.next, other.next]
end
rescue StopIteration
nil
end
end

def interleave(other)
Enumerator.new do |yielder|
loop do
yielder << self.next
yielder << other.next
end
rescue StopIteration
nil
end
end

def batch(size)
Enumerator.new do |yielder|
buffer = []

each do |item|
buffer << item

if buffer.size >= size
yielder << buffer
buffer = []
end
end

yielder << buffer unless buffer.empty?
end
end
end
end

using EnumeratorChaining

# Kết hợp các dãy số
fibonacci = Enumerator.new do |yielder|
a, b = 0, 1
loop do
yielder << a
a, b = b, a + b
end
end

squares = Enumerator.new do |yielder|
n = 0
loop do
yielder << n * n
n += 1
end
end

# Kết hợp hai dãy số
combined = fibonacci.lazy.take(10).chain(squares.lazy.take(10))
combined.to_a # => [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# Ghép cặp các phần tử
pairs = fibonacci.lazy.take(10).zip_with(squares.lazy)
pairs.take(5).force # => [[0, 0], [1, 1], [1, 4], [2, 9], [3, 16]]

# Xen kẽ các phần tử
interleaved = fibonacci.lazy.take(5).interleave(squares.lazy.take(5))
interleaved.to_a # => [0, 0, 1, 1, 1, 4, 2, 9, 3, 16]

# Xử lý theo lô
batched = (1..10).each.batch(3)
batched.to_a # => [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

🎯 Kết luận

Những kỹ thuật chuyên gia này đại diện cho trình độ cao nhất trong việc làm việc với mảng trong Ruby. Chúng không chỉ giúp bạn viết code hiệu quả và tối ưu mà còn mở ra những khả năng mới trong việc xử lý dữ liệu phức tạp.

Hãy nhớ rằng, với sức mạnh lớn đi kèm trách nhiệm lớn. Những kỹ thuật này rất mạnh mẽ nhưng cũng có thể làm cho code của bạn trở nên khó hiểu nếu sử dụng không đúng cách. Luôn cân nhắc giữa sự phức tạp và tính dễ đọc của code.

Bạn đã sẵn sàng trở thành bậc thầy về mảng trong Ruby chưa? Hãy thử nghiệm và áp dụng những kỹ thuật này vào dự án của bạn!


Bạn có kỹ thuật xử lý mảng nâng cao nào khác muốn chia sẻ? Hãy để lại bình luận bên dưới nhé!