Tips for golfing in PowerShell

46

11

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.

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

3When I googled "PowerShell golf" this was the first hit! – Matt – 2016-03-14T15:53:33.243

Answers

24

Powers of 10 literals with scientific notation:

4e6 = 4000000

Powers of 2 literals:

4KB = 4096
4MB = 4194304
4GB = 4294967296

# TB and PB suffixes also exist, but less useful for golf.

Could come in handy.

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

19

If you need to run a loop, and you know exactly how many times it needs to run every time, consider piping an array of contiguous integers into ForEach-Object via the % alias instead of using for.

for($x=1;$x-le10;$x++){...}

vs

1..10|%{...}

Iszi

Posted 2011-01-29T13:24:06.487

Reputation: 2 369

18

You can skip spaces a lot in PowerShell. If it feels like it might not be needed, it quite possibly isn't. This is particularly useful in comparisons.

Example:

$x-eq$i-and$x-ne9

vs.

$x -eq $i -and $x -ne 9

If you need to branch your script based on the result of a single test which may have multiple outcomes, switch can sometimes match or beat an if statement.

Example (switch vs. if/else - tie):

if($x%2-eq0){'even'}else{'odd'}

vs.

switch($x%2){0{'even'}1{'odd'}}

Or (switch vs. if/elseif/else - switch wins by 15):

if($x%2-eq0){'even'}elseif($x%2-eq1){'odd'}else{'error'}

vs.

switch($x%2){0{'even'}1{'odd'}2{'error'}}

If the switch is actually based on certain math results, like the modulo operation above, you can replace the switch entirely with an array. Here, it saves another 13 characters and is even shorter than the original two-option if/else statement. (Thanks to Danko Durbic for this bit.)

('even','odd','error')[$x%2]

If you will be using a particular command a lot, especially one without a pre-existing short-hand alias, set up a single-character alias early on.

Example:

nal g Get-Random;g 10;g 10;g 10

vs.

Get-Random 10;Get-Random 10;Get-Random 10

Iszi

Posted 2011-01-29T13:24:06.487

Reputation: 2 369

('even','odd')[$x%2] FYI. – TheIncorrigible1 – 2018-11-16T02:09:01.107

15

Encapsulating the command that defines a variable in parenthesis allows you to feed the variable's definition directly to other commands.

For example, you can set $x and then set $y based on the value of $x in one shot with this:

$y=($x=1)+1

Instead of this:

$x=1;$y=$x+1

You can set $h and output it with this:

($h='Hello World!')

Instead of this:

$h='Hello World!';$h

Iszi

Posted 2011-01-29T13:24:06.487

Reputation: 2 369

1This is especially useful for calling methods on objects. ($x=New-Object Windows.Forms.Form).Controls.Add($t) – SpellingD – 2013-11-26T21:49:37.883

14

A switch can act like a loop, when given an array. For example:

$FooBarMeh='a','b','c'
switch ($FooBarMeh)
{
    'a'{'FOO'}
    'b'{'BAR'}
    default{'MEH'}
}

Will output:

FOO
BAR
MEH

I'm not totally sure where this will be useful, but I expect it will be handy for someone some time.

Iszi

Posted 2011-01-29T13:24:06.487

Reputation: 2 369

This is... damn weird. An array shouldn't match with a char, but it does. – cat – 2016-04-24T14:56:41.917

2It is handy every time you need a ForEach-Object and a switch within that. It's for example (in real-world) code very nice for writing quick parsers of text files where you need to do different things depending on which regex a line matches. – Joey – 2013-11-24T12:02:16.307

@Joey That's what switch -File $path is for – TheIncorrigible1 – 2018-11-16T02:10:22.273

Not everything that's parsed happens to be a file. – Joey – 2018-11-17T15:17:30.537

12

Replace [math]::pow with multiplication. Instead of

[math]::pow($a,$b)

you can write

"$a*"*$b+1|iex

This works for integer exponents >= 0.

Danko Durbić

Posted 2011-01-29T13:24:06.487

Reputation: 10 241

I needed to see this answer to understand iex ... Alias for Invoke-Expression

