January 06, 2015

Dyanmo: Custom Node, Integer To String - Zero Padding

Having created a node that would convert an integer to a string, padding a single-character string to two characters by adding an initial "0" so that a YYYYMMDD date string could be generated, it occurred to me that having a more general purpose node, where you could specify the number of characters to which the string should be zero-padded, would be more useful. As with the two-character node, no effort is made to truncate the original string if it exceeds the specified length, but strings with fewer characters will have the necessary number of zero characters ("0") added to the front of the string to bring it up to the specified number of characters.

As seen in the short Screencast below, the custom node takes two numeric inputs, the number of characters to which zero-padding is to be applied and an "integer" [see note at end of article] that is to be converted to a string, with zero-padding as necessary. In the Dynamo file I set up to demonstrate the custom node, an Integer Slider node (in the Library, Core > Input > Integer Slider) is used to specify the number of digits desired and a Number node (Core > Input > Number) is used to provide the integer to be converted to a string. Note that the Run Automatically toggle is checked, so as changes are made, the results are calculated and displayed without having to press the Run button each time.

Integer To String - Zero Padding Node
The image above shows the graph of the Integer To String - Zero Padding custom node. A description of the graph function follows.
  1. The integer to be converted to a string is processed by the Math.Floor node (Core > Math > Actions > Floor) to remove any fractional part (see note below) prior to being passed to the ToString node (Builtin Function > ToString), which converts it to a string.
  2. The integer string created in Step 1 is passed to a String.Length node (Core > String > Actions > Length), which returns the string length as an integer. A custom node called Zero Padding String takes the Final String Length as input to the Integer To String - Zero Padding custom node, and the string length of the integer string and returns a string containing the zeros that need to be added to the front of the integer string (if any). See below for the graph of this custom node.
  3. The zeros string from Step 2 is concatenated onto the front of the integer string from Step 1 by a String.Concat node (Core > String > Actions > Concat), which is then passed as the output of the Integer To String - Zero Padding custom node. Note that if the length of the Step 1 integer string is greater than or equal to the Final String Length, the zero string will be an empty string, and the end result will be equivalent to the Step 1 integer string.

Zero Padding String Node
The image above shows the graph of the Zero Padding String custom node. A description of the graph function follows.
  1. The easiest way to explain how this graph functions is to start with the LoopWhile node (Builtin Functions > LoopWhile). The LoopWhile node takes three inputs: init, the intial state of the object being processed; continueWhile, the test condition that determines whether or not the current object being processed should be processed again; and loopBody, which defines what action(s) are taken on each pass of the loop.
  2. The init input is provided by a Code Block node (Core > Input > Code Block; or double-click in the graph canvas). Code Blocks allow for custom scripting in Dynamo; in this case, it is being used to simply generate an empty string. A String node (Core > Input > String), with the input left blank, could also have been used; I felt that seeing the two double quotation mark characters, with no other characters in between, in the Code Block made the node read better. I also double-clicked on the Code Block title and renamed it to "Empty String" to more clearly indicate its purpose.
  3. The continueWhile input is the most complicated. A - [Subtraction] node (Operators > -) calculates the difference between the Final String Length input and the Starting String Length. If this is a positive number, it represents the number of zero characters needed in the final padding string. We want to compare the length of the current string to this number, and continue to process the string if the current string length is less than the desired string length. This comparison is done by the < [Less Than] node (Operators > <), and the current string length is generated by the String.Length node. For reasons beyond my current understanding, the output of the String.Length node cannot simply be put into the x input of the < node, and the result of that fed to the continueWhile input. The continueWhile input requires a single function as the input, and so a Function.Compose node (Core > Evaluate > Function.Compose) is used to combine these nodes in a way that is acceptable as input for the continueWhile input. The + and - buttons on the node allow you to add or remove function inputs so that you have the correct amount for your application. In this case, the initial default of two inputs was what was needed.
    You may have noticed that both the < and String.Length nodes have unconnected inputs. The LoopWhile node presumes that you will be doing something to whatever is passed to the init input, in this case, a string, and will supply that value to any open input of matching type. Likewise, the Function.Compose node supplies the integer value passed to the func0 input by the String.Length node to the open x input of the < node. Also note that by including the current value of the processed object in the evaluation, we make it possible for the the comparison to eventually be false, and for the loop to terminate. Failure to do so would result in an infinite loop, which is not a good thing.
  4. The loopBody input is fairly straightforward. Another Code Block node, renamed to "Zero String" creates a string with a single zero character in it. This is the input to the string0 input of a String.Concat node. The string1 input is left open, so the processed object string will be supplied here. Each time the loop body is entered, a zero character will be added to the front of the current processed object string, making it one character larger. Eventually, the processed string length will be equal to the difference between the Final String Length and the Starting String Length, the loop will terminate and the final result will be a string with the proper number of zero characters.
  5. To summarize, the loop starts with an initial value of an empty string. The length of that empty string (0) is compared to the difference between the Final String Length and Starting String Length inputs, to see if it is less than the difference. If that difference is 0 or a negative number, then the value fed to the continueWhile input is false, no action will be taken, and the result of the custom node will be an empty string. If the difference is 1 or greater, then the value fed to the continueWhile input is true, and the loop body code will be run, adding a zero character to the empty string. The loop process is then run again, but this time the current value is not an empty string but "0", and the string length is 1. If the difference is 1, then the less than condition is false and the "0" string is passed to the output of the custom node. Otherwise, the loop body is run again and "00" becomes the new current value of the string. This continues until the string length is not less than the difference, at which point the loop terminates and the current string value becomes the custom node result.

NOTE: I put "integer" in quotation marks when describing the node input above because while that is the intended input, both inputs will accept a double (precision real number), with a non-zero fractional value, as input. For the number to be converted, any fractional part is discarded prior to the conversion to a string. The way the Final String Length value is used, any fractional amount will be the same as specifying the next higher integer. If a given application could generate a value with a fractional amount as the input for the Final String Length and only the whole number value is desired, the Math.Floor node should be used to strip the fractional amount. This works for non-negative numbers; the function returns the first integer that is lower than the input value. For negative numbers, this will change the units number. So 1.9 becomes 1, but -1.1 becomes -2. I was not expecting to be using zero-padding on negative numbers; if you have an application that would require that, then you may want to modify the code to accommodate that. Note also that the sign ("-") counts as a character after a negative number is converted to a string. If you want the zero padding of a negative number to be calculated without including the "-" sign in the number of characters, the code would have to be modified to account for that.

No comments: