Number to word

Posted by Revolution Tue, 12 Feb 2008 10:42:35 GMT

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

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)

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

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

 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

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

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

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

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

ทีนี้ก็ต้องมาดูต่อที่ wordify เขียนได้จ๊าบจริงๆ ตรรกเจ๋งมาก

  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

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

จากนั้นก็มาสู่ method to_s หลัก เอาค่าที่เป็นคำแล้วมาเรียบเรียง

  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

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

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

Comments - (Leave a comment)

  1. taiko said 2 days later:

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

Trackbacks

Use the following link to trackback from your own site:
/articles/trackback/2170

Leave a comment