– HeatfanJohn – 2019-01-17T16:57:03.907

11

Comparison operators work on collections of values by returning matching values:

1..5 -gt 2

will yield 3, 4 and 5. In some cases this can help to save an otherwise longer |?{$_...}.

-match is a comparison operator too.

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

1Note that you don't need spaces in that example 1..5-gt2 – Matt – 2016-03-14T15:34:49.867

1

I know; this was more about showing the technique. Spaces are in a separate answer.

– Joey – 2016-03-14T18:08:01.610

11

Use aliases whenever possible. There are a bunch of useful ones:

?        Where-Object
%        ForEach-Object
gu       Get-Unique
sort     Sort-Object
iex      Invoke-Expression

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

11

Want to find the maximum or minimum of a collection of values? Tried

(...|measure -ma).Maximum

or

(...|measure -mi).Minimum

already?

Just sort and use the last or first item:

(...|sort)[-1]  # maximum
(...|sort)[0]   # minimum

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

1And, if you know the length of the collection of values and it's less than 10... – wizzwizz4 – 2016-01-10T14:12:59.677

@wizzwizz4: Oh, maximum lengths can often be employed creatively, not necessarily being 10. Although for this particular case I don't recall an instance where it ever helped. – Joey – 2016-01-10T14:53:21.230

1I mean, if the final item is known to be 0, 1, 2, 3, 4, 5, 6, 7, 8 or 9, it would save a byte to write the known length instead of -1. – wizzwizz4 – 2016-01-10T14:55:42.660

10

Shortening Property Names

Sadly, unlike parameters, properties/methods (anything accessed with a dot .) cannot usually be shortened down to its unambiguous form.

But certain cmdlets can operate on property names and take wildcards, and there are little-known parameter sets of % and ? that can be useful.

Usually we pass in a scriptblock and refer to the item with $_, but there's another form of these that takes a property name, and it accepts a wildcard.

$o|select Le*  
$o|%{$_.Length}

With a property like .Length we can't use the v3 magic that would normally work on an array because Length is a property of the array itself, so the above two could be used to get the lengths of the individual members. The select comes in a little bit shorter.

But % can take a property name directly and return that value:

$a|% Length

Which can be shortened with wildcards. The wildcard must resolve to a single property (or method, more on that later), so it will throw a helpful error if it doesn't, indicating exactly which members it could resolve to.

In the case of Length, Le* is typically the shortest. Even on a single string, this method is 1 byte shorter than just using the property.

$a.Length                # 9   #(doesn't work on array)
$a|%{$_.Length}          # 15
$a|% Le*                 # 8

But depending on what you're doing with this, this can be worse. You can do $a.Length*5 but to do it with the pipeline expression you'd have to wrap it ($a|% Le*)*5; might still be worth it if it's against an array, but the point is it's not always appropriate as a straight substitution.

It works with methods too, and you can leave off the () which makes a full name the same length, but same restriction as above about sometimes having to wrap it. The method must have an overload that takes no parameters (you can pass arguments by placing them after the method name, which is really nice):

$a.ToUpper()             # 12
$a|% *per                #  9

With arguments:

'gaga'-replace'g','r'    # 21
'gaga'|% *ce g r         # 16

These aren't strictly the same in that the -replace operator does a regex replace, but if you're just doing a string replace, it can (now) be shorter to use the method; it helps that the strings are cmdlet arguments instead of method arguments so they don't need to be quoted.

Where-Object Properties

? can take (partial) property names as well, and apply an "operator" to it (in the form of switch parameters). Again this can be shorter than using the standard Where-Object scriptblock approach if the property name is sufficiently long and unique.

$a|?{$_.Length-gt5}      # 19
$a|? Le* -GT 5           # 14

($a|% Le*)-gt5           # 14 - Lengths, not objs

briantist

Posted 2011-01-29T13:24:06.487

Reputation: 3 110

1One of the few places where whitespace is required. Nice find, though. I can think of several areas where this will shorten things. – AdmBorkBork – 2017-02-27T15:48:51.313

Nice edit Joey! Thanks. – briantist – 2017-02-27T15:56:24.327

