CNRuby-2

  • warning: realpath() [function.realpath]: SAFE MODE Restriction in effect. The script whose uid is 1001 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 1001 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.

โจทย์ข้อสองตามมา

ให้แปลง Numeric เป็น String ที่มี comma คั้นแต่ละ 3 digits หน้าจุดทศนิยม
ตัวอย่างเช่น
ให้ 19999999999.55 แปลงเป็น “19,999,999,999.55”
ให้ 19999995.99 แปลงเป็น “19,999,995.99”

โค้ดสำหรับ benchmark

require 'benchmark'
 
ITERATION = 500
 
def measure(fls,&block)
  ITERATION.times { block.call }
end
 
fls = []
400.times do 
  fls << sprintf("%0.2f",rand() * (10**11)).to_f
end
 
br = Benchmark.bmbm do |b|
  b.report("Running #{ITERATION} iterations") do
    measure(fls) {
      fls.each do |t|
       # ใส่โค้ดเพื่อทดสอบตรงนี้        
      end
    }
  end
end
taiko_gogo's picture

ในที่สุดก็มา ขอลุยคนแรกเลยละกันครับ

def addComma(t)
  t.to_s.gsub(/\d*\./,t.split(".")[0].reverse.scan(/\d{1,3}/).join(",").reverse+".")
end

พยายามจะเขียนในบรรทัดเดียวเลยออกมาประหลาด ๆ
ท่าทางเรื่องเวลาคงไม่ต้องพูดถึง ช้าแหงเลย >_<
reverse ตั้งสองที แถมยังใช้ scanด้วย

แอบแก้ให้ครับ มี bug นิดนึง

def addComma(t)
  t.to_s.gsub(/\d*\./,t.to_s.split(".")[0].reverse.scan(/\d{1,3}/).join(",").reverse+".")
end
taiko_gogo's picture

โอ้ ขอบคุณ sikachu ครับ ผมลืม .to_s ของอันที่สองไป คือตอนแรกผมไม่ได้อ่านว่า input มาเป็นตัวเลข >_< เลยแก้รอบนึง ใส่ .to_s แต่ดันลืมใส่ให้ตัวที่สองแฮะ ขอบคุณที่เตือนและแก้ให้ครับ

RESULT: 3.374971

ชอบตรง reverse :)

ขอให้เร็วด้วยเถิดดดด

ปล. ระวัง overflow นะครับ เลข float น่ะ :)

def delimited(flt)
  int,dec = flt.to_s.split('.')
  times = int.length % 3 != 0 ? int.length / 3 : int.length / 3 - 1
  1.upto(times) do |i|
    int = int.insert(-4*i, ',')
  end
 
  "#{int}.#{dec}"
end
taiko_gogo's picture

ผมว่าเร็วแหง อย่างน้อยก็ทำเวลาดีกว่าของผมชัวร์ๆ >_<

RESULT: 2.046558

เร็วได้ใจ !!

taiko_gogo's picture

เร็วจริง ๆ ตามคาด (>_<)b

ใน regexp มันมี feature ที่เรียกว่า lookaround อยู่
เราสามารถนำ lookahead กับ lookbehind มาใช้ implement feature นี้ได้

แต่ปัญหาก็คือ เจ้า ruby 1.8 ไม่มี feature lookbehind (อยากให้มีก็ได้ แต่ต้องออกแรง patch หน่อย)

เริ่มด้วยการใช้ ruby 1.9 ก่อน

def format(val)
  val.gsub(/(?<=\d)(?=(\d\d\d)+\.)/, ',')
end

?<= คือ lookbehind
?= คือ lookahead
ความหมายของข้างบนคือ หาตำแหน่งที่มีเลขสามตัวอยู่ด้านขวา และมีเลขหนึ่งตัวอยู่ด้านซ้าย
จากนั้นก็แทนที่ด้วย comma

ส่วน ruby 1.8 เนื่องจากไม่มี lookbehind แต่เราก็สามารถ work around ได้แบบนี้

def format(val)
  val.gsub(/(\d)(?=(\d\d\d)+\.)/) {|s| s + ','}
end

เนื่องจาก ตัวเลขที่อยู่ด้านซ้าย (ของตำแหน่งคอมมา) เราไม่สามารถใส่ lookbehind ได้
เราก็เลยใส่ตัวเลขจริงๆไปเลย ซึ่งจะเกิดปัญหาว่า regexp มัน consume ตัวเลขตัวนั้นไป
เราก็เลยต้องใช้ block + gsub เพื่อใส่ตัวเลขที่โดน consume กลับเข้ามาพร้อมกับเครื่องหมาย comma

Note: pattern ข้างบนไม่ support จำนวนเต็มที่ไม่มีจุดทศนิยม

เนียนสุดๆ

  • ได้ความรู้ใหม่ด้วย :)

RESULT: 3.669626

ป.ล. ผมลองรัน benchmark เฉพาะโค้ดสำหรับ 1.8 ครับ

taiko_gogo's picture

ผมพอจะรู้conceptพวก look behind/ahead จากตอนเรียน แต่ไม่เคยได้ใช้เลย ความรู้ใหม่เหมือนกันครับ ขอบคุณพี่ป๊อกครับ >_<

ลองใช้ look-ahead ดูบ้าง บน 1.8 ผมใช้ reverse ช่วย

val.to_s.reverse.gsub(/(\d{3})(?=\d)(?!\d*\.)/,'\1,').reverse

มองกลับด้าน มีตัวเลขสามตัวทางซ้าย แล้ว look-ahead มีตัวเลขทางขวาอย่างน้อยหนึ่งตัว และไม่มีจุดอยู่ทางขวา

RESULT: 2.135641

เดินสายกลาง

เสียวแว๊บบบ เลย ๕๕๕๕

ขอโหวต style winner ให้อันนี้ละกัน 55+ RegEx เข้าใจไม่ยากเกินไป + ใช้ method reverse ดูง่าย

รีบโหวตจะได้หาข้อต่อไปมาเล่นต่อ

taiko_gogo's picture

โหวดด้วยคนคร๊าบ

ย้าย Codenone

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

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