53
\$\begingroup\$

What general tips do you have for golfing in Windows PowerShell? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to PowerShell (e.g. "remove comments" is not an answer). Please post one tip per answer.

—taken nearly verbatim from marcog's question.

\$\endgroup\$
1
  • 6
    \$\begingroup\$ When I googled "PowerShell golf" this was the first hit! \$\endgroup\$
    – Matt
    Commented Mar 14, 2016 at 15:53

51 Answers 51

1
2
4
\$\begingroup\$

Reversing an array

Comes in handy in a lot of challenges where the output is mirrored in some fashion.

Suppose you have

$a=1,2,3,4,5

The traditional reversal method is long, boring, and doesn't leave the array on the pipeline for immediate use.

[array]::reverse($a)

Indexing directly into the array in reverse order saves a few bytes, since it leaves the results on the pipeline, but is still rather long:

$a[($a.count-1)..0]

Instead, try a loop

$a|%{$a[-++$i]}
\$\endgroup\$
1
  • \$\begingroup\$ the reverse indexing works well when there's an upper bound for the count \$\endgroup\$
    – Joey
    Commented Oct 25, 2017 at 9:04
4
\$\begingroup\$

Starting with PowerShell Core 6, you can also use ranges for characters:

'a'..'z'

which can replace the much more cumbersome

0..25|%{[char]($_+97)}
\$\endgroup\$
2
  • \$\begingroup\$ Oh, this is going to come in so handy. \$\endgroup\$ Commented Nov 29, 2017 at 13:34
  • 2
    \$\begingroup\$ [char[]](65..90) is also a handy way to generate the alphabet \$\endgroup\$
    – Veskah
    Commented Jun 21, 2019 at 15:06
4
\$\begingroup\$

Use variables to store .NET names

As outlined by cogumel0 in this answer, you can use variables to store .NET type names. For example, changing

param($a)$a-[math]::pow(2,[math]::floor([math]::log($a,2)))

into

param($a)$a-($m=[math])::pow(2,$m::floor($m::log($a,2)))

saved 3 bytes in this example.

\$\endgroup\$
0
4
\$\begingroup\$

SLS instead -match

You can use an sls cmdlet (alias for Select-String) with |% M* as shortcut for property Matches instead a -match operator with a $Matches automatic variable. Compare:

$args|sls 'pattern'|% M*

vs.

if("$args"-match'pattern'){$Matches}


You can also use sls with -a parameter (-AllMatches) to find all matches:

$args|sls 'pattern'-a|% M*


Also, you can find all matched lines in a multiline string without explicitly line breaking. It need to use a regexp option ?(m). Compare:

$args|sls '(?m)^pattern$'-a|% M*

vs.

$args-split"``n"|%{$_-match'^pattern$';$Matches}

\$\endgroup\$
4
\$\begingroup\$

Rather than using [bool] for boolean conversion you can instead use !!

1         # 1
[bool]1   # True
!!1       # True

0         # 0
[bool]0   # False
!!0       # False
!!$null   # False
\$\endgroup\$
2
  • 1
    \$\begingroup\$ use [int] as is. the !! is redundant for [int]. use ! and !! for [object] to [bool] conversation. [string] -is [object] :) \$\endgroup\$
    – mazzy
    Commented Apr 7, 2021 at 16:06
  • 1
    \$\begingroup\$ @mazzy This was more meant for in a case where you are golfing something and the result has to be True or False so the output of 1 would not work, I agree with you though :-) \$\endgroup\$ Commented Apr 7, 2021 at 22:25
3
\$\begingroup\$

A simple one but control-flow statements (as opposed to pipelines), such as if, for, and while, do not need a semi-colon after their braces.

E.g.

# Control-flow statements (All are valid)
if(...){...}"foo"
for(...){...}"foo"
while(...){...}"foo"
switch(...){...}"foo"

# Pipelines
1..2|%{...}"bar" #This will throw an error.
1..2|%{...};"bar" #This will work.

A free byte that's easy to miss.

\$\endgroup\$
2
  • \$\begingroup\$ Good point. I've been using this for ages. I've also taken the liberty of using the correct terms in your answer; I hope you don't mind. \$\endgroup\$
    – Joey
    Commented Oct 26, 2018 at 7:29
  • \$\begingroup\$ @Joey S'all good. \$\endgroup\$
    – Veskah
    Commented Oct 26, 2018 at 19:59
3
\$\begingroup\$

When declaring a hard-coded list of strings, depending on the size of the strings and the number of them, it's often shorter to split a single string up by spaces, since space-delimited splitting doesn't require a delimiter provided as a parameter

For example,

Instead of

$numbers='one','two','three','four','five','six','seven','eight','nine','ten'

Do

$numbers=-split'one two three four five six seven eight nine ten'

You can further delimit with another character and split twice to hold shortened hard-coded multidimensional arrays

\$\endgroup\$
1
  • 4
    \$\begingroup\$ $numbers=echo one two three four five six seven eight nine ten ;) \$\endgroup\$
    – briantist
    Commented Dec 27, 2018 at 20:58
3
\$\begingroup\$

Use $x-shr$k if you want to get the result of \$ \big\lfloor{x\over2^k}\big\rfloor \$.

The biggest application of this is getting the truncated int result of halving a positive number without having to deal with Banker's rounding.

Works on ints and floats, however it will cast floats to ints before shifting, resulting in the same banker problem as before. Whether or not this is acceptable will obviously depend. Below is a sample script that shows the results of -5..10. 3/2 and 7/2 demonstrate the difference in return value.

Try it online!

Inspired by this answer

\$\endgroup\$
1
  • \$\begingroup\$ note that this might not be as useful for negative numbers \$\endgroup\$
    – ASCII-only
    Commented Mar 11, 2019 at 3:25
2
\$\begingroup\$

If you need an if/else at the end of the program (maybe to handle a special case differently), then instead of

if(foo){a}else{b}

do

if(foo){a;exit}b

which saves a character.

\$\endgroup\$
2
\$\begingroup\$

Declaring an anonymous function should come up alot. There are multiple variations of this that are covered here https://stackoverflow.com/questions/10995667/lambda-expression-in-powershell involving scripts blocks. I use a similar one to this

$a={iex([int[]][char[]]$args[0]-join"+")}
&$a 'abcd'

This would save a few character from declaring a function with a name and goes better if used more than once. The one above converts a string into a char array then into an int array. Then creates a string joined with + so that Invoke-Expression will add all the values.

\$\endgroup\$
1
2
\$\begingroup\$

Get Length of elements of an array

You can write the name immediately after the point to get the value of the property or method.

$a=gci
$a.fullName

compare to:

$a|%{$_.fullName}

Both expressions returns array like this:

C:\Archive
C:\PerfLogs
C:\Pictures
C:\Program Files
C:\Program Files (x86)
C:\Users
C:\Windows

There is a property Length which is defined for the array itself. So the Powershell expression $a.Length returns the number of elements in an array. You can use the Shortening Names to get length of elements:

$s=@('22','55555','7777777')
$s|% Length       # 11 bytes

or shortcut:

$s|% Le*          # 8 bytes.

Compare to:

$s|%{$_.Length}   # 15 bytes

Result is the array 2,5,7, not the number 3.

\$\endgroup\$
2
\$\begingroup\$

Use the new() method instead of New-Object

For example, instead of doing the following (37 bytes):

New-Object Drawing.Bitmap(1024,1024)

Try (32 bytes):

[Drawing.Bitmap]::new(1024,1024)

Kind of an edge case, but you can do this pretty much anytime you initialize a new .NET object. Sadly, there's no equivalent for COM objects so you still have to use New-Object for those :P

\$\endgroup\$
2
\$\begingroup\$

String multiplication:

"#"*5 ==> #####
\$\endgroup\$
2
\$\begingroup\$

If a string is used in an arithmetic operation, it is implicitly converted to a number. What's interesting is that this conversion supports all prefixes and suffixes of PowerShell numeric literals. So you can do:

"0x$h"  # convert hex string to number
"0b$b"  # convert binary string to number

The n suffix is useful to create large BigInts like powers of ten:

'9'*50+'n'
"1$('0'*50)n"
'10n*'*50+1|iex
(0n)::Pow(10,50)
[bigint]::Pow(10,50)

You can also combine 0x and 0b with n, or use suffixes like KB. The resulting string must be used in an arithmetic operation to be converted, for example by subtracting zero.

\$\endgroup\$
1
\$\begingroup\$

If you're calling a command with a string argument, you don't need quotes unless there are spaces:

gci MyPath
gci 'My Path'

But that space cost you 2 extra bytes. You can save a single byte by escaping the space without quotes:

gci My` Path

This is only worth it when there's a single space, because there's no advantage with 2 or more.

\$\endgroup\$
1
\$\begingroup\$

If a path contains more than 1 space, you can do :

gci C:\My*Example*Path

instead of

gci 'C:\My Example Path'

or

gci C:\My` Example` Path
\$\endgroup\$
1
  • \$\begingroup\$ Useful to know, although it risks picking up a different but similar path. And I'm not sure how many code golf challenges involve the file system, as I'd be very wary of testing any solution that might be clobbering my system. If you wrote this answer as a continuation of this one, the gci was only for illustrative purposes of a command that may receive an argument with spaces. Not an actual file system golfing trick. \$\endgroup\$
    – Joey
    Commented Oct 10, 2018 at 5:12
1
\$\begingroup\$

Use Uppercase over lowercase when dealing with ASCII values

When given the choice, it's usually better to deal entirely with Uppercase. This is because their range is 65..90 vs lowercase's 97..122. For example, the one-liner to generate ABCD...Z in PS v5

-join([char[]](65..90))
-join([char[]](97..122))

This still applies when normalizing mix-case because using the |% short-property trick, ToUpper and ToLower shorten to *per and *wer, respectively.

"Lower to upper: "+-join([char[]](97..122))|% *per
"Upper to lower: "+-join([char[]](65..90))|% *wer

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Short way to convert bits to integer:

if you trust that $args contains int only (see Joey's comments below):

$args|%{$r+=$_+$r};$r

if you assume that $args is an array of int or string representation:

$args|%{$r+=+$r+$_};$r

$args is an array of char 48, 49:

$args|%{$r+=+$r+"$_"};$r

A sliding byte containing 8 bits from the array of int:

$args|%{($r=2*$r%256+$_)}

Compare to:

[Convert]::ToInt64($args,2)
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Any reason why you're using $r+=+$r+$_ instead of just $r+=$_+$r? Coercion should work the same for the $null case. \$\endgroup\$
    – Joey
    Commented Jan 13, 2020 at 11:37
  • \$\begingroup\$ Good point. The +$r guarantees that $r will be converted $r to an integer. because the array of int was specified as an incoming condition, you are right: it should be $args|%{$r+=$r+$_};$r. Thanks \$\endgroup\$
    – mazzy
    Commented Jan 13, 2020 at 13:00
  • \$\begingroup\$ That's why I proposed $_+$r, as $_ is already numeric and arithmetic operators convert the right operand to the type of the left. If $r were a string $r+$_ would not do the same as +$r+$_. \$\endgroup\$
    – Joey
    Commented Jan 13, 2020 at 14:10
  • \$\begingroup\$ $args is input parameters. I'm afraid to trust the input parameters so much :) Text fixed. Joey, freely edit the text as you wish. Thanks. \$\endgroup\$
    – mazzy
    Commented Jan 13, 2020 at 14:50
1
\$\begingroup\$

Utilize subtraction and booleans in comparisons

If you are in need of an operation like -ne and -gt and you are counting down, you can use the subtraction operator, -, instead of using an extra two bytes

Example:

for($i=40;$i-10;$i--){$i}                                         # 40...11
for($i=40;$i-gt10;$i--){$i}                                       # 40...11
for($i=40;$i-ne10;$i--){$i}                                       # 40...11
"$(for($i=40;$i-10;$i--){$i})"`
-eq"$(for($i=40;$i-gt10;$i--){$i})"`
-eq"$(for($i=40;$i-ne10;$i--){$i})"                               # true
\$\endgroup\$
1
\$\begingroup\$

If you're tackling a drawing/rendering problem that involves creating, manipulating, and then printing a 2D array, I've found it shorter character-wise to instead deal with a 1D array that you access with $arr[$y*$width+$x] rather than $arr[$y][$x].

It's very simple and short to set up the array with blanks or zeroes with something like

$arr=@(" ")*$width*$height

Half the reason for using 1D arrays is that initialization is much shorter.

To easily print the 1D array as a "2D" array to console, you can use a width-based regular expression to split your array into rows. This will join your array into a string, and then split the string into equally sized chunks and print them in order, effectively printing your 1D array as a 2D array:

-join$arr-split"(.{$width})"|?{$_}
\$\endgroup\$
0
\$\begingroup\$

Fake ternary operator for recursive invocations

If the fake ternary operator returns a code block and a function, it can be used to perform recursive invocations:

$f={&($f,{result})[condition]}

Notice the {} around the result and the & before the array instead of before the function variable $f

Example, a recursive function that returns only when $r has the value 1:

$f={if($r=(Get-Random)%2){$r}else{&$f}} # with an if else
$f={&($f,{$r})[($r=(Get-Random)%2)]}    # with a fake ternary operator recursive invocations
\$\endgroup\$
1
  • \$\begingroup\$ Example \$\endgroup\$
    – Julian
    Commented Nov 15 at 8:00
1
2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.