Found you a way to get it shorter ;-) ... the sad thing about this is that as a whole it's a pipeline again, which limits its usefulness a bit. Or rather, there are a lot of places where you'd have to wrap it into parentheses again to get an expression. But few golf techniques are without trade-offs ... – Joey – 2017-02-27T15:56:35.330

@Joey yeah I mentioned that, it really does depend. If you're passing it along in the pipeline or just returning it it's great, otherwise it may need wrapping; even with wrapping it could be shorter in certain situations. – briantist – 2017-02-27T15:57:40.277

Also, one point where this breaks down: The automatic Count property introduced in PSv3 for any object. Doing ''.Count returns 1, but ''|% Count returns nothing since there isn't actually a Count property there. – Joey – 2017-02-27T16:03:22.500

@Joey yeah I did notice that.. but typically in golfing you're going to know when to use .Count or not, whether it's an array of single object, in advance, but that's a good thing to know. It could end up being confusing. – briantist – 2017-02-27T16:05:06.983

Used this on .ToString here to save a tonne, you can pass params through to methods by placing them after it.

– colsw – 2017-04-03T21:23:49.933

1@ConnorLSW ah yes I meant to update this as I'd since realized you can pass args that way, which greatly ups its usefulness. Great use of .ToString()! – briantist – 2017-04-03T21:29:33.160

2@briantist this has turned PowerShell into a much more competitive language, really cuts out some of the annoyingly verbose .Net calls. – colsw – 2017-04-04T08:19:27.837

@ConnorLSW I added an example with parameters too; if you have others please feel free to edit :) – briantist – 2017-04-04T14:25:08.910

9

Finding a sum the long way:

(...|measure -s).Sum

A shorter way:

...|%{$s+=$_};$s

And even shorter:

...-join'+'|iex

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

9

The Get verb is implied. This can shorten any Get-Frob to just Frob. Frequent contenders are date or random.

Note that this won't work properly in some cases because you might have GNU utilities in your path (or other native programs that clash). Order of command lookup in that case seems to prefer the native program before it considers cmdlets with the Get- removed:

PS Home:\> date

Freitag, 15. November 2013 07:13:45


PS Home:\> $Env:Path += ';D:\Users\Joey\Apps\GnuWin32\bin'
PS Home:\> date
Fr Nov 15 07:14:13 W. Europe Standard Time 2013

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

This doesn't quite always work out as expected. I have a script that starts with nal g Get-Random to save characters later on. Changing it to nal g Random causes the script to hang indefinitely (or, at least, take an inordinate amount of time to process - I haven't had the patience to wait for it to end, but it's taking several orders of magnitude longer than the original form before I abort). – Iszi – 2013-11-15T00:56:51.530

2Two short Measure-Command invocations (100 times Get-Random vs. random) tell me it's about 500 times slower. I didn't know that before, to be honest. But it's good to keep it in mind, especially in loops with many iterations. That being said, golfed code should be short, not fast (that being said, it sucks to have to wait two days for an answer to a Project Euler problem). – Joey – 2013-11-15T06:18:28.217

1

My problem was running 10,000 iterations of the Monty Hall scenario each requiring three iterations of Get-Random. A 50,000% increase in processing time, multiplied across 30,000 runs is pretty nasty.

– Iszi – 2013-11-15T06:23:03.453

Wow. It looks like the same probably holds true for any alias. Tested Get-Variable vs. gv and got a similar comparison. – Iszi – 2013-11-15T14:43:10.377

I suspect it is because of what I added in the answer this morning – the implied Get- seems to be after everything else in the command lookup list, which means everything on the PATH needs to be checked first. I cannot look into the implementation though, lest I want to be unable to continue working on Pash. – Joey – 2013-11-15T14:47:05.993

1That could be it. I did some math and figured my Monty Hall script should take about 1.5 hours (normally, 10-11 seconds) to run without Get-. That's a pretty gnarly bloat in run time for a savings of just 4 characters in length. – Iszi – 2013-11-15T14:49:17.427

