Perl6 String Concatenation Hangs - concatenation

I'm trying to concatenate a string with Perl6 thus:
my $cmd = "databricks jobs --job-id 37 --notebook-params '\{";
put $cmd;
$cmd ,= "\"directory\": \"$s3-dir\",";
put $cmd;
However, the output hangs after the ,= operator. I'm assuming that this works the same way from the Perl .= operator.
https://docs.perl6.org/language/operators
Why is this job hanging? how can I properly concatenate the string?

There is no ,= or ~= operators in Perl6.
Those are instances of the = meta operator combined with another infix operator.
# these are all functionally equivalent:
$a = $a ~ 'a';
$a ~= 'a';
$a [~]= 'a';
$a [&[~]]= 'a';
$a [&infix:<~>]= 'a';
$a = infix:<~> $a, 'a'; # use an operator as a subroutine
There is a history of C-like languages having operators like +=.
It would get a little tiring having to define new operators like that for every infix operator.
In Perl6 you can also easily define new operators.
So it has = as a meta-operator that will automatically work with all infix operators.
sub infix:<foo> (\l,\r){…}
$a = $a foo 3;
$a foo= 3;
$a [foo]= 3;
If you want to use an operator like +=, just look for the base operator that matches what you want and add =.
If you want to do string concatenation, the base operator is the infix ~ operator.
(Which looks a lot like the string coercion prefix operator ~.)
$a = $a ~ 'a';
$a ~= 'a';
If you want to do Set difference, the base operator is (-).
$a = $a (-) 3;
$a (-)= 3;
You can add any number of [] surrounding an infix operator.
(It needs space before it so that it isn't confused with postcircumfix [])
$a - 3;
$a [-] 3;
$a [[-]] 3;
$a [[[-]]] 3;
Which can be useful to make sure that meta operators combine the way you want them too.
$a -= 3;
$a [-]= 3;
3 R-= $a; # $a = $a - 3;
$a [R-]= 3; # $a = 3 - $a;
3 R[-=] $a; # $a = $a - 3;
3 R[[R-]=] $a; # $a = 3 - $a;
This was extended so that [&…] where &… is the name of a function works as an infix operator.
sub bar (\l,\r){…}
# these are functionally identical.
$a = bar( $a, 3 );
$a = $a [&bar] 3;
$a [&bar]= 3;
When you used ,= you created a self-referential data structure.
(Note that say calls .gist, I added an extra .gist to be extra clear that I'm not printing a Str.)
my $c = 0;
# $c ,= 1;
$c = ($c,1);
say $c.gist;
# (\List_94195670785568 = (List_94195670785568 1))
say $c.WHICH;
# List|94195670785568
When you do something that coerces a List or Array into a Str, it follows the structure turning each part into a Str.
$c = ($c,…);
~$c;
# $c.Str
# | \___________________
# | \
# $c[0].Str ~ ' ' ~ $c[1].Str
# | \ \ \__________
# | | \ \__________________ 1.Str
# | | \
# | V \_____________
# | \
# $c[0].Str ~ ' ' ~ $c[1].Str
# | \ \ \__________
# | | \ \__________________ 1.Str
# | | \
# | V \_____________
# | \
# $c[0].Str ~ ' ' ~ $c[1].Str
# | \ \ \__________
# | | \ \__________________ 1.Str
# | | \
# | V \_____________
# | \
# …
It of course never finishes turning the first part into a Str.
The combination of , and = works safely with a Hash though.
(Which is what the docs show.)
my %c = a => 0;
%c ,= b => 1;
# %c = (%c, b => 1)
say %c.gist;
# {a => 0, b => 1}

There was some confusion in the manual.
my $cmd = "databricks jobs --job-id 37 --notebook-params '\{";
put $cmd;
$cmd ,= "\"directory\": \"$s3-dir\",";
put $cmd;
should be
my $cmd = "databricks jobs --job-id 37 --notebook-params '\{";
put $cmd;
$cmd ~= "\"directory\": \"$s3-dir\",";
put $cmd;
the proper way to concatenate an already declared variable in Perl6 is with ~=

Related

How to create an array like this in powershell?

I need to store data like below for TextToColumns Excel automation.
I need to implement Code-2 or Code-3 or Code-4 is that any way to achieve?
I have more than 350+ data so I cant use Code-1, that's not fair for me.
Code-1: working fine
$var = (1,2),(2,2),(3,2),(4,2),(5,2),(6,2)........(300,2)
$ColumnA.texttocolumns($colrange,1,-412,$false,$false,$false,$false,$false,$true,"|",$var)
Code-2: not Working
$var = #((1,2)..(300,2))
$ColumnA.texttocolumns($colrange,1,-412,$false,$false,$false,$false,$false,$true,"|",$var)
Code-3: not Working
$var = #()
#forloop upto 300
{ $var += ($i,2) }
$ColumnA.texttocolumns($colrange,1,-412,$false,$false,$false,$false,$false,$true,"|",$var)
Code-4: not Working
[array]$var = 1..300 | foreach-object { ,#($_, 2) }
$ColumnA.texttocolumns($colrange,1,-412,$false,$false,$false,$false,$false,$true,"|",$var)
I can't fully explain what happens here but I guess that it is related to the fact that the texttocolumns requires an (deferred) expression rather than an (evaluated) object.
Meaning that the following appears to work for the Minimal, Reproducible Example from #mclayton:
$Var = Invoke-Expression ((1..6 |% { "($_, `$xlTextFormat)" }) -Join ',')
And expect the following to work around the issue in the initial question:
$Var = Invoke-Expression ((1..300 |% { "($_, 2)" }) -Join ',')
Not an answer - just documenting some research to save others some time...
I can repro the issue here with the following code:
$xl = new-object -com excel.application;
$xl.Visible = $true;
$workbook = $xl.Workbooks.Add();
$worksheet = $workbook.Worksheets.Item(1);
$worksheet.Range("A1") = "aaa|111";
$worksheet.Range("A2") = "bbb|222";
$worksheet.Range("A3") = "ccc|333";
$worksheet.Range("A4") = "ddd|444";
$worksheet.Range("A5") = "eee|555";
$worksheet.Range("A6") = "fff|666";
which builds a new spreadsheet like this:
If you then run the following it will parse the contents of column A and put the results into columns B and C:
$range = $worksheet.Range("A:A");
$target = $worksheet.Range("B1");
# XlColumnDataType enumeration
# see https://learn.microsoft.com/en-us/office/vba/api/excel.xlcolumndatatype
$xlTextFormat = 2;
# XlTextParsingType enumeration
# see https://learn.microsoft.com/en-us/office/vba/api/excel.xltextparsingtype
$xlDelimited = 1;
# XlTextQualifier enumeration
# https://learn.microsoft.com/en-us/office/vba/api/excel.xltextqualifier
$xlTextQualifierNone = -4142;
$var = (1,$xlTextFormat),(2,$xlTextFormat),(3,$xlTextFormat),(4,$xlTextFormat),(5,$xlTextFormat),(6,$xlTextFormat);
# parse the values in A1:A6 and puts the values in a 2-dimensional array starting at B1
# see https://learn.microsoft.com/en-us/office/vba/api/excel.range.texttocolumns
$result = $range.TextToColumns(
$target, # Destination
$xlDelimited, # DataType
$xlTextQualifierNone, # TextQualifier
$false, # ConsecutiveDelimiter
$false, # Tab
$false, # Semicolon
$false, # Comma
$false, # Space
$true, # Other
"|", # OtherChar
$var # FieldInfo
);
which then looks like this:
However, if you change the declaration for $var to
$var = 1..6 | % { ,#($_, $xlTextFormat) };
you get the following error:
OperationStopped: The remote procedure call failed. (0x800706BE)
and the Excel instance terminates.
So there's something different about these two declarations:
$var = (1,$xlTextFormat),(2,$xlTextFormat),(3,$xlTextFormat),(4,$xlTextFormat),(5,$xlTextFormat),(6,$xlTextFormat);
$var = 1..6 | % { ,#($_, $xlTextFormat) };
but what that is eludes me :-S

Using Get-Random after using Where-Object creates a null valued expression

I'm trying to create a 10 character password that includes a mix of numbers, letters (uppercase and lowercase), and symbols.
Below is the script I am using in the function:
Function Get-TempPassword {
$TempPassword = $null
$ascii = $NULL;For ($a = 33;$a –le 126;$a++) {$ascii +=, ([char][byte]$a | Where-Object {$_ -notin "'",'`','|','_',"`;",'"',','})}
Do {$TempPassword += $ascii | Get-Random; $loop++}
Until ($loop -eq 11)
return $TempPassword
}
If I remove the following section:
| Where-Object {$_ -notin "'",'`','|','_',";",'"',','}
The creation of the password works fine albeit including the symbols I don't want included.
Having the Where-Object function causes the Get-Random function to only use the first 5 characters in the array, and therefore I don't get letters of any case type, or numbers, or any of the other symbols.
I've found that if I use $ascii[26] (being the 25th character in the array) I get a null value, however I would think this would allow any character up to this character to be used, or none at all, not just the first 5. The 25th character just so happens to be a ; (ascii value number 59). I tried adding the symbol to the Where-Object exclusion, and it was removed from the array, but the 25th character still showed as a null value.
I performed a reverse lookup of the ascii value [int[]][char[]] of each character either side of where the ; symbol would appear and it returned values 58 and 60, leading me to believe it was value 59 that was offending, but the symbol at this point should have been excluded.
Adding characters to the 'where-object' exclusion list should be removing them from the array, and it appears to, however running $ascii.Count shows 49 characters, regardless of whether I add or remove characters to the Where-Object exclusion list.
I have looked for information on the web and can't seem to find any, although it may be the search terms I'm using, as it's a bit of a complex case that not many would be reporting on.
Any help is appreciated.
I didn't write this and i can't remember where i got it but i have built this into any scripts to create random secure Windows passwords, you can specify the length of the password returned by the param [int]$PasswordLength ( i have already set it to 10 ).
function New-SWRandomPassword {
[CmdletBinding(DefaultParameterSetName='FixedLength',ConfirmImpact='None')]
[OutputType([String])]
Param
(
# Specifies minimum password length
[Parameter(Mandatory=$false,
ParameterSetName='RandomLength')]
[ValidateScript({$_ -gt 0})]
[Alias('Min')]
[int]$MinPasswordLength = 8,
# Specifies maximum password length
[Parameter(Mandatory=$false,
ParameterSetName='RandomLength')]
[ValidateScript({
if($_ -ge $MinPasswordLength){$true}
else{Throw 'Max value cannot be lesser than min value.'}})]
[Alias('Max')]
[int]$MaxPasswordLength = 11,
# Specifies a fixed password length
[Parameter(Mandatory=$false,
ParameterSetName='FixedLength')]
[ValidateRange(1,2147483647)]
[int]$PasswordLength = 10,
# Specifies an array of strings containing charactergroups from which the password will be generated.
# At least one char from each group (string) will be used.
[String[]]$InputStrings = #('abcdefghijkmnpqrstuvwxyz', 'ABCEFGHJKLMNPQRSTUVWXYZ', '23456789', '!"#%&'),
# Specifies a string containing a character group from which the first character in the password will be generated.
# Useful for systems which requires first char in password to be alphabetic.
[String] $FirstChar,
# Specifies number of passwords to generate.
[ValidateRange(1,2147483647)]
[int]$Count = 1
)
Begin {
Function Get-Seed{
# Generate a seed for randomization
$RandomBytes = New-Object -TypeName 'System.Byte[]' 4
$Random = New-Object -TypeName 'System.Security.Cryptography.RNGCryptoServiceProvider'
$Random.GetBytes($RandomBytes)
[BitConverter]::ToUInt32($RandomBytes, 0)
}
}
Process {
For($iteration = 1;$iteration -le $Count; $iteration++){
$Password = #{}
# Create char arrays containing groups of possible chars
[char[][]]$CharGroups = $InputStrings
# Create char array containing all chars
$AllChars = $CharGroups | ForEach-Object {[Char[]]$_}
# Set password length
if($PSCmdlet.ParameterSetName -eq 'RandomLength')
{
if($MinPasswordLength -eq $MaxPasswordLength) {
# If password length is set, use set length
$PasswordLength = $MinPasswordLength
}
else {
# Otherwise randomize password length
$PasswordLength = ((Get-Seed) % ($MaxPasswordLength + 1 - $MinPasswordLength)) + $MinPasswordLength
}
}
# If FirstChar is defined, randomize first char in password from that string.
if($PSBoundParameters.ContainsKey('FirstChar')){
$Password.Add(0,$FirstChar[((Get-Seed) % $FirstChar.Length)])
}
# Randomize one char from each group
Foreach($Group in $CharGroups) {
if($Password.Count -lt $PasswordLength) {
$Index = Get-Seed
While ($Password.ContainsKey($Index)){
$Index = Get-Seed
}
$Password.Add($Index,$Group[((Get-Seed) % $Group.Count)])
}
}
# Fill out with chars from $AllChars
for($i=$Password.Count;$i -lt $PasswordLength;$i++) {
$Index = Get-Seed
While ($Password.ContainsKey($Index)){
$Index = Get-Seed
}
$Password.Add($Index,$AllChars[((Get-Seed) % $AllChars.Count)])
}
Return $(-join ($Password.GetEnumerator() | Sort-Object -Property Name | Select-Object -ExpandProperty Value))
}
}
}
New-SWRandomPassword
EDIT:::
https://gallery.technet.microsoft.com/scriptcenter/Generate-a-random-and-5c879ed5
The script can be found here.
Short version (best method for me):
$possible=36..38 + 40..43 + 45..58 + 60..94 + 97..123 + 125..126 + 33
(get-random -count 10 -input $possible | % {[char]$_}) -join ''
Using the following script seems to have worked exactly how I want.
I removed the comma (,) from after += on this line:
$ascii = $NULL;For ($a = 33;$a –le 126;$a++) {$ascii +=, ([char][byte]$a | Where-Object {$_ -notin "'",'`','|','_',";",'"',','})}
I created a blank array before the array is added to:
$ascii = #()
The full code block is below:
Function Get-TempPassword {
$TempPassword = $null
$ascii = #()
For ($a = 33;$a –le 126; $a++) { $ascii += ([char][byte]$a | Where-Object { $_ -notin "'",'`','|','_',";",'"',',' }) }
Do {$TempPassword += $ascii | Get-Random; $loop++}
Until ($loop -eq 11)
return $TempPassword
}
Recursive method :
Function random-password ($length = 10)
{
$Assembly = Add-Type -AssemblyName System.Web
$password = [System.Web.Security.Membership]::GeneratePassword($length, 2)
$desablechar = "[``'|_;,`"]"
if ($password -match $desablechar )
{
random-password $length
}
else
{
$password
}
}
random-password
try Something like this :
Function random-password ($length = 10)
{
$possible=36..38 + 40..43 + 45..58 + 60..94 + 97..123 + 125..126 + 33
$password = get-random -count $length -input $possible |
% -begin { $aa = $null } -process {$aa += [char]$_} -end {$aa}
return $password
}
random-password
I have rectified my others propositions, but i propose an other method :)
Function random-password2 ($length = 10)
{
$Assembly = Add-Type -AssemblyName System.Web
$password = [System.Web.Security.Membership]::GeneratePassword(50, 2)
$possible=36..38 + 40..43 + 45..58 + 60..94 + 97..123 + 125..126 + 33
$newchar=[char](get-random -count 1 -input $possible)
$password=$password -replace "[`'|_;,]", $newchar
$password.Substring(0, $length)
}
random-password2

Move elements in an array of hashtables to another array in PowerShell

I'd like to move hashtables from one array to another.
Assuming that I have an array of hashtables:
PS> $a = #( #{s='a';e='b'}, #{s='b';e='c'}, #{s='b';e='d'} )
Name Value
---- -----
s a
e b
s b
e c
s b
e d
I can copy a selected set to another array:
PS> $b = $a | ? {$_.s -Eq 'b'}
Name Value
---- -----
s b
e c
s b
e d
Then remove b's items from a:
PS> $a = $a | ? {$b -NotContains $_}
Name Value
---- -----
s a
e b
Is there a more-succinct way of doing this?
PS 4.0 using Where method:
$b, $a = $a.Where({$_.s -Eq 'b'}, 'Split')
More info:
ForEach and Where magic methods
I would argue that doing two assignments with a filter and the inverted filter is the most straightforward way of doing this in PowerShell:
$b = $a | ? {$_.s -eq 'b'} # x == y
$a = $a | ? {$_.s -ne 'b'} # x != y, i.e. !(x == y)
You could wrap a function around this operation like this (using call by reference):
function Move-Elements {
Param(
[Parameter(Mandatory=$true)]
[ref][array]$Source,
[Parameter(Mandatory=$true)]
[AllowEmptyCollection()]
[ref][array]$Destination,
[Parameter(Mandatory=$true)]
[scriptblock]$Filter
)
$inverseFilter = [scriptblock]::Create("-not ($Filter)")
$Destination.Value = $Source.Value | Where-Object $Filter
$Source.Value = $Source.Value | Where-Object $inverseFilter
}
$b = #()
Move-Elements ([ref]$a) ([ref]$b) {$_.s -eq 'b'}
or like this (returning a list of arrays):
function Remove-Elements {
Param(
[Parameter(Mandatory=$true)]
[array]$Source,
[Parameter(Mandatory=$true)]
[scriptblock]$Filter
)
$inverseFilter = [scriptblock]::Create("-not ($Filter)")
$destination = $Source | Where-Object $Filter
$Source = $Source | Where-Object $inverseFilter
$Source, $destination
}
$a, $b = Remove-Elements $a {$_.s -eq 'b'}
or a combination of the above.

Combine 2 PowerShell arrays

I'm trying to figure out how to combine 2 arrays as explained here by Microsoft.
$Source = 'S:\Test\Out_Test\Departments'
$Array = Get-ChildItem $Source -Recurse
$Array.FullName | Measure-Object # 85
$Array.FullName + $Source | Measure-Object # 86
$Source + $Array.FullName | Measure-Object # 1
The following only has 1 item to iterate through:
$i = 0
foreach ($t in ($Source + $Array.FullName)) {
$i++
"Count is $i"
$t
}
My problem is that if $Source or $Array is empty, it doesn't generate seperate objects anymore and sticks it all together as seen in the previous example. Is there a way to force it into separate objects and not into one concatenated one?
In PowerShell, the left-hand side operand determines the operator overload used, and the right-hand side gets converted to a type that satisfies the operation.
That's why you can observe that
[string] + [array of strings] results in a [string] (Count = 1)
[array of strings] + [string] results in an [array of strings] (Count = array size + 1)
You can force the + operator to perform array concatenation by using the array subexpression operator (#()) on the left-hand side argument:
$NewArray = #($Source) + $Array.FullName
This is because $Source is a System.String and + is probably an overloaded for Concat. If you cast $Source to an array, you will get your desired output:
[array]$Source + $Array.FullName | Measure-Object # 86

Find closest numerical value in Powershell arrays

Q: I'm looking for a more elegant way to get the closest match of a numerical from an array.
I may have over-complicated things here
Input
## A given array A where to search in
$a = (16/10),(16/9),(4/3),(5/4),(21/10),(21/9)
## A given value B which should be searched for in array A (closest match)
$b = 16/11
Desired output
"Closest value to 16/11 is 4/3"
My current code to solve the problem
## New array C for the differences of array A - B
$c = $a | %{ [math]::abs($_ - $b) }
## Measure array C to get lowest value D
$d = $c | measure -Minimum
## Get position E of value D in array C
$e = [array]::IndexOf($c, $d.minimum)
## Position E correlates to array A
echo "Closest value to $b is $($a[$e])
Remarks
It don't has to be an array if a hash table or something else suits better
My current code outputs decimals like 1.33333 instead of fractions 4/3. It would be nice to output the fraction
Short code is always better
$a = (16/10),(16/9),(4/3),(5/4),(21/10),(21/9)
$b = 16/11
$oldval = $b - $a[0]
$Final = $a[0]
if($oldval -lt 0){$oldval = $oldval * -1}
$a | %{$val = $b - $_
if($val -lt 0 ){$val = $val * -1}
if ($val -lt $oldval){
$oldval = $val
$Final = $_} }
Write-host "$Final is the closest to $b"
$diff = $b - $a[0]
$min_index = 0
for ($i = 1; $i -lt $a.count; $i++)
{
$new_diff = $b - $a[$i]
if ($new_diff -lt $diff)
{
$diff = $new_diff
$min_index = $i
}
}
Write-Output "Closest value to $b is $($a[$min_index])"
Powershell dose not support fraction values...
## A given array A where to search in
$a = '16/10','16/9','4/3','5/4','21/10','21/9'
## A given value B which should be searched for in array A (closest match)
$b = '16/11'
$numericArray = ($a | % { Invoke-Expression $_ })
$test = Invoke-Expression $b
$best = 0
$diff = [double]::MaxValue
for ($i = 1; $i -lt $numericArray.count; $i++) {
$newdiff = [math]::abs($numericArray[$i] - $test)
if ($newdiff -lt $diff) {
$diff = $newdiff
$best = $i
}
}
"Closest value to $b is $($a[$best])"
The big difference here is that the inputs are strings and not numbers, so we can preserve the fractions.
Security note: Don't use this if the inputs are from a user, as passing user-generated strings to Invoke-Expression is obviously a recipe for trouble.
## A given array A where to search in
$a = (16/10),(16/9),(4/3),(5/4),(21/10),(21/9)
## A given value B which should be searched for in array A (closest match)
$b = 16/11
<#Create a new-Object , we'll use this to store the results in the Foreach Loop#>
$values = [Pscustomobject] #{
'result' = #()
'a-values' = #()
}
foreach($aa in $a)
{ #round to 2 decimal places and then subtract
#store the result at the 'a-value' used to create that result
$values.result += [MATH]::Abs( [Math]::Round($B,2)`
-[Math]::Round($aa,2)`
)
$values."a-values" += $aa
}
# sort ascending and then this gets me the number closest to Zero
$lookFor = $($values.result | Sort-Object )[0]
#the result of the Number closest to $b = 16/11
$endresult = $values.'a-values'[ $values.result.indexof($lookfor) ]
Write-host "the number closest to '$b' is '$endresult'"
Remove-Variable values
Here's a oneliner sorting with distance function:
($a | Sort-Object { [Math]::abs($_ - $b) })[0]
Returning a fraction is not possible in this case because e.g. 16/10 is immediately converted to a decimal.

Resources