Number to word

Source จาก http://sohne.net/articles/2006/04/30/convert-numbers-to-words/ เท่าที่แกะดูก็ไม่ยากแต่ไม่เข้าใจว่า หลายที่เงื่อนไขไม่ลงเลย สงสัยทำเผื่อไว้

Source from http://sohne.net/articles/2006/04/30/convert-numbers-to-words/ I have some confuse with this code, some condition will not may happen but i still be.

มีที่ดีกว่านี้ที่ http://www.deveiate.org/projects/Linguistics/wiki/English

<typo:uvcode lang="ruby"> class Number

def self.to_words(number)

Number.new.to_s(number)

end

def self.commify(number)

(s=number.to_s;x=s.length;s).rjust(x+(3-(x%3))).gsub(/(\d)(?=\d{3}+(\.\d*)?$)/, '\1,')

end

def initialize

@unit = %w[zero one two three four five six seven eight nine]
@teen = %w[ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen]
@tens = %w[zero ten twenty thirty fourty fifty sixty seventy eighty ninety]
@qtys = %w[hundred thousand million billion trillion quadrillion quintillion]
@zero = ["zero"]
@hundred = "hundred"
@sepr = "and"

end

def to_s(number)

out = quantify(number).flatten
for x in 0 .. out.length - 1
  out[x] = nil if out[x] == @sepr && out[x+1] == @sepr
  out[x] = nil if out[x] == "," && out[x+1] == ","      
end
out.compact!
out = @zero if out.length == 1 && out[0] == @sepr
out.pop while out.last == @sepr
out.shift while out.first == @sepr    
out.join(' ').gsub(/ ,/,',')

end

private

def padded_groups(v)

#make number to be 3 units per slot  by fill zero infront (x%3 =0)
out = []
padded = (s=v.to_s;x=s.length;s).rjust(x+(3-(x%3))).gsub(/ /,'0')
padded.scan(/.{3}/)

end

def wordify(v)

#Get 3 unit of slot and compare by ascii code, then word it out
out = []
zero = '0'[0]
h, t, u = v[0] - zero, v[1] - zero, v[2] - zero
if h != 0
  out << @unit[h]
  out << @hundred
end
#"and" word will add when hundred appear and unit appear 
out << @sepr if h != 0 && (t != 0 || u != 0)
out << @sepr if h == 0 && t == 0 && u != 0
if t == 1
  out << @teen[u]
else
  out << @tens[t] if t != 0
  out << @unit[u] if u != 0
end
return out

end

def quantify(v)

#make ach 3 of each slot to be a word by calling wodify
v = padded_groups(v).reverse
cur = pos = v.length - 1
out = []
while pos >= 0
  if v[pos] == ','
    out << ','
    next
  end
  word = wordify(v[pos])
  if word[0] != nil
    out << word
    out << @qtys[cur] if cur != 0                
  else
    out << @sepr
  end
  cur -= 1
  pos -= 1
end
return out

end

end

puts Number.to_words(1234567890) puts Number.commify(1234567890) </typo:uvcode>

ถ้าอยากรู้ว่า code ทำงานอย่างไรอ่านต่อข้างใน

แรกสุดเลยหลังจากที่มีตัวเลขเข้ามา method แรกที่ทำงานคือ padded_groups ทำหน้าที่แบ่งเลขออกเป็นชุด ชุดละ 3 หลัก

<typo:uvcode lang="ruby"> def padded_groups(v)

#make number to be 3 units per slot  by fill zero infront (x%3 =0)
out = []
padded = (s=v.to_s;x=s.length;s).rjust(x+(3-(x%3))).gsub(/ /,'0')
padded.scan(/.{3}/)

end </typo:uvcode>

โดยจะทำการเติม 0 ไว้ข้างหน้าในกรณีที่เมื่อแบ่ง 3 แล้วไมเต็ม แลวทำการแยกออกมาเป็นชุดๆ จากคำสั่ง scan(/.{3}/) เราจะได้ array ที่แต่ละตำแหน่งมีเลข 3 หน่วย /.{3}/ เป็น Regexp หมายถึงจับ 3

method padded_groups จะถูก method quantify เรียกใช้

<typo:uvcode lang="ruby"> def quantify(v)

v = padded_groups(v).reverse
cur = pos = v.length - 1
out = []
while pos >= 0
  if v[pos] == ','
    out << ','
    next
  end
  word = wordify(v[pos])
  if word[0] != nil
    out << word
    out << @qtys[cur] if cur != 0                
  else
    out << @sepr
  end
  cur -= 1
  pos -= 1
