Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Name to Number Conversion (and vice versa)


  • Please log in to reply
1 reply to this topic
iPhilip
  • Members
  • 138 posts
  • Last active: Sep 12 2015 06:27 PM
  • Joined: 03 Jun 2010

I would like to offer the two functions below:

1. Name2Number(Name)
2. Number2Name(Number)

These might be useful for educational purposes, e.g. learning how to write numbers, as in writing the amount on a check. The functions are well documented in the header.  I also included a script that uses the above two functions to do word math, e.g. three hundred and thirty-three times four equals one thousand three hundred and thirty-two. It even speaks the operation and the result using TTS.

This is the Name2Number function:

;
; AutoHotkey: v1.1.11+
; Language:   English
; Platform:   Win7
; Author:     iPhilip
;
; Convert the name of a number into the number
;
; The function Name2Number(String) converts the name of a number into a number from 1 to 999,999,999,999.
; For example, the string "two hundred sixty-four thousand three hundred and fifty eight" converts to 264358.
; See http://en.wikipedia.org/wiki/List_of_numbers#Cardinal_numbers for a reference to the names of numbers.
; Numbers such as "eleven hundred" are not allowed. Use "one thousand one hundred" instead.
; Spaces and tabs are allowed at the start and end of the string and in between words.
; Hyphenated ("twenty-one") and non-hyphenated names ("twenty one") are allowed.
; The word "and" can be used inside the string but only after the words hundred, thousand, million, or billion.
;
; The function sets ErrorLevel to one of the following values:
;
;    0 - If the conversion was successful
;    1 - If there are typos ("elevn") or spaces inside hyphenated ("twenty- one") or regular ("seven teen") words
;    2 - If there are syntax errors in the string ("two hundred thirty ten" or "one-thirty")
;
; ------------------------------------------------------------------------------------------

