ลองทำโจทย์ดีกว่า

  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.
  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1005 is not allowed to access /tmp owned by uid 0 in /var/www/sites/sugree/codenone.com/subdomains/www/html/includes/file.inc on line 190.

เห็น python เขาครึกครื้น ส่วน ruby เราชั่งเหงาหงอย
งั้นมาเรียนรู้ ruby ผ่านโจทย์กันดีกว่า

เริ่มด้วย
ลองเขียน method ที่รับ input
[1,2,3,4,5,6,7,8,9]
แล้วให้ผลลัพท์เป็น
[[1,2,3],[4,5,6],[7,8,9]]

โดยจำนวนใน sub array เป็น parameter ที่ระบุได้

(เพื่อให้เกิดการเรียนรู้ข้ามภาษา
python ก็สามารถ submit code ได้ครับ)

sugree's picture

ฝากตรวจการบ้าน Python ด้วยครับ http://www.codenone.com/node/22

เพิ่มเติม: อยากเห็น Erlang จังเลยครับ

veer's picture
def x(l, n)
  ([nil] * ((l.length + n - 1) / n)).collect{|i| l.slice!(0,n) }
end

ทำตั้งนาน :-P (ก็รู้สึกว่ายังไม่เท่ห์เท่าไหร่ แบบมันมีข้างหน้ายาวๆ)

แก้นิดนึง

def x(l, n)
  ([nil] * (l.length / n.to_f).ceil.collect{|i| l.slice!(0,n) }
end

แย่กว่าเดิมเปล่าเนี่ย -_-!

sugree's picture

อธิบายหน่อยดิ

([nil] * (l.length / n.to_f).ceil)

  1. มันได้ออกมาเป็นอะไร list ใน list รึป่ะ

.collect{|i| l.slice!(0,n) }

  1. collect นี่มันทำอะไร
  2. |i| l.slice!(0,n) นี่เอาไว้ทำอะไร
veer's picture

a.collect{|i| func(i) } น่าจะคล้ายๆกับ map(func, a) ของ python ส่วน a.slice(0,n) ก็ประมาณ a[0:n] ของ python

a.slice! ก็คล้าย a.slice แต่ว่า จะแก้ค่าของ a ให้เป็น a[n:] ด้วย

ในที่สุด veer ก็ช่วย post :)
ค่อยยังชั่ว นึกว่าต้องถามเองตอบเองแล้ว

ยกตัวอย่างเพิ่มเติมให้คุณ sugree
method map กับ collect เหมือนกัน

[1,2,3].collect { |i| i + 1 } # => [2,3,4]
[1,2,3].map { |i| i + 1 }     # => [2,3,4]

operator * ถูก define ใน Array class

[nil] * 3     # => [nil, nil, nil]
[1,2,3] * 2   # => [1,2,3,1,2,3]
[1,2,3] * "," # => "1,2,3"

ส่วน slice! กับ slice

a = [1,2,3,4]
a.slice!(0,2) # => [1,2]
a             # => [3,4]
 
a = [1,2,3,4]
a.slice(0,2)  # => [1,2]
a             # => [1,2,3,4]

ลองดู ที่คนอื่นเขาทำ แบบที่ 1 (แสดงว่ายังมีอีกหลายแบบ)
เขา define method /

class Array
  def / len
    inject([]) do |ary, x|
      ary << [] if [*ary.last].nitems % len == 0
      ary.last << x
      ary
    end
  end
end
 
[1,2,3,4] / 2 # => [[1,2],[3,4]]
[1,2,3,4,5,6,7,8,9] / 3 # => [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

สำหรับคนที่ยังไม่คุ้นกับ method inject

# Sum some numbers
(5..10).inject {|sum, n| sum + n }              #=> 45
# Multiply some numbers
(5..10).inject(1) {|product, n| product * n }   #=> 151200
veer's picture

สำหรับแฟนไพธอน inject ก็คงจะคล้ายๆกับ reduce

sugree's picture

