Non-Alphanumeric Ruby

Đã bao giờ bạn nghĩ sẽ viết code mà không dùng tới số và chữ. Thật khó mà thực hiện được điều đó, càng khó hơn khi chúng ta cần viết các thuật toán xử lý phức tạp. Thông qua cuộc thi CTF vừa rồi, bằng googling, mình đã tìm được một bài viết nói về việc không sử dụng chữ và số để viết code. Sau đây xin chia sẻ lại cùng mọi người bài viết Non-Alphanumeric Ruby for Fun and Not Much Else và việc mình thử áp dụng vào để xử lý một phần của bài toán trong CTF. Ngôn ngữ được sử dụng là ruby.

Bài viết lấy cảm hứng từ bài viết Programming with Nothing của tác giả Tom Stuart, là sức mạnh của ngôn ngữ ruby như thế nào nếu chúng ta loại bọ hết các kiểu dữ liệu trừ Proc và tất cả những features trừ Proc.newProc#call? Và để kiểm chứng cũng nhữ kiểm tra giới hạn sức mạng có thể của ngôn ngữ ruby, tác giả đã thực nghiệm và phát hiện ra có thể viết chương trình ruby mà không cần sử dụng kỹ tự chữ và số. Trong bài viết này, chúng ta sẽ được tác giả chia sẻ về cách tiếp cận bài toán để viết Fizzbuzz.

Tất nhiên là chúng ta sẽ không thể có được những con số mà không gõ rõ ràng những con số. Chúng ta sẽ phải tạo ra chúng, nhưng hãy xem, Ruby đã đem lại cho chúng ta điều gì từ global variables lúc bắt đầu chương trình của chúng ta.

global_variables.select { |g| Fixnum === eval(g.to_s) }
# [:$SAFE, :$., :$$, $-W]

Không nhiều, nhưng chúng ta có thể sử dụng nó. $SAFE$-W là 0, 1. $. là 100 và $$ là process ID. Chúng ta có thể tạo ra các số khác bằng cách chia chúng với nhau.

Vậy là chúng ta đã có các số, nhưng không thể lữu trữ chúng.Tuy nhiên, có thể sử dụng các ký tự gạch chân và có thể kết hợp làm prefix với @ hoặc $ để tạo ra các biến instanceglobal. Từng ấy là đủ, bên cạnh đó chúng ta cũng có thể sự dụng sự bất thường của biến $-$-_ để gán khi cần thiết:

_   = $$ / $$ #  1
__  =  _ -  _ #  0
@_  =  _ +  _ #  2
$_  = @_ +  _ #  3
$-  = @_ + $_ #  5
$-_ = $- * $_ # 15

Việc tạo ra các con số không còn khó khăn nữa, nên chúng ta sẽ thực hiện in ra Fizz, Buzz, hoặc kết hợp giữa chúng. Bằng cách sử dụng toán tử <<, chúng ta sẽ thực hiện kết hợp các string lại với nhau:

"a" << "b" << "c"
=> "abc"

Bằng cách kết hợp các mã unicode chúng ta cũng có

'' << 97 << 98 << 99

Đơn giản để tạo ra cái chúng ta muốn:

z    = '' << 122
fizz = '' << 70 << 105 << z << z
buzz = '' << 66 << 117 << z << z

Thực hiện gán biến cho những biểu thức chúng ta đã định nghĩa:

@__  = '' << 15 * (5 + 3) + 2
$___ = '' << 15 * 5 - 5 << 15 * (5 + 2) << @__ << @__
@___ = '' << 15 * 5 - 3 * 3 << 15 * (5 + 3) - 3 << @__ << @__

Cuối cùng thay thế bởi những biến chứa giá trị tương ứng:

@__  = '' << $-_ * ($- + $_) + @_
$___ = '' << $-_ * $- - $- << $-_ * ($- + @_) << @__ << @__
@___ = '' << $-_ * $- - $_ * $_ << $-_ * ($- + $_) - $_ << @__ << @__