Name2Number(Name)
{
   static Ones  := {one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9}
   static Teens := {one:1,two:2,three:3,four:4,five:5,six:6,seven:7,eight:8,nine:9,ten:10,eleven:11,twelve:12,thirteen:13,fourteen:14,fifteen:15,sixteen:16,seventeen:17,eighteen:18,nineteen:19}
   static Tens  := {twenty:20,thirty:30,forty:40,fifty:50,sixty:60,seventy:70,eighty:80,ninety:90}
   static Thousands := {thousand:10**3,million:10**6,billion:10**9}
   static Dictionary := "and,one,two,three,four,five,six,seven,eight,nine,ten,eleven,twelve,thirteen,fourteen,fifteen,sixteen,seventeen,eighteen,nineteen,twenty,thirty,forty,fifty,sixty,seventy,eighty,ninety,hundred,thousand,million,billion"
;
; Strip extra spaces from the start and end of the string, as well as in between words
; Perform syntax checking and strip the hyphen from hyphenated words
; Spell check each word and assemble them into a temporary array
;
   String =                                               ; Initialize the temporary string
   ErrorLevel := 0                                        ; Initialize the error code
   Loop, Parse, Name, %A_Space%%A_Tab%                    ; Parse the name using spaces and tabs as delimeters
   {
      if A_LoopField =                                    ; Skip the blanks between multiple spaces
         Continue
      if (Hyphen := Instr(A_LoopField, "-"))              ; If there is a hyphen in the word ...
      {
         LeftSide := Substr(A_LoopField, 1, Hyphen - 1)   ; Extract the left side of the hyphenated word
         RightSide := Substr(A_LoopField, Hyphen + 1)     ; Extract the right side of the hyphenated word
         if not (Tens.HasKey(LeftSide) AND Ones.HasKey(RightSide))   ; If the left side is not in the Tens array and the right side is not in the Ones array ...
            ErrorLevel := 2                               ; Set the error code corresponding to a syntax error
         if LeftSide not in %Dictionary%                  ; If the left part of the word is not in the Dictionary ...
            ErrorLevel := 1                               ; Set the the error code corresponding to a typo or an illegal word
         if RightSide not in %Dictionary%                 ; If the right part of the word is not in the Dictionary ...
            ErrorLevel := 1                               ; Set the the error code corresponding to a typo or an illegal word
         if ErrorLevel                                    ; If the error code is non-zero ...
            Return                                        ; Return with a null result
         String .= LeftSide A_Space RightSide A_Space     ; Store the two parts of the hyphenated word in the temporary string
         Continue                                         ; Skip the rest of the loop
      }
      if A_LoopField not in %Dictionary%                  ; If the word is not in the Dictionary ...
      {
         ErrorLevel := 1                                  ; Set the the error code corresponding to a typo or an illegal word
         Return                                           ; Return with a null result
      }
      String .= A_LoopField A_Space                       ; Store the word in the temporary string
   }
   String = %String%                                      ; Remove the trailing space at the end of the string (assumes the default AutoTrim, On)
   StringSplit, Array, String, %A_Space%                  ; Put all the words into a temporary array
;
; Perform syntax checking on "and" words (see above) and remove them
; Put the remaining words in the string into an array
; Count the number of "word periods", i.e. the blocks of words that represent 3 digits (left to right)
; For example, the number "two million seventy-three thousand four hundred fifty one" (2,073,451)
; has 3 word periods: "two million", "seventy-three thousand", and "four hundred fifty-eight"
;
   NumPeriods := NumWords := 0                            ; Initialize the parameters
   Loop %Array0%                                          ; Loop through each word
      if (Array%A_Index% = "and")                         ; If the word is "and" ...
      {
         Prev := A_Index - 1                              ; Setup a variable for pointing to the previous word
         if (A_Index = 1 OR A_Index = Array0 OR not (Array%Prev% = "hundred" OR Thousands.HasKey(Array%Prev%)))
         {  ; If the "and" is at the start or at the end of the string or it doesn't immediately follow the words hundred, thousad, million, or billion ...
            ErrorLevel := 2                               ; Set the error code corresponding to a syntax error
            Return                                        ; Return with a null result
         }
      }
      else                                                ; If the word is not "and" ...
      {
         NumWords++                                       ; Increment the number of words in the string
         Word%NumWords% := Array%A_Index%                 ; Assign the current word to the word array
         if Thousands.HasKey(Array%A_Index%)              ; If the word is part of the Thousands array ...
            NumPeriods++                                  ; Increment the number of word blocks
      }
   if not Thousands.HasKey(Word%NumWords%)                ; If the last word is not part of the Thousands array, e.g. "two thousand *twelve*", ...
      NumPeriods++                                        ; Increment the number of blocks by one to include the ones block
;
; Convert the word array into a number
;
   Total := 0                             ; Initialize the running total for the result
   Index := 1                             ; Initialize the index that refers to the word that is being analyzed
   ErrorLevel := 0                        ; Initialize the error code
   Thou := Thousands.Clone()              ; Make a copy of the Thousands array as it gets "consumed" in the loop below
   Loop %NumPeriods%                      ; Iterate the loop below for each word block
   {
      Sum := 0                            ; Initialize the sum for each word block
      if Ones.HasKey(Word%Index%)         ; If first word is a number from 1-9, e.g. "*seven* hundred thousand four hundred", ...
      {
         Sum := Ones[Word%Index%]         ; Convert the word into a number
         if (++Index > NumWords)          ; If that's the last word in the string, e.g. "one thousand *four*", ...
            Return Total + Sum            ; Return the result
         if (Word%Index% = "hundred")     ; If the next word is "hundred", e.g. "seven *hundred* thousand", ...
         {
            Sum *= 100                    ; Multiply the number associated with the first word by 100
            if (++Index > NumWords)       ; If that's the last word in the string, e.g. "one thousand two *hundred*", ...
               Return Total + Sum         ; Return the result
         }
         else                             ; If the next word is not "hundred" ...
         {
            Sum := 0                      ; Reset the sum of the current word block
            Index--                       ; Reset the index to the start of the word block
         }                                ; The original condition "if Ones.HasKey(Word%Index%)" will be detected again with "if Teens.HasKey(Word%Index%)" below
      }                                   ; Note that without the above "else" block, the string "one two" would be allowed
      if Tens.HasKey(Word%Index%)         ; If the next word is a number in the Tens array, e.g. "two hundred *sixty* four million", ...
      {
         Sum += Tens[Word%Index%]         ; Add that number to the word block sum
         if (++Index > NumWords)          ; If that's the last word in the string, e.g. "two hundred *sixty*", ...
            Return Total + Sum            ; Return the result
         if Ones.HasKey(Word%Index%)      ; If the next word is a number from 1-9, e.g. "two hundred sixty *four* million", ...
         {
            Sum += Ones[Word%Index%]      ; Add that number to the word block sum
            if (++Index > NumWords)       ; If that's the last word in the string, e.g. "two hundred sixty *four*", ...
               Return Total + Sum         ; Return the result
         }
      }
      else if Teens.HasKey(Word%Index%)   ; If the next word is a number from 1-19, e.g. "two hundred *eighteen* million", ...
      {
         Sum += Teens[Word%Index%]        ; Add that number to the word block sum
         if (++Index > NumWords)          ; If that's the last word in the string, e.g. "two hundred *eighteen*", ...
            Return Total + Sum            ; Return the result
      }
      if Thou.HasKey(Word%Index%)         ; If the next word is a member of the Thousands array, e.g. "two hundred sixty four *million*", ...
      {
         ThouValue := Thou[Word%Index%]   ; Copy the value associated with the current word into a temporary variable
         Sum *= ThouValue                 ; Multiply the word block sum by that number
         if (Sum = 0)                     ; If the word block sum is zero ...
            Break                         ; Break out of the loop as it means that the string has a syntax error, e.g. "two thousand *thousand*"
         Array := Thou.Clone()            ; Copy the Thou array into a temporary array
         for Key, Value in Array          ; For each (Key,Value) pair in the temporary array ...
            if (Value >= ThouValue)       ; If the Value is larger than or equal than the value associated with the current word ...
               Thou.Remove(Key)           ; Remove the pair as a way to detect syntax errors, e.g. "two thousand one thousand"
         if (++Index > NumWords)          ; If that's the last word in the string, e.g. "two hundred sixty four *million*", ...
            Return Total + Sum            ; Return the result
      }
      else                                ; If the next word is not a member of the Thousands array ...
         Break                            ; Break out of the loop as it means that the string has a syntax error, e.g. "one one million"
      Total += Sum                        ; Add the word block sum to the running total
   }
   ErrorLevel := 2                        ; If the end of string is not detected because there is a syntax error, e.g. "four three", set the corresponding error code
   Return                                 ; Return with a null result
}

