คนรัก Ruby หาอะไรเล่นกันดีกว่า

อยากหาอะไรให้ Ruby Forum เล่นกันสนุกๆ (เห็นช่วงนี้เงียบมาก) ถ้าใครได้เริ่มเขียน Ruby มาบ้าง จะรู้ว่าเวลาทำอะไรแต่ละอย่าง มันมีวิธีแก้หลายวิธีเหลือเกิน (There’s more than one way to do it) บางทีก็เสียเวลากว่าจะตัดสินใจว่าควรจะใช้วิธีไหนดี ซึ่งบางคนก็เน้นว่าเขียนแบบนี้แหละรันเร็วสุด บางคนก็สนใจว่าเขียนแบบนี้สวยสุด บางคนก็บอกว่าใช้งานได้พอแล้ว :) (แนวใครแนวมัน)

เลยอยากมี Series โจทย์ไว้เล่นกันแต่ละอาทิตย์ ใครจะตั้งโจทย์ก็ได้(แล้วใครจะตั้งโจทย์? คุยกันอีกที) เรียกว่า CNRuby Series (Codenone Ruby Series) ละกัน

แต่ขอเป็นโจทย์ง่ายๆ (เน้นว่าง่ายๆ) แต่มีอะไรให้เล่น วิธีแก้(code) ออกมาไม่ควรเกิน 20 บรรทัด ไม่เอาแบบโจทย์โอลิมปิก codegolf..etc. เห็นแล้วเหนื่อย และหลายคนในนี้คงไม่ว่างทำ จุดประสงค์ก็เพื่อ อยากให้เห็นว่าปัญหาเล็กๆง่ายๆ มันก็มีวิธีแก้หลายแบบ มีข้อดีข้อเสียในแต่ละวิธีนั้นอย่างไรบ้าง Ruby โปรแกรมเมอร์แต่ละท่านเขียนกันอย่างไร อยากให้ทุกคนได้ร่วมตอบง่ายๆ ไม่ต้องเสียเวลาทำนาน

Winner จะมีสองแบบคือ 1.Style Award- เขียนสวยสุด (ช่วยกันเลือก?) 2.Performance Award-เขียนออกมาแล้วรันเร็วสุด (คนตั้งโจทย์มี benchmark code ให้) (ไม่มีรางวัล :) )

เริ่มต้นด้วย

CNRuby-1

โจทย์: กำหนัดตัวแปร str เป็น String นับจำนวนคำซ้ำที่อยู่ใน String นั้น และเก็บผลลัพท์ในโครงสร้างแบบ Hash

ตัวอย่างเช่น สมมติ ให้

str = "Ruby is a dynamic, open source programming language with a focus on simplicity and productivity."

ให้แปลงเป็น

 {"productivity"=>1, "and"=>1, "a"=>2, "focus"=>1, "language"=>1, 
           "simplicity"=>1, "programming"=>1, "ruby"=>1, "on"=>1, 
             "source"=>1, "dynamic"=>1, "with"=>1, "open"=>1, "is"=>1}

โค้ดไว้สำหรับทดสอบ speed

require 'benchmark'
 
ITERATION = 500
 
def measure(&block)
  ITERATION.times { block.call }
end
 
str = ""
5000.times do 
  str << (('a'..'z').to_a[rand(26)] + ('a'..'z').to_a[rand(26)] + ('a'..'z').to_a[rand(26)]+ ('a'..'z').to_a[rand(26)])[0..rand(3)] + ' ' 
end
 
br = Benchmark.bmbm do |b|
  b.report("Running #{ITERATION} iterations on string length #{str.length}") do
    measure {
      # ใส่โค้ดเพื่อทดสอบตรงนี้
    }
  end
end

จะมีคนเล่นด้วยรึเปล่าเนี่ย

ป.ล. ผล benchmark รันบน OSX, Intel Core 2 Duo, 2.4 GHz, RAM 4 GB, Bus 800 MHz

ประกาศผล :P

[Speed Winner]

def count_word2(str)
  hash = Hash.new(0)
  str.each(' ') do |word|
    hash[word] += 1
  end
  hash