ข้อดีของ Ruby ที่เห็นชัดๆ ในนี้ คือมันแก้ Array กันสดๆ เห็นแล้วทึ่งมาก Python หมดสิทธิ์

ว่าแต่ผมยังสงสัยเรื่อง Scope น่ะครับ ผลของการปรับแต่ง Array ครั้งนี้จะ

  1. มีผลเมื่อไหร่ นี่มันยังเป็น Interpreter รึเปล่านี่
  2. แค่ในโมดูล ในคลาส หรือว่าเปลี่ยนทีเดียวทั้งโปรแกรม

กรณี Array ที่เรา add method เข้าไป
ผลของมันจะ effect ทั้ง process ที่กำลังทำอยู่เลยครับ

effect จะเริ่มทันทีหลังจาก evaluate class definition

$ irb
>> class Array
>> def yo
>>  puts 'hi'
>> end
>> end
=> nil
>> [1,2,3].yo
hi
=> nil
>>

มีการ define อีกแบบ ที่มีผลเฉพาะ scope ของ Instance นั้นๆ

>> a = [1,2,3]
=> [1, 2, 3]
>> class << a
>>  def say
>>   puts 'hello'
>>  end
>> end
=> nil
>> a.say
hello
=> nil
>> [1,2,3].say
NoMethodError: undefined method `say' for [1, 2, 3]:Array
        from (irb):15
>>     
sugree's picture

โอ้ ทำได้ทั้งสองแบบ มันน่าทึ่งมาก

ลืมบอกไป คนคิดอันที่ 1 คือคนที่ใช้ชื่อ nickname why ใน Redhanded.hobix.com
ส่วนแบบที่ 2 คน post ชื่อ Yxhuvud
เจ๋งมาก

class Array
  def / len
    each_index do |i|
      self[i,len] = [self[i,len]]
    end
  end
end
veer's picture

อันนี้นี่เห็นแล้วต้องเพ่งซักพัก ;-)

apirak's picture

ผมว่าอันนี้ดูง่ายสุดเลยนะ

อันบนๆ ผมแพ่งนานมั๊กๆ

sugree's picture

เห็นแล้วปิ๊ง ใน Python 2.2 ทำแบบนี้ไม่ได้ แต่ 2.3 เป็นต้นมาก็ทำได้บ้างละน่า

def problem21_5(input,n):
    for i,x in enumerate(input):
        input[i:i+n] = [input[i:i+n]]
    return input

enumerate() จะให้ index และ element ออกมาพร้อมๆ กันจึงมั่นใจได้ว่าจะได้ค่าปัจจุบันเสมอ range() ใช้ไม่ได้เพราะทำงานแค่ครั้งเดียวจริงๆ

คุณ sugree
พรุ่งนี้ผมจะ post solution ที่เป็น erlang ให้ครับ
ผมเก็บ code ใน thumb drive
แต่คุณลูกผมโชมย thumbdrive ไปนอนด้วยเสียนี่
ขอก็ไม่ยอมให้ กำแน่นเชียว

ลืมบอกไป
solution ของคุณ sugree, กับ ruby แบบที่ 1
เป็นวิธีที่ไม่ได้ใช้ destructive method
นั่นคือ state ของ list หลังจากที่เรียกใช้ method
จะยังเหมือนเดิมอยู่
ส่วนวิธีของ veer กับ ruby แบบที่ 2
เป็น destructive method
state ของ list จะเปลี่ยนไปหลังจากถูกเรียกใช้

ที่เขานิยมกัน ก็คือพยายามหลีกเลี่ยง destructive method
เพราะจะทำให้มีโอกาสเกิด bug ในอนาคตได้น้อยกว่า

veer's picture

ขอบคุณครับ มีสรุปเทคนิคให้ด้วย :-D

แบบที่ 3

require 'enumerator'
def sep(list, n)
  list.enum_for(:each_slice,3).to_a
end
sugree's picture

อู๊ย เห็นแล้วใจสลาย

อ่านไม่รู้เรื่องเลย... ท่าจะมีอะไรเล่นเยอะ... ":each_slice"!!!

veer's picture

อันนี้ผมว่าเข้าใจง่ายดี แต่ว่าให้อยู่มานั่งนึกก็คงนึกแบบนี้ไม่ออก :-P

สุดท้าย จบลงด้วย benchmark ว่า 4 แบบข้างบน
อันไหนมี performance ดีกว่ากัน

ทดสอบ benchmark ตัด [1,2,3,4,5,6,7,8,9,10] ด้วย ขนาด sub array = 3
5000 ครั้ง

require 'benchmark'
n = 5000
Benchmark.bm  do |x|
  x.report { n.times {sep_veer([1,2,3,4,5,6,7,8,9,10], 3)} }
  x.report { n.times {sep1([1,2,3,4,5,6,7,8,9,10], 3)} }
  x.report { n.times {sep2([1,2,3,4,5,6,7,8,9,10], 3)} }
  x.report { n.times {sep3([1,2,3,4,5,6,7,8,9,10], 3)} }
end
       user     system      total        real
veer  0.150000   0.000000   0.150000 (  0.094876)
#1    0.316667   0.033333   0.350000 (  0.206514)
#2    0.116667   0.000000   0.116667 (  0.070286)
#3    0.166667   0.016667   0.183333 (  0.104720)

(python เร็วกว่าอยู่แล้ว กรุณาอย่ามาเปรียบเทียบให้ช้ำใจ)

sugree's picture

เกิดความสงสัยทันทีที่เห็นโค้ดสวยๆ ไม่สามารถว่า {} มันหมายถึงอะไรครับ inline function? รบกวนช่วยอธิบายวิธีทำงานของ บรรทัด 3 4 ซักนิด เท่าที่ผมเข้าใจ

  • Benchmark เป็นโมดูล ส่วน bm เป็นฟังก์ชั่น
  • do |x| นี่แปลว่าเอา bm มาใช้ในชื่อ x
  • x.report คือ bm.report นั่นเอง
  • n.times คือ loop n ครั้ง

แต่ {} นี่งงแฮะ เห็นใช้ตอน collect ทีนึงแล้ว

note: ผมเคยอ่าน Ruby ผ่านเมื่อหลายปีก่อน จำไม่ได้ซักอย่าง

{ } เป็น syntactic sugar ของ ruby ครับ
จริงๆมันก็คือ Proc Object หรือ lambda อันหนึ่งที่ pass เข้าไปใน method หรือ function ด้วย
เพียงแต่เราไม่ต้อง declare parameter ขึ้นมารับ

my_func(foo, bar) { .... }
 
# เขียนแบบเป็นทางการ
block = proc { ... } # หรือจะใช้ block = lambda { ... } ก็ได้
my_func(foo, bar, &block)
 
# ลอง test ดูอีกแบบ
def test(&proc)
  puts "&proc class is " + proc.class.to_s
end
 
test { puts 'hi' } # => &proc class is Proc

ปกติที่เขาเอาไปใช้กัน มันจะใช้คู่กับคำสั่ง yield
โดย yield คือการเรียกใช้ block ที่ pass เข้ามา

def say
  yield 'hello'
end
 
say { |msg| puts msg + "world" }  # => "helloworld"

ถ้าเราลอง implement method collect เอง
มันก็น่าจะออกมาประมาณนี้

class Array
  def mycollect
    ret = []
    for i in 0...size
      ret << yield(self[i])
    end
    ret
  end
end
 
[1,2,3].mycollect {|n| n + 1} # => [2,3,4]

ย้าย Codenone

ประกาศย้าย Codenone ไปใช้ Forum ของ Blognone แทนครับ ตามไปตั้งกระทู้ต่อได้ที่ Codenone Forum (รายละเอียดอ่านจากกระทู้ ย้าย Codenone ไปรวมกับ Blognone)

กระทู้เก่าๆ จะย้ายตามไปในภายหลัง ตอนนี้ปิดการโพสต์กระทู้ไว้ เหลือไว้เฉพาะอ้างอิงเท่านั้น