This is the Number2Name function:

;
; AutoHotkey: v1.1.11+
; Language:   English
; Platform:   Win7
; Author:     iPhilip
;
; Converts a number into the name of the number
;
; The function Number2Name(Number) converts an integer from 1 to 999,999,999,999 into the name of the number.
; For example, the number 264358 converts to "two hundred sixty-four thousand three hundred and fifty eight".
; See http://en.wikipedia.org/wiki/List_of_numbers#Cardinal_numbers for reference. Comma separators in the
; number are allowed, e.g. 264,358.
;
; The function sets ErrorLevel to one of the following values:
;
;    0 - If the conversion was successful
;    1 - If the number contains any non-digit characters other than commas ("123.4")
;    2 - If the number is larger than the maximum (10**12-1)
;
; ------------------------------------------------------------------------------------------

Number2Name(Number)
{
   static OnesArray := ["one","two","three","four","five","six","seven","eight","nine"]
   static TeensArray := ["ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"]
   static TensArray  := ["twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"]
   static ThousandsArray := ["thousand","million","billion"]

   StringReplace, Number, Number, `,, , All          ; Remove any comma separators from the input string
   ErrorLevel := 0                                   ; Initialize the error code to the default value
   if Number is not digit                            ; If the string contains anything other than digits
      ErrorLevel := 1                                ; Set the corresponding error code
   else if (Number > 10**12-1)                       ; Otherwise, if the number is larger than the maximum ...
      ErrorLevel := 2                                ; Set the corresponding error code
   if ErrorLevel                                     ; If the error code is non-zero
      Return                                         ; Return
   String =                                          ; Initialize the output string
   NumPeriods := Ceil(StrLen(Number)/3)              ; Calculate the number of periods in the number
   Loop %NumPeriods%                                 ; Loop through for each period
   {                                                 ; A period is a grouping of three digits, e.g. 46,322 has two periods
      Multiplier := 10**(3*(NumPeriods-A_Index))     ; Calculate the multiplier corresponding to the current period
      Period := Floor(Number/Multiplier)             ; Extract the current period
      Hundreds := Floor(Period/100)                  ; Calculate the hundreds digit for the period
      Tens := Floor((Period-Hundreds*100)/10)        ; Calculate the tens digit for the period
      Ones := Period-Hundreds*100-Tens*10            ; Calculate the ones digit for the period
      if Hundreds                                    ; If the period has a non-zero hundreds digit ...
         String .= OnesArray[Hundreds] A_Space "hundred" A_Space   ; Append the name for the ones digit and the word "hundred" to the string, e.g. "two hundred"
      if (Tens > 1)                                  ; If the tens digit is greater than one, e.g. twenty-two
      {
         if (NumPeriods = 1 AND Hundreds OR NumPeriods > 1 AND A_Index > 1)
         ; If there are fewer than 3 digits and the number is > 99 or there are multiple periods and it's not the first period ...
            String .= "and" A_Space                  ; Append an "and " to the string
         String .= TensArray[Tens-1]                 ; Append the name for the tens digit to the string
         if Ones                                     ; If the period has a non-zero ones digit, e.g. 37
            String .=  "-" OnesArray[Ones] A_Space   ; Append a dash and the name of the ones digit to the string
      }
      else if Tens                                   ; Otherwise, if the tens digit is one, e.g. 214
      {
         if (NumPeriods = 1 AND Hundreds OR NumPeriods > 1 AND A_Index > 1)
         ; If there are fewer than 3 digits and the number is > 99 or there are multiple periods and it's not the first period ...
            String .= "and" A_Space                  ; Append an "and " to the string
         String .= TeensArray[Period-Hundreds*100-9] A_Space   ; Append the name of the of the "teens" digit to the string
      }
      else if Ones                                   ; Otherwise, if the tens digit is zero and the ones digit is non-zero ...
      {
         if (NumPeriods = 1 AND Hundreds OR NumPeriods > 1 AND A_Index > 1)
         ; If there are fewer than 3 digits and the number is > 99 or there are multiple periods and it's not the first period ...
            String .= "and" A_Space                  ; Append an "and " to the string
         String .= OnesArray[Ones] A_Space           ; Append the name of the ones digit to the string
      }
      if (Period AND A_Index < NumPeriods)           ; If the period is non-zero and it's not the last period
         String .= ThousandsArray[NumPeriods-A_Index] A_Space   ; Append the name of the "thousands" digit to the string
      Number -= Period*Multiplier                    ; Reduce the number by the current period
   }
   String = %String%                                 ; Trim the space at the end of the string
   Return String                                     ; Return the value of the string
}

This is the WordMath.ahk script that uses the above functions.

#NoEnv
ScriptName := RegExReplace(A_ScriptName, ".ahk")
SoundGet, VolumeLevel          ; Remember the volume level of the speakers
SoundGet, MuteStatus, , MUTE   ; Remember the mute status of the speakers
SoundSet, 0, , Mute            ; Unmute the speakers
SoundSet, 50                   ; Set the master volume to 50%
AnnaVoice := TTS_CreateVoice("Microsoft Anna", -3, 100, -3)
Operation := "three hundred and thirty-three times four"
Loop
{
   InputBox, Operation, %ScriptName%, Please enter the word operation:, , 305, 125, , , , , %Operation%
   if ErrorLevel
      Break
   String1  := RegExReplace(Operation, "i) (plus|minus|times|divided by).*")
   Operator := RegExReplace(Operation, "i).*(plus|minus|times|divided by).*", "$1")
   String2  := RegExReplace(Operation, "i).*(plus|minus|times|divided by) ")
   Number1 := Name2Number(String1)
   if (ErrorLevel = 1)
      MsgBox, 48, , % "There is a TYPO or an ILLEGAL WORD in the string:`n`n""" String1 """`n`nPlease correct it and try again."
   else if (ErrorLevel = 2)
      MsgBox, 48, , % "There is a SYNTAX ERROR in the string:`n`n""" String1 """`n`nPlease correct it and try again."
   if ErrorLevel
      Continue
   Number2 := Name2Number(String2)
   if (ErrorLevel = 1)
      MsgBox, 48, , % "There is a TYPO or an ILLEGAL WORD in the string:`n`n""" String2 """`n`nPlease correct it and try again."
   else if (ErrorLevel = 2)
      MsgBox, 48, , % "There is a SYNTAX ERROR in the string:`n`n""" String2 """`n`nPlease correct it and try again."
   if ErrorLevel
      Continue
   Equals := "`nequals`n"
   if (Operator = "plus")
      Result := Number1+Number2
   else if (Operator = "minus")
      Result := Number1-Number2
   else if (Operator = "times")
      Result := Number1*Number2
   else if (Operator = "divided by")
   {
      Result := Round(Number1/Number2)
      if (Result - Number1/Number2)
         Equals := "`nis approximately`n"
   }
   Output := Operation Equals Number2Name(Result)
   SetTimer, SpeaknShowButton, -250      ; Wait long enough for the button to go completely gray
   SetTimer, HideButton, -1
   MsgBox, , %ScriptName%, %Output%
}
SoundSet, (MuteStatus = "ON" ? 1 : 0), , MUTE   ; Reset the mute status of the speakers
SoundSet, VolumeLevel                           ; Reset the volume level of the speakers
ExitApp

SpeaknShowButton:
TTS(AnnaVoice, "SpeakWait", Output)
Control, Enable, , Button1, %ScriptName%
ControlFocus, Button1, %ScriptName%
Return

HideButton:
Control, Disable,, Button1, %ScriptName%
Return

#Include Name2NumberFunction.ahk
#Include Number2NameFunction.ahk
#Include TTS.ahk   ; http://www.autohotkey.com/board/topic/53429-function-easy-text-to-speech/

For sake of completeness, below is the TTS.ahk set of functions.

; Reference: http://www.autohotkey.com/board/topic/53429-function-easy-text-to-speech/

TTS(oVoice, command, param1="", param2="") {		; by Learning one. For AHK_L. Thanks: jballi, Sean, Frankie.
	; AHK forum location:	www.autohotkey.com/forum/topic57773.html
	; Read more:			msdn.microsoft.com/en-us/library/ms723602(v=VS.85).aspx, www.autohotkey.com/forum/topic45471.html, www.autohotkey.com/forum/topic83162.html
	static CommandList := "ToggleSpeak,Speak,SpeakWait,Pause,Stop,SetRate,SetVolume,SetPitch,SetVoice,GetVoices,GetStatus,GetCount,SpeakToFile"
	if command not in %CommandList%
	{
		MsgBox, 16, TTS() error, "%command%" is not valid command.
		return
	}
	if command = ToggleSpeak	; speak or stop speaking
	{
		Status := oVoice.Status.RunningState
		if Status = 1	; finished
		oVoice.Speak(param1,0x1)	; speak asynchronously
		Else if Status = 0	; paused
		{
			oVoice.Resume
			oVoice.Speak("",0x1|0x2)	; stop
			oVoice.Speak(param1,0x1)	; speak asynchronously
		}
		Else if Status = 2	; reading
		oVoice.Speak("",0x1|0x2)	; stop
	}
	Else if command = Speak		; speak asynchronously
	{
		Status := oVoice.Status.RunningState
		if Status = 0	; paused
		oVoice.Resume
		oVoice.Speak("",0x1|0x2)	; stop
		oVoice.Speak(param1,0x1)	; speak asynchronously
	}
	Else if command = SpeakWait		; speak synchronously
	{
		Status := oVoice.Status.RunningState
		if Status = 0	; paused
		oVoice.Resume
		oVoice.Speak("",0x1|0x2)	; stop
		oVoice.Speak(param1,0x0)	; speak synchronously
	}
	Else if command = Pause	; Pause toggle
	{
		Status := oVoice.Status.RunningState
		if Status = 0	; paused
		oVoice.Resume
		else if Status = 2	; reading
		oVoice.Pause
	}
	Else if command = Stop
	{
		Status := oVoice.Status.RunningState
		if Status = 0	; paused
		oVoice.Resume
		oVoice.Speak("",0x1|0x2)	; stop
	}
	Else if command = SetRate
		oVoice.Rate := param1		; rate (reading speed): param1 from -10 to 10. 0 is default.
	Else if command = SetVolume
		oVoice.Volume := param1		; volume (reading loudness): param1 from 0 to 100. 100 is default
	Else if command = SetPitch				; http://msdn.microsoft.com/en-us/library/ms717077(v=vs.85).aspx
		oVoice.Speak("<pitch absmiddle = '" param1 "'/>",0x20)	; pitch : param1 from -10 to 10. 0 is default.
	Else if command = SetVoice
	{
		Loop, % oVoice.GetVoices.Count
		{
			Name := oVoice.GetVoices.Item(A_Index-1).GetAttribute("Name")	; 0 based
			If (Name = param1)
			{
				DoesVoiceExist := 1
				break
			}
		}
		if !DoesVoiceExist
		{
			MsgBox,64,, Voice "%param1%" does not exist.
			return
		}
		While !(oVoice.Status.RunningState = 1)
		Sleep, 20
		oVoice.Voice := oVoice.GetVoices("Name=" param1).Item(0) ; set voice to param1
	}
	Else if command = GetVoices
	{
		param1 := (param1 = "") ? "`n" : param1		; param1 as delimiter
		VoiceList =   ; Added by iPhilip to prevent warnings from #Warn
		Loop, % oVoice.GetVoices.Count
		{
			Name := oVoice.GetVoices.Item(A_Index-1).GetAttribute("Name")	; 0 based
			VoiceList .= Name param1
		}
		Return RTrim(VoiceList,param1)
	}
	Else if command = GetStatus
	{
		Status := oVoice.Status.RunningState
		if Status = 0 ; paused
		Return "paused"
		Else if Status = 1 ; finished
		Return "finished"
		Else if Status = 2 ; reading
		Return "reading"
	}
	Else if command = GetCount
		return oVoice.GetVoices.Count
	Else if command = SpeakToFile	; param1 = TextToSpeak,    param2 = OutputFilePath
	{
		oldAOS := oVoice.AudioOutputStream
		oldAAOFCONS := oVoice.AllowAudioOutputFormatChangesOnNextSet
		oVoice.AllowAudioOutputFormatChangesOnNextSet := 1	
		
		SpStream := ComObjCreate("SAPI.SpFileStream")
		FileDelete, % param2	; OutputFilePath
		SpStream.Open(param2, 3)
		oVoice.AudioOutputStream := SpStream
		TTS(oVoice, "SpeakWait", param1)
		SpStream.Close()
		oVoice.AudioOutputStream := oldAOS
		oVoice.AllowAudioOutputFormatChangesOnNextSet := oldAAOFCONS
	}
}	
 TTS_CreateVoice(VoiceName="", VoiceRate="", VoiceVolume="", VoicePitch="") {		; by Learning one. For AHK_L.
	oVoice := ComObjCreate("SAPI.SpVoice")
	if !(VoiceName = "")
		TTS(oVoice, "SetVoice", VoiceName)
	if VoiceRate between -10 and 10
		oVoice.Rate := VoiceRate		; rate (reading speed): from -10 to 10. 0 is default.
	if VoiceVolume between 0 and 100
		oVoice.Volume := VoiceVolume	; volume (reading loudness): from 0 to 100. 100 is default
	if VoicePitch between -10 and 10
		TTS(oVoice, "SetPitch", VoicePitch)	; pitch: from -10 to 10. 0 is default.
	return oVoice
}

iPhilip
 


Windows 7 Pro (64 bit) - AutoHotkey v1.1+ (Unicode 32-bit)


smorgasboard
  • Members
  • 660 posts
  • Last active: Jan 14 2016 08:53 AM
  • Joined: 18 Jul 2012

Projecteuler problem 17 solved with that code

awesome!

why reinvent the wheel

:)

Thanks iphilip

That is really really helpful as such

:)