end

from pphetra

[Style Winner]

def count_word(str)
  hash = Hash.new(0)
  str.split.each do |word|
    hash[word] = hash[word] +  1
  end
  hash
end

from pphetra

taiko_gogo's picture

ไม่มีใครตอบเลย ผมขอโซโล่แบบง่าย ๆ ก่อนเลยนะ :D ฮ่า ๆ

  result={}
  str="Ruby is a dynamic, open source programming language with a focus on simplicity and productivity.".split(" ").each do |token|
    unless result[token]
      result[token] = 1
    else
      result[token] = result[token]+1
    end
  end

จะเริ่มเรียนแล้ว เดี๋ยวเรียนเสร็จแล้วค่อยโพสต่อ

sugree's picture

ลืม , รึเปล่า

taiko_gogo's picture

ลืมอ่ะ่ครับ โอ พึ่งนึกได้ ตอนเช้าเขียนแบบรีบจัด :p

RESULT: 3.797270

def count_word(str)
  hash = Hash.new(0)
  str.split.each do |word|
    hash[word] = hash[word] +  1
  end
  hash
end

RESULT: 3.686017

def count_word2(str)
  hash = Hash.new(0)
  str.each(' ') do |word|
    hash[word] += 1
  end
  hash
end

RESULT: 2.999015

(จาก 3 code ด้านบน ผมชอบ style นี้มากสุด บังเอิญ..เร็วสุดด้วย 55+)

taiko_gogo's picture

ด้วยคำรู้อันน้อยนิด ผมเพิ่งรู้ว่าทำแบบนี้ได้เลย โดยไม่ต้องเช็คว่า มี hash[word] ก่อนหรือปล่าว ตอนแรกเข้าใจว่า ถ้า hash[word] มันไม่มีค่า มันก็จะเป็น nil += 1 ซึ่งมันไม่น่าจะได้

Hash.new(0) จะให้ default value ของ hash เป็น 0 ( ถ้าเป็น {} default เป็น nil ) ก็เลยบวกกันได้

taiko_gogo's picture

แบบนี้นี่เอง ปกติเวลาผมใช้ hash ผมจะใช้ เป็น var={} แบบนี้อย่างเดียว ไม่เคยใช้ Hash.new เลย มาคราวนี้ได้เปลี่ยนวิธีเขียนละ เย้

def word_count(str)
  freq = Hash.new(0)
  str.split.each {|w| freq[w] += 1 }
  return freq
end

RESULT: 3.226794

 
str.scan(/\w+/).inject(Hash.new(0)) { |h,w| h[w] += 1; h }

RESULT: 5.686920

แบบนี้รันช้าจังแฮะ…

taiko_gogo's picture

ผมชอบอันนี้อ๊ัะ >_<

ความจริงอันนี้อ่านเข้าใจง่ายดี

scan ผมก็ลอง ช้ากว่ากันเยอะ ส่วนที่ช้าสุด ก็คือ เขียน loop เอง แล้วใช้พวก [] slice คำออกมา ช้ากว่าเกือบ 4 เท่า

str.split.inject(Hash.new(0)) { |h,w| h[w] += 1; h }

RESULT: 4.584874

อันนี้ก็ยังช้าอยู่ดี… แต่ก็สวยอีกแบบ

สรุปผลโจทย์ข้อนี้กันเลยมั้ยครับ… taiko_gogo , pphetra ช่วยเลือกหน่อยครับว่า code แบบไหนน่าจะได้ style winner

ส่วน speed winner ก็ยกให้ code

def count_word2(str)
  hash = Hash.new(0)
  str.each(' ') do |word|
    hash[word] += 1
  end
  hash
end

ของ pphetra

taiko_gogo's picture

ผมขอโหวตอันที่ใช้ scan คร๊าบ + ขอโจทย์อันถัดไปด้วย :D

ผม vote str.split.each เพราะ อ่านเข้าใจง่ายดี

รอข้อต่อไปครับ :P

/me nudge zdk

ย้าย Codenone

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

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