Another interesting bit: An alias e.g.: nal g random seems to not be functional in PS 2.0. The alias gets created, but it's unresolvable when you try to use it - even though random works. The alias worked fine (minus the lag) in 4.0. – Iszi – 2013-11-15T14:54:37.877

9

Semicolons and line breaks are interchangeable. Golfed code is often more readable if not jammed into a single line. And the length is still the same (provided you use U+000A as line break which PowerShell handles without problems).

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

2Wait, we are worried about readability?! – Kevin Cox – 2013-11-22T15:50:32.863

4If it has no effect on the length ... why not? – Joey – 2013-11-22T17:02:40.097

Doesn't it technically make a difference since a semicolon is one less character than \r\n? Only in Unix it is a singular \n. – Vasili Syrakis – 2014-01-09T04:47:03.593

2@Vasili: You can save the files just fine with only U+000A between the lines. PowerShell won't complain. Which I wrote in the answer already, by the way. Line breaks are a property of the file, not of the operating system it is used on. No one says that you cannot use Unix line endings on Windows. – Joey – 2014-01-09T05:50:08.947

@Joey If you’ve used a fresh install of windows to do some quick programming, you’ll notice that Notepad doesn’t play nice with \x0a – Stan Strum – 2018-12-18T20:58:23.373

@StanStrum: So? Don't use Notepad, then, or wait for the next update of Windows 10 where Notepad will handle Unix line endings. If it bothers you too much, you can still use semicolons just fine. Besides, these days PowerShell also runs on other operating systems than Windows by now. – Joey – 2018-12-19T19:04:52.100

@Joey I didn’t mean it that way. I was just expressing how annoying \r is... it’s so archaic, too much so to be used pedantically imo – Stan Strum – 2018-12-19T19:05:58.110

All major operating systems have archaic remnants embedded into their design ;) – Joey – 2018-12-19T19:13:27.497

8

for loops can have anything between 0 and three statements in their header:

Endless loop:

for(){}

Loop with initialization:

for($n=0){}

Loop with initialization and end condition:

for($n=0;$n-lt7){}

In such cases the additional semicolons at the end may be omitted (it's explicitly stated in the language specification, so it's not an implementation detail) in contrast to C-like languages which always require exactly three statements.

This also makes while a bit shorter. Compare

while(...){}

and

for(;...){}

With the added bonus that you can stick in a previous line (if there is one) into the for as well without extra cost (and even saving a character).

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

8

Casting to string:

[string]$x

vs.

"$x"

Casting to string like this can also be used to flatten an array of strings, instead of joining it:

$a = @('a','b','c')
$a -join ' '

vs.

$a = @('a','b','c')
"$a"

Casting a string to a numeric type:

[int]$x     [float]$x

vs.

+$x

Also very useful to know that PowerShell always takes the type of the left operand to determine the final type of an expression and conversions to apply:

'1'+2    -> '12'
1+'2'    -> 3

which can help determining where needless casts are.

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

8

If you are assigning an array that you know will only have two values, don't use indexing.

Something like this:

$a="apple","orange"
$a[0] # apple
$a[1] # orange

Can easily be turned into this:

$a,$o="apple","orange"
$a # apple
$o # orange

This can also be useful for if you just need to the first element of an array:

$a,$b=1..10
$a # 1
$b # 2..10

SomeShinyObject

Posted 2011-01-29T13:24:06.487

Reputation: 953

In this case (with the strings) $a="apple";$o="orange" is identical in length. It's the longer arrays that can sometimes be optimised fairly nicely, e.g. by putting all elements in a string with a separator and then using -split (best to use whitespace as separator because then the unary -split will suffice). – Joey – 2014-06-02T06:03:02.567

6

Don't forget that you don't always need to provide the full name of a parameter, and some parameters are positional.

Get-Random -InputObject (0..10)

...can be trimmed to...

Get-Random -I (0..10)

...because "I", in this case, is enough to uniquely identify InputObject from the other valid parameters for this command.

You could trim it further to...

Get-Random (0..10)

...because InputObject is a positional parameter.

Piping is usually shorter than feeding objects as a parameter, especially when it can remove the need for parenthesis. Let's trim our random number generator further...