Chúng ta đã hoàn thành việc in ra FizzBuzz, nhưng làm thế nào để thực hiện vòng lặp từ 1 đến 100? Sự dụng để quy, đó là cách tiếp cận trong Programming with Nothing cái mà tôi đã nhắc ở trên. Chúng ta sẽ định nghĩa một lambda, bỏ qua yêu cầu không sử dụng chữ và số:

n = 0

fizzbuzz = -> {
  n += 1
  puts n % 15 == 0 ? 'FizzBuzz'
    :  n %  3 == 0 ? 'Fizz'
    :  n %  5 == 0 ? 'Buzz'
    :  n
  n < 100 ? fizzbuzz[] : 0
}

fizzbuzz[]

Logic thì khá đơn giản, chúng ta sẽ bắt đầu từ 0, tăng giá trị lên và in ra giá trị sau mỗi lần lặp. Chúng ta có thể gọi tới lambda ngay trong chính nội tại của nó, sử dụng #[] như là một alias của #call. Sau khi thay thế:

___ = -> {
  $. += _
  puts $. % $-_ == __ ? $___ + @___
    :  $. % $_  == __ ? $___
    :  $. % $-  == __ ? @___
    :  $.
  $. < 100 ? ___[] : __
}

___[]

Mọi thứ thì ok, tuy nhiên chúng ta cần thay thế puts method, thật may khi Ruby có hỗ trợ $> là một alias của $stdout. Viết lại bao gồm những bổ sung như sau:

_   = $$  / $$ #  1
__  =  _  -  _ #  0
@_  =  _  +  _ #  2
$_  = @_  +  _ #  3
$__ = @_  + $_ #  5
$-_ = $__ * $_ # 15

@__  = '' << $-_ * ($__ + $_) + @_ # z
$___ = '' << $-_ * $__ - $__ << $-_ * ($__ + @_) << @__ << @__ # Fizz
@___ = '' << $-_ * $__ - $_ * $_ << $-_ * ($__ + $_) - $_ << @__ << @__ # Buzz

(___ = -> { # the fizzbuzz lambda
   $. += _   # increment n
   $> << ($. % $-_ == __ ? $___ + @___ # "FizzBuzz" if mod-15
        : $. % $_  == __ ? $___        # "Fizz" for 3
        : $. % $__ == __ ? @___        # "Buzz" for 5
        : $.) <<                       # Otherwise, n
        ('' << $__ * @_)               # and a newline
   $. < ($__ * @_) ** @_ ? ___[] : _   # Check n against 100.
 })[] # Immediately invoke the lambda.

Sau khi refactor lại:

_=$$/$$;__=_-_;@_=_+_;$_=@_+_;$__=@_+$_;$-_=$__*$_
@__ =''<<$-_*($__+$_)+@_
$___=''<<$-_*$__-$__<<$-_*($__+@_)<<@__<<@__
@___=''<<$-_*$__-$_*$_<<$-_*($__+$_)-$_<<@__<<@__
(___=->{$.+=_;$><<($.%$-_==__ ?$___+@___:$.%$_==__ ?$___:$.%
$__==__ ?@___:$.)<<(''<<$__*@_);$.<($__*@_)**@_?___[]:_})[]

Bên cạnh việc in ra FizzBuzz ở trên, tác giả còn cung cấp một repo chứa rất nhiều đoạn code tham khảo

Một ví dụ cho số fibonaci:

$_ = $$ / $$ # a = 1
@_ = $_      # b = 1
$. = $_ + $_ + $_  #  3 for 10
$. *= $.; $. += $_ # 10 for newline

($-_ = -> {
  $> << $_ << ('' << $.) # Output a.
  $_ += @_ # Add b to a.
  $> << @_ << ('' << $.) # Output b.
  @_ += $_ # Add a to b.
  $-_[] # Repeat ad infinitum.
})[]

Rất thú vị phải không các bạn! Hi vọng các bạn sẽ có thời gian funny với cách viết code này của Ruby.

Tham khảo globale variables của Ruby