I am fairly new to PowerShell programming, so need help with set of strings I have as described below:
"14-2-1-1"
"14-2-1-1-1"
"14-2-1-1-10"
I want to pad zero to each number in between -
if that number is between 1
and 9
. So the result should look like:
"14-02-01-01"
"14-02-01-01-01"
"14-02-01-01-10"
I came up with the following code but was wondering if there is a better/faster solution.
$Filenum = "14-2-1-1"
$hicount = ($Filenum.ToCharArray() | Where-Object{$_ -eq '-'} | Measure-Object).Count
$FileNPad = ''
For ($i=0; $i -le $hicount; $i ) {
$Filesec= "{$i}" -f $Filenum.split('-')
If ([int]$Filesec -le 9)
{
$FileNPad = "$FileNPad-" "0" "$Filesec"
}
Else
{
$FileNPad="$FileNPad-$Filesec"
}
}
$FileNPad = $FileNPad.Trim("-"," ")
CodePudding user response:
Instead of trying to manually keep track of how many elements and inspect each value, you can simply split on -
, padleft, then join back together with -
"14-2-1-1","14-2-1-1-1","14-2-1-1-10" | ForEach-Object {
($_ -split '-').PadLeft(2,'0') -join '-'
}
Which outputs
14-02-01-01
14-02-01-01-01
14-02-01-01-10
CodePudding user response:
I'd be inclined to go with something like Doug Maurer's answer due to its clarity, but here's another way to look at this. The last section below shows a solution that might be just as clear with some advantages of its own.
Pattern of digit groups in the input
Your input strings are composed of one or more groups, where each group...
- ...contains one or more digits, and...
- ...is preceded by a
-
or the beginning of the string, and... - ...is followed by a
-
or the end of the string.
Groups that require a leading "0"
to be inserted contain exactly one digit; that is, they consist of...
- ...a
-
or the beginning of the string, followed by... - ...a single digit, followed by...
- ...a
-
or the end of the string.
Replacing single-digit digit groups using the -replace
operator
We can use regular expressions with the -replace
operator to locate that pattern and replace the single digit with a "0"
followed by that same digit...
'0-0-0-0', '00-00-00-00', '1-2-3-4', '01-02-03-04', '10-20-30-40', '11-22-33-44' |
ForEach-Object -Process { $_ -replace '(?<=-|^)(\d)(?=-|$)', '0$1' }
...which outputs...
00-00-00-00
00-00-00-00
01-02-03-04
01-02-03-04
10-20-30-40
11-22-33-44
As the documentation describes, the -replace
operator is used like this...
<input> -replace <regular-expression>, <substitute>
The match pattern
The match pattern '(?<=-|^)(\d)(?=-|$)'
means...
(?<=-|^)
: A zero-width positive lookbehind assertion for either-
or the beginning of the string- In other words, match but don't capture either
-
or the beginning of the string
- In other words, match but don't capture either
(\d)
: A single digit, captured and made available with the replacement substitution$1
(?=-|$)
: A zero-width positive lookahead assertion for either-
or the end of the string- In other words, match but don't capture either
-
or the end of the string
- In other words, match but don't capture either
The replacement pattern
The replacement pattern '0$1'
means...
- The literal text
'0'
, followed by... - The value of the first capture (
(\d)
)
Replacing single-digit digit groups using [Regex]::Replace()
and a replacement [String]
Instead of the -replace
operator you can also call the static
Replace()
method of the [Regex]
class...
'0-0-0-0', '00-00-00-00', '1-2-3-4', '01-02-03-04', '10-20-30-40', '11-22-33-44' |
ForEach-Object -Process { [Regex]::Replace($_, '(?<=-|^)(\d)(?=-|$)', '0$1') }
...and the result is the same.
Replacing digit groups using [Regex]::Replace()
and a [MatchEvaluator]
A hybrid of the regular expression and imperative solutions is to call an overload of the Replace()
method that takes a [MatchEvaluator]
instead of a replacement [String]
...
# This [ScriptBlock] will be passed to a [System.Text.RegularExpressions.MatchEvaluator] parameter
$matchEvaluator = {
# The [System.Text.RegularExpressions.Match] parameter
param($match)
# The replacement [String]
return $match.Value.PadLeft(2, '0')
}
'0-0-0-0', '00-00-00-00', '1-2-3-4', '01-02-03-04', '10-20-30-40', '11-22-33-44' |
ForEach-Object -Process { [Regex]::Replace($_, '(\d )', $matchEvaluator) }
This produces the same result as above.
A [MatchEvaluator]
is a delegate that takes a Match
to be replaced ($match
) and returns the [String]
with which to replace it (in this case, the matched text left-padded to two digits). I think this is a much more compelling solution than regular expressions alone because it's the best of that and the imperative world:
- It uses a simple regular expression pattern to locate digit groups in the input string
- It uses a simple
[ScriptBlock]
to transform digit groups in the input string - By not splitting the input string apart it does not create as much intermediate string and array garbage
- Whether this potential performance improvement is overshadowed by using regular expressions at all, I can't say without benchmarking