0..10|Get-Random

Also be on the lookout for other ways to accomplish the same thing, even if you can't change the command. For the above case, you could do this:

Get-Random 11

Or, incorporating another suggestion*:

Random 11

**Note: Omitting Get- from a command name can bloat the run time by about 50,000%. Not bad if you only need the command once, but be careful using it in long loops.*

And that's how can knock a simple command down to a third of its size.

Iszi

Posted 2011-01-29T13:24:06.487

Reputation: 2 369

6

When using a number as an argument to an operator that would otherwise require a string, you can use the number directly. Compare

...-join'0'

vs.

...-join0

Works with -split as well. The argument is always converted to a string first.

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

For reference, this works with -replace as well. – AdmBorkBork – 2017-10-24T13:23:54.240

-replace also works with no second argument wheb you want to remove matches, – Joey – 2017-10-25T09:05:29.243

6

Fake ternary operator. You can assign straight from an if statement:

$z=if($x-eq$y){"truth"}else{"false"}

But you can use a 2-element array and use the test to index into it. $falsey results get element 0, $truthy results take element 1:

$z=("false","true")[$x-eq$y]

NB. that this is really doing array indexing, and if the test results in a value which can be cast to an integer, you'll ask for an item outside the bounds of the array and get $null back, and will need to do !(test) to force cast the result to a bool, with the options reversed.

TessellatingHeckler

Posted 2011-01-29T13:24:06.487

Reputation: 2 412

The first snippet didn't work at one point in time (older PowerShell versions), if I remember correctly, making it necessary to wrap it into $(). There was essentially a distinction of using pipelines made from commands and statements as expressions. Seems that is gone by now, at least in PowerShell 5. – Joey – 2016-05-14T08:47:46.713

Additionally, if the array elements are non-static, both get parsed and processed as part of the array creation before the indexing happens and the result is assigned. For example.

– AdmBorkBork – 2016-05-25T20:12:47.150

5

Absolute value

With

$n=-123

Instead of

[math]::abs($n)

use

$n-replace'-'

Of course, the savings are cancelled if parentheses are needed.

Rynant

Posted 2011-01-29T13:24:06.487

Reputation: 2 353

Or if you need the result on the left side of an operator ;-) – Joey – 2013-11-15T16:44:13.120

5

If you need to silence errors, the obvious variant would be