end
return out

end </typo:uvcode>

quantify จะเลือกทำจากหน่วยสูงสุดก่อน ถ้าจังหวะนั้นไปเจอจุลภาค (,) ก็จะใส่ต่อแล้วข้ามไป จากนั้นก็จะไปเรียก method wordify ซึ่งมีหน้าที่เปลี่ยนตัวเลขเป้นคำโดยเอาคำมาจาก method initialize เมื่อได้คำมาจาก wordify แล้วจะทำการใส่กำลังให้ตามตำแหน่งเช่น ร้อย พัน ล้าน และก็ใส่ and ถ้าเป็นหลักสุดท้ายในชุดนั้นๆ

ทีนี้ก็ต้องมาดูต่อที่ wordify เขียนได้จ๊าบจริงๆ ตรรกเจ๋งมาก <typo:uvcode lang="ruby"> def wordify(v)

#Get 3 unit of slot and compare by ascii code, then word it out
out = []
zero = '0'[0]
h, t, u = v[0] - zero, v[1] - zero, v[2] - zero
if h != 0
  out << @unit[h]
  out << @hundred
end
#"and" word will add when hundred appear and unit appear 
out << @sepr if h != 0 && (t != 0 || u != 0)
out << @sepr if h == 0 && t == 0 && u != 0
if t == 1
  out << @teen[u]
else
  out << @tens[t] if t != 0
  out << @unit[u] if u != 0
end
return out

end </typo:uvcode> ค่าที่ได้จาก padded_groups คือแต่ละชุดจะมี 3 หลัก เขาก็คิดว่ามันคือ ร้อย สิบ หน่วย แล้วก็เอาค่าแต่ละอันมาหักลบจากค่า acsii ของ 0 (acsii = 48) แล้วก็เอาแต่ละหลักมาใส่กำลัง ตรงหลักสิบคือปัญหาเพราะภาษาอังกฤษมันมีพวก teen คือ 11 ถึง 19 แล้วก็พวก ten คือ 10 20 30.. 90 แต่ก็แค่เอาค่าหลักสิบมาเทียบว่าถ้าเป็น 1 คือ teen นั่นเอง

จากนั้นก็มาสู่ method to_s หลัก เอาค่าที่เป็นคำแล้วมาเรียบเรียง <typo:uvcode lang="ruby"> def to_s(number)

out = quantify(number).flatten
for x in 0 .. out.length - 1
  out[x] = nil if out[x] == @sepr && out[x+1] == @sepr
  out[x] = nil if out[x] == "," && out[x+1] == ","      
end
out.compact!
out = @zero if out.length == 1 && out[0] == @sepr
out.pop while out.last == @sepr
out.shift while out.first == @sepr    
out.join(' ').gsub(/ ,/,',')

end </typo:uvcode>

สุดท้ายก็เอาคำ "and" ที่อยู่ข้างหน้าและข้างหลังออกและทำการรวบ array ทุกตำแหน่งให้เป็นคำเดียว

เจ๋งจริงๆ ได้แนวความคิดมาเขียนเป็นภาษาไทยได้เลย รอหน่อยแล้วผมจะเอา code สำหรับภาษาไทยมาแปะ

Tue, 12 Feb 2008 10:35 Posted in

Tags

If you liked this article you can add me to Twitter

  1. Avatar

    By taiko 2 days later:


    ผมชอบอันนี้ padded = (s=v.to_s;x=s.length;s).rjust(x+(3-(x%3))).gsub(/ /,'0') ตอนเขียนไม่เคยใช้แบบนี้เลย ส่วนใหญ่จะเขียนแบบ ไม่ได้อยู่ในบรรทัดเดียว เนื่องจากไม่ค่อยได้แกะ code ชาวบ้าน เลยไม่ค่อยได้เห็น รูปแบบ statement;statement.. เท่าไหร่ ไปรื้อ code ที่เคยเขียน จำไม่ได้ว่าเขียนอะไรไปบ้าง พบว่า สิ่งที่ตัวเองเคยเขียนไปคือ แปลงวันที่ เป็น คำภาษาไทย แต่...ยกเว้นตัวเลข ( แปลแค่ชื่อเดือนกับวัน -_-" ง่ายนี่หว่า -_-")

Comment Number to word


RSS