Philippe
Philippe

Reputation: 111

powershell arrays add-member - looking for elegant code

I have a pretty basic PowerShell array: $TestArray with 2 text columns: Price and TimeStamp (that's the way I get them, nothing to be done about this):

Price  TimeStamp       
-----  ----------------       
0.0567 1542056680.72746
0.0567 1542056650.34414
0.0555 1542056197.46668
0.0551 1542056167.28967

I would like, in a single PowerShell line to add a rounded Time2 value

$Time2 = [math]::Round($TestArray.TimeStamp)

The code I am thinking of is:

 $TestArray | Add-Member -Name Time2 -MemberType NoteProperty -Value { [math]::Round($Table.TimeStamp) }

Of course, I can do a ForEach loop; it would take care of it easily, but I would like to achieve this in this single line of code.

Any idea ?

Cheers, Philippe

Upvotes: 3

Views: 689

Answers (3)

mklement0
mklement0

Reputation: 440637

Mathias R. Jessen's answer directly solves your problem, by creating a script property (type ScriptProperty) that dynamically calculates its value from the enclosing object's .TimeStamp property.

  • The advantage of this approach is that even later changes to .TimeStamp will correctly be reflected in .Time2, albeit at the cost of having to calculate the value on every access.

Manuel Batsching's answer offers a Select-Object-based alternative that creates static note properties (type NoteProperty), as you've originally attempted yourself.

  • The advantage of this approach is that you incur the cost of calculation only once, but later changes to .TimeStamp won't be reflected in .Time2.

To offer faster PSv5+ alternatives (a single method call each, spread across multiple lines for readability):

# ScriptProperty - dynamic
$TestArray.ForEach({ 
  $_.psobject.properties.Add(
    [psscriptproperty]::new('Time2', { [math]::Round($this.TimeStamp) })
  ) 
})
# NoteProperty - static
$TestArray.ForEach({ 
  $_.psobject.properties.Add(
    [psnoteproperty]::new('Time2', [math]::Round($_.TimeStamp))
  ) 
})

The above solutions use the PSv4+ .ForEach() collection method and the PSv5+ static ::new() type method for calling constructors.


Finally, re one-liner:

The following foreach-loop based note-property solution would have solved your problem too, and would have been faster; while it is spread across multiple lines for readability here, it also works as a one-liner:

foreach ($el in $TestArray) { 
  Add-Member -InputObject $el -Name Time2 -MemberType NoteProperty `
             -Value ([math]::Round($el.TimeStamp)) 
}

Generally, while the pipeline often enables more elegant, single-command solution, that - unfortunately - comes at the expense of performance.

Upvotes: 5

Manuel Batsching
Manuel Batsching

Reputation: 3616

Alternatively you can achieve the same with Select-Object and a custom property:

$TestArray | Select-Object *,@{ n='Time2';e={ [math]::Round($_.TimeStamp) }}

Upvotes: 2

Mathias R. Jessen
Mathias R. Jessen

Reputation: 175084

Change the member type to ScriptProperty and refer to the individual array item as $this:

$TestArray |Add-Member Time2 -Value { [math]::Round($this.Timestamp) } -MemberType ScriptProperty

Worth noting that in this example, the pipeline itself acts as a foreach loop, unravelling the array and binding each individual item to Add-Member

Upvotes: 1

Related Questions