try{ <# something that throws errors #> }catch{}

However, this is way too long. A shorter variant is to run the try block as a script block and just redirect the errors into an unset variable ($null would be the usual one, but that's still too long):

.{ <# something that throws errors #> }2>$x

This saves five valuable bytes (if not the world).

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

5

Use the $ofs special variable to change the Output Field Separator used when stringifying an array. Useful if you're needing to transform arrays to strings multiple times.

For example:

$a=1,2,3,4
$a-join',';$a-join','
$ofs=',';"$a";"$a"

Saves 2+n characters on the 2nd -join, where n is the length of the separator, and saves an additional 5+n for the 3rd -join and each thereafter.

AdmBorkBork

Posted 2011-01-29T13:24:06.487

Reputation: 41 581

1Sadly very rarely useful (at least in my golfing so far – I tend to strip it down to only a single join at the end). – Joey – 2016-03-04T20:07:40.093

5

Automatic variables have booleans for True and False as $true and $false but you can get similar results using the logical not operator ! and the integers 0 and 1( or any non-zero integer.)

PS C:\Users\matt> !1
False

PS C:\Users\matt> !0
True

Near all PowerShell expressions can be evaluated as booleans. So as long as you are aware of how certain data is evaluated you can get booleans and never need to explicitly cast them. Be aware of the LHS value when doing these.

  • Integer 0 is false and non-zero integers are evaluated to true.
  • non-zero length strings are true and empty or null (and nulls themselves) strings are false.

There are other examples but you can easily test by doing a cast

PS C:\Users\matt> [bool]@(0)
False

Matt

Posted 2011-01-29T13:24:06.487

Reputation: 1 075

1

Many other data types also have similar status. Uninitialized variables default to $null, which is falsey. The empty string (and variables set to the empty string) are falsey. Etc. This can then be used to shortcut indexing into an array (e.g., when doing an if/else), as was used in FizzBuzz.

– AdmBorkBork – 2016-03-14T16:45:38.640

@TimmyD Very true. Using ints is just shorter – Matt – 2016-03-14T16:53:50.237

@TimmyD I wanted to answer fizzbuzz until I saw yours.... Can't beat that .... at least not yet – Matt – 2016-03-14T16:55:39.690

Note that in many cases you don't actually need a bool. You can use 0 and 1 just as well. Implicit conversions help a lot in that regard (which you can often force with certain operators). – Joey – 2016-03-14T18:09:38.483

4

Consider storing repeated script blocks in variables, instead of using functions.

I was going to use this to save some characters in my Rock, Paper, Scissors implementation before I realized that re-writing the function as a variable made even the variable unnecessary. This could still be useful for other scripts though, where you're actually running the same code multiple times.

function Hi{echo 'Hello, World!'};Hi

vs.

$Hi={echo 'Hello, World!'};&$Hi

Iszi

Posted 2011-01-29T13:24:06.487

Reputation: 2 369

You could use $args to avoid the need for params; i.e. $x={$args[0]+2} (or even $x={2+"$args"}); may save a character or 2 in some circumstances. You can also combine this with another trick for multiple params: $x={$a,$b=$args;$a+$b+3} – JohnLBevan – 2017-01-01T20:35:01.163

2Useful for functions that do not take arguments. Otherwise the params(...) would take up more space than the function definition saves. Related: Use filter over function when you can do so. – Joey – 2013-11-26T21:57:18.187

4

You can use $s|% t*y instead [char[]]$s to split a string to char array. Given from TessellatingHeckler's answer: % t*y expands to | ForEach-Object -Method ToCharArray equiv. of "$args".ToCharArray()

For example, compare

$s|% t*y

and

[char[]]$s

and

$s.ToCharArray()

It's useful with $args especially: $args|% t*y

mazzy

Posted 2011-01-29T13:24:06.487

Reputation: 4 832

1That's quite neat. I've used the % trick for members a few times as well, but most of my golfing predates that feature ;-). It's quite a general technique as well: Try finding a letter/wildcard combination that matches the property/method you need (and is shorter than the real thing). – Joey – 2018-07-08T09:36:47.957

Another useful examples from the answer: $s|% *per for $s.toUpper() and $s|% *wer for $s.toLower(). I'm agree that's quite neat. – mazzy – 2018-07-08T12:19:26.360

One more example: |% t* "ddd\:hh\:mm\:ss" for [TimeSpan].toString("ddd\:hh\:mm\:ss") – mazzy – 2018-07-08T12:42:35.237

Well, that's just a waste of quotes, then; you don't need them here :-) – Joey – 2018-07-08T17:54:00.843

ok. the quote formatted as regular text. please feel free to make any changes – mazzy – 2018-07-08T19:29:43.757

4

Invoke-Expression and Get-Random can also get pipeline input instead of arguments.

For iex this allows to save parentheses on some expressions:

iex 1..5-join'+'   # won't work
iex(1..5-join'+')  # does work, but has extra parentheses
1..5-join'+'|iex   # doesn't need the parentheses

In case of random this allows a common case to be optimized a bit:

random -mi 10 31   # gets a random integer between 10 and 30
10..30|random      # much better :-)
(random 21)+10     # if needed in another expression that doesn't require extra
                   # parentheses

The latter way of using it simply selects an item from a list. The -c argument can be given to allow more than a single selection.

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

3

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]}

AdmBorkBork

Posted 2011-01-29T13:24:06.487

Reputation: 41 581

the reverse indexing works well when there's an upper bound for the count – Joey – 2017-10-25T09:04:13.637

3

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)}

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

Oh, this is going to come in so handy. – AdmBorkBork – 2017-11-29T13:34:04.467

1[char[]](65..90) is also a handy way to generate the alphabet – Veskah – 2019-06-21T15:06:12.517

3

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.

AdmBorkBork

Posted 2011-01-29T13:24:06.487

Reputation: 41 581

3

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.

Veskah

Posted 2011-01-29T13:24:06.487

Reputation: 3 580

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. – Joey – 2018-10-26T07:29:57.047

@Joey S'all good. – Veskah – 2018-10-26T19:59:18.087

3

Converting floating-point numbers to integers in PowerShell is a bit of a minefield. By default the conversion does Bankers Rounding which doesn't always trim off the decimal and leave the smaller whole number, or always round .5 up to the next number like people do casually, it rounds evens one way and odds another way - this can be surprising, e.g.

PS C:\> [int]1.5
2

PS C:\> [int]2.5
2

and break codegolf calculations. Many other common languages do truncation-rounding, therefore golf questions often require truncation. You might reach for [Math]::Floor() as the next best thing, but beware this only behaves the same as truncation for positive numbers, but it takes negative numbers lower - further away from zero. [Math]::Truncate() is what you need to bring PS behaviour in line with other language's default rounding, but it's a lot of characters.

Regex replacing digits after the decimal point can help save a couple of characters:

[Math]::Truncate(1.5)
[Math]::Floor(1.5)
1.5-replace'\..*'

[Math]::Truncate($a)
[Math]::Floor($a)
$a-replace'\..*'
$a.split('.')[0]        # literal character split, not regex pattern split

TessellatingHeckler

Posted 2011-01-29T13:24:06.487

Reputation: 2 412

3

Use Boolean logic in place of if-counters in a loop

Suppose you're adding all even numbers from 1 to 10 ... 2+4+6+8+10=30

1..10|%{if($_%2-eq0){$o+=$_}};$o

You could use Boolean negation to shorten it to

1..10|%{if(!($_%2)){$o+=$_}};$o

to save a byte, but how about instead use implicit casting of Booleans to ints, and roll the conditional into the accumulator

1..10|%{$o+=$_*!($_%2)};$o

Saves 6 bytes in this example.

AdmBorkBork

Posted 2011-01-29T13:24:06.487

Reputation: 41 581

2Jesus christ. I'm planning to do this in my production code at work, my colleagues will love me for it. – Chavez – 2016-11-04T09:13:16.707

2

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}

mazzy

Posted 2011-01-29T13:24:06.487

Reputation: 4 832

2

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

Tor

Posted 2011-01-29T13:24:06.487

Reputation: 201

1$numbers=echo one two three four five six seven eight nine ten ;) – briantist – 2018-12-27T20:58:12.137

2

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.

Joey

Posted 2011-01-29T13:24:06.487

Reputation: 12 260

2

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.

Matt

Posted 2011-01-29T13:24:06.487

Reputation: 1 075

I realize now that this is similar to http://codegolf.stackexchange.com/a/15432/52023 but this one shows use with arguments and input.

– Matt – 2016-03-14T15:33:03.227

1

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

Veskah

Posted 2011-01-29T13:24:06.487

Reputation: 3 580

note that this might not be as useful for negative numbers – ASCII-only – 2019-03-11T03:25:00.193

1

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.

mazzy

Posted 2011-01-29T13:24:06.487

Reputation: 4 832

1

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!

Veskah

Posted 2011-01-29T13:24:06.487

Reputation: 3 580

1

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

Gabriel Mills

Posted 2011-01-29T13:24:06.487

Reputation: 778

1

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)

mazzy

Posted 2011-01-29T13:24:06.487

Reputation: 4 832

1Any reason why you're using $r+=+$r+$_ instead of just $r+=$_+$r? Coercion should work the same for the $null case. – Joey – 2020-01-13T11:37:21.857

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 – mazzy – 2020-01-13T13:00:51.500

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+$_. – Joey – 2020-01-13T14:10:28.487

$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. – mazzy – 2020-01-13T14:50:03.777

0

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.

briantist

Posted 2011-01-29T13:24:06.487

Reputation: 3 110

0

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

TDiff

Posted 2011-01-29T13:24:06.487

Reputation: 1

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.

– Joey – 2018-10-10T05:12:02.277