Let me start by apologising that the below data structure probably isn't written correctly. I dynamically created the hashes in the code and I'm not very good at trying to represent what gets created.
$Sailings= #{
'Arrivals' = #{
$DynamicKey_booking_Ref = #{
'GoingTo' = 'Port1';
'Scheduled' = '09:05';
'Expected' = '10:09';
'Status' = 'Delayed'
};
'Departures' = #{
$DynamicKey_booking_Ref = #{
'ArrivingFrom' = 'Port1';
'Scheduled' = '09:05';
'Expected' = '09:05';
'Status' = 'OnTime'
};
}
}
I typically access the data like this. (hopefully that confirm the structure I'm using):
$Sailings.Arrivals.PDH083.GoingTo which returns "Port1"
$Sailings.Arrivals.PDH083.Scheduled which returns "09:05"
where PDH083 in this example is a dynamically created key based on a booking ref.
What I am trying to do is compare this structure with another identical structure but with potentially different values. E.g. are these two elements the same?
$Sailings.Arrivals.PDH083.GoingTo = "Port1"
$Output.Arrivals.PDH083.GoingTo = "Port5555"
If they're not the same capture the difference and the path/key that was different. Then report on them at the end.
What I'm struggling to write is a recursive loop that can walk down to the last element and then compare it to the $output. While my hashes are fixed now, I'd like to allow for the possibility that more nested hashes might be added lower down at a later date. Is this something that can be done easily?
I've used
($Sailings.Arrivals.keys | ? {$Output.Arrivals.keys -notcontains $_})
to show missing keys, but I just can't fathom a similar / efficient way of doing this for the values. I again can see I can use .values but it's still and element at a time.
Here is a semi recursive example, which returns string representations of the paths for all the leaves in the tree. Hope it gives you ideas:
$Sailings = #{
'Arrivals' = #{
'PDH083' = #{
'GoingTo' = 'Port1'
'Scheduled' = '09:05'
'Expected' = '10:09'
'Status' = 'Delayed'
}
}
'Departures' = #{
'PDH083' = #{
'ArrivingFrom' = 'Port1'
'Scheduled' = '09:05'
'Expected' = '09:05'
'Status' = 'OnTime'
}
}
}
function Roam($arg, $result="") {
if(!($arg -is [Hashtable])) {
return "$result/$arg"
}
foreach($pair in $arg.GetEnumerator()) {
Roam $pair.value "$result/$($pair.key)"
}
}
Roam $Sailings
The if is stop condition, the first thing you should ask when you are designing recursive operations: When I have a result?
Suppose you are standing on the roots of a huge tree, and you have been given a task of mapping routes to every single leave of that tree, every turn to the left or right on the branches from the trunk to a leave. Overwhelming, eh? But think instead about finding out a route to a single leave, whichever from the myriad.
You start climbing, and reach first branch. Will you turn to the left or right? Doesn't matter. You decide to take the left branch, and write down on piece of paper left. You reach next branch, and for matter of fun turn right, writing down right. After a few branches you are carrying notes like left, right, right, left, right and so on, until (because trees don't usually produce loops) eventually there is no more climbing but only a leave in font of you.
You've done it! You mapped the whole (and only one) path to this single leave, and can now jump down (hoping the tree is not too tall) and represent the paper containing the route to your adoring friends. Reaching the leave was the stop condition.
But how about the other leaves? Imagine you have a weird superpower of cloning yourself. When you reach a branch, you clone yourself and the paper you are carrying. If you turn right, you add that right turn to the notes, but the clone goes left, and writes down left instead. On next branch you again clone yourself and the paper, and write down your chosen direction, and the clone does the same for its direction. You don't have to worry about the clones, (Maybe you are a clone yourself!) just repeat that until you've reached the one leave and can jump out.
The $result argument is that piece of paper, and originally it doesn't came from anywhere, it's empty.
Because all the leaves in you data structure are strings, you could also write the if statement like:
if($arg -is [String])
How about GetEnumerator? Hashtables are normally not ordered in PowerShell. We can't pick first or second or sixth pair. But your data structure branches to more directions than left and right, so the Hashtable has to be ordered to an Array as a crowd of people to a queue, so we can use foreach loop to send our clones to their paths. (We could replace that loop with recursion, but let it be)
So in the function call Roam $pair.value "$result/$arg" the first argument is the branch ahead, and the second one is the piece of paper we just added the current direction.
Recommendation: You don't need to scramble it through, even the first few chapters are enlightening. Structure and Interpretation of Computer Programs https://mitpress.mit.edu/sicp/full-text/book/book.html
Related
I have a scenario where I THINK a circular dependency is the right answer. In my actual code I have a class that contains some data that is used for token replacements. That class contains other classes for the data, and contains a method that does the lookup of token values and returns the value. However, those dependent classes need to be validated, and some of those values are dependent on lookups. So, I have mocked up this code to validate the approach.
class Tokens {
[Collections.Specialized.OrderedDictionary]$primaryData
[Collections.Specialized.OrderedDictionary]$secondaryData
Tokens ([Secondary]$seconday) {
$this.primaryData = [Ordered]#{
'1' = 'One'
'2' = 'Two'
'3' = 'Three'
'4' = 'Four'
'5' = 'Five'
}
$this.secondaryData = $seconday.secondaryData
}
[String] GetToken ([String]$library, [String]$item) {
return $this.$library.$item
}
[Void] SetToken ([String]$library, [String]$item, [String]$value) {
$this.$library.$item = $value
}
[String] ToString () {
[Collections.Generic.List[String]]$toString = [Collections.Generic.List[String]]::new()
foreach ($key in $this.primaryData.Keys) {
$toString.Add("$key : $($this.primaryData.$key)")
}
foreach ($key in $this.secondaryData.Keys) {
$toString.Add("$key : $($this.secondaryData.$key)")
}
return [string]::Join("`n", $toString)
}
}
class Secondary {
[Collections.Specialized.OrderedDictionary]$secondaryData
Secondary () {
$this.secondaryData = [Ordered]#{
'A' = 'a'
'B' = 'b'
'C' = 'c'
'D' = 'd'
'E' = 'e'
}
}
[Void] Update ([Tokens]$tokensReference) {
$tokensReference.SetToken('secondaryData', 'A', 'A')
$tokensReference.SetToken('secondaryData', 'B', "$($tokensReference.GetToken('secondaryData', 'A')) and $($tokensReference.GetToken('secondaryData', 'B'))")
}
[String] ToString () {
[Collections.Generic.List[String]]$toString = [Collections.Generic.List[String]]::new()
foreach ($key in $this.secondaryData.Keys) {
$toString.Add("$key : $($this.secondaryData.$key)")
}
return [string]::Join("`n", $toString)
}
}
CLS
$secondary = [Secondary]::new()
$tokens = [Tokens]::new($secondary)
$secondary.Update($tokens)
Write-Host "$($tokens.ToString())"
This is working, exactly as expected. however, the idea of circular dependency injection has my hair standing on end. Like, it could be a real problem, or at least is a code smell. So, my question is, am I on the right track, or is this a dead end and I just haven't found that end yet? Given that PowerShell isn't "fully" object oriented yet, I imagine there could be some uniquely PS related issues, and everything I have found searching for Powershell Circular Dependency talks about removing them. So far I haven't found anything about when it is appropriate and how to do it well.
And, assuming it is a valid approach, is there anything obvious in this simplified implementation that could lead to problems or could be better done some other way?
EDIT: OK, so perhaps I am going to refine my vocabulary a bit too. I was thinking circular dependency since Secondary is a dependency, or member perhaps, of Tokens, and then I update Secondary from inside Secondary, while referencing data from Secondary, via the method in Tokens.
To clarify (I hope) the ultimate goal, these lookups are for program specific data, which I have in XML files. So, for example the data file for Autodesk Revit 2021 would include these three items
<item id="GUID" type="literal">{7346B4A0-2100-0510-0000-705C0D862004}</item>
<item id="installDataKey" type="registryKeyPath">HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\[Revit 2021~GUID]</item>
<item id="displayName" type="registryPropertyValue" path="installDataKey">Revit 2021</item>
In actual use I would want to get the DisplayName property found in the key defined in <item id="installDataKey">, and if the value matches the value in <item id="displayName"> then I might also look for the value of the DisplayVersion property in the same key, and make decisions based on that. But because there are new versions every year, and 20+ different software packages to address, managing these data files is a pain. So I want to validate the data I have in the files against a machine that actually has the software installed, to be sure I have my data correct. Autodesk is famous for changing things for no good reason, and often for some very customer hostile reasons. So, things like referencing the GUID as data and reusing it as a token, i.e. the [Revit 2021~GUID] above, saves effort. So during the validation process only, I would want to set the GUID, then do the standard token replacement to convert HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\[Revit 2021~GUID] to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{7346B4A0-2100-0510-0000-705C0D862004} and should that key actually be found, use it to validate about 20 other registry and file paths as well as the actual value of DisplayName. If everything validates I will sign the XML, and in actual use signed XML will basically treat everything as a literal and no validation is done, or rather it was prevalidated.
But before use a reference to [product~installDataKey] when the current product is Revit 2021 would resolve first to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\[Revit 2021~GUID] then to HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{7346B4A0-2100-0510-0000-705C0D862004} at which point the code could use it as a registry path and see if Revit 2021 is in fact installed.
So, 99.9% of the time, I would instantiate Tokens with a constructor that just includes the full dataset and move on. But on the .1% of occasions where I am validating the data itself, I need to be able to read the xml, set the value for that GUID and immediately use the lookup to validate that Autodesk hasn't done something stupid like move some data out of the GUID key and into a secondary key. They have done that before.
I also might want to replace HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall with a token like [windows~uninstallKey] just to make life easier there too.
Hopefully that makes some sense. It's a mess to be sure, but anything to do with Autodesk is a mess.
I was just playing around with hash tables and I did this:
$C = #{color = [System.Collections.ArrayList]#(#{y = 'yellow'},#{y = 'blue'})}
and I ended up with finding I can do:
($C.color.y).Get(1) or ($C.color.y).Item(1) and it does exactly the same thing.
I also found even though the autocomplete doesn't show you can do this you can:
($C.color.y).Get(0).Contains('yellow')
($C.color.y).Get(1).Contains('blue')
The Get and Item methods appear to be interchangeable what is the difference between them?
I did check the hash codes they are the same.
($C.color.y).Get(1).GetHashCode() or ($C.color.y).Item(1).GetHashCode()
I'm just starting to learn Perl and I have to do an exercise containing references.
I have to create a program, that constructs a list with two sided references, that are are received as command line arguments. At the beginning of the program, there is only one element in the list - 0. To go through the list, reference is being used, that references to the only element of the list at the moment - 0. The arguments of the command line are being read one by one and added right behind the element, that is being referenced to. When one argument is added, the reference slides one element to the right(it references to the newly added element). There are also two special elements - + and -. + allows the reference to move one element to the right, and - one element to the left. Also, it is important that the reference would not go beyond the list limit.
The output is all the arguments in the correct order of the list.
Additional requirements are that the list must be created by using hashes, that contain links to neighbouring elements. Also, I cannot use arrays to store the whole list.
There are a few examples to make it easier to grasp the concept, this is the most useful one:
./3.pl A D - B C + E
0 A B C D E
All I've got now is just the start of the program, it is nowhere near done and doesn't compile, but I can't figure out where to go from there. I've tried looking for some information about two-sided references(I'm not sure if I'm translating it correctly), but I can't seem to find anything. Any information about two-sided references or any tips how to start writing this program properly would be very appreciated.
My code:
#!/usr/bin/perl
use strict;
use warnings;
my $A= {
value=>'0',
prev=>'undef',
next=>'$B'
};
my $B= {
value=>'0',
prev=>'$A',
next=>'$C'
};
my $C= {
value=>'0',
prev=>'$B',
next=>'undef'
};
for my $smbl(0..#$ARGV) {
$A-> {value} = $ARGV[$smbl];
$Α-> {next} = $ARGV[$smbl+1];
}
First of all, what you are building is called a doubly linked list.
Let me tell you the biggest trick for working with linked lists: Create a dummy "head" node and a dummy "tail" node. You won't print their values, but having them will greatly reduce the number of special cases in your code, making it so much simpler!
At the core, you will have three "pointers" (references).
$head points to the first node of the list.
$tail points to the last node of the list.
$cursor initially points to the node in $tail. New nodes will be inserted before this node.
When processing +, two different situations you need to handle:
$cursor == $tail: Error! Cursor moved beyond end of list.
$cursor != $tail: Point $cursor to the node following the one it references.
When processing -, there are two different situations you need to handle:
$cursor->{prev} == $head: Error! Cursor moved beyond start of list.
$cursor->{prev} != $head: Point $cursor to the node preceding the one it references.
When processing inserting nodes, no checks need to be performed because of the dummy nodes!
I am looking to create a rule in Office 365 applied to all of the members in our org.
I would like this rule to append a warning on all incoming email from outside the organization with the same Display Names as our users.
When I attempt to apply it to all of the users in our org I get an error stating that the rule is too long.
In order to solve that I pulled a group, but I am still about 1000 characters over the limit.
I would like to make two variables, that each hold one half of the list, created by this command:
(Get-DistibutionGroupMember -Identity email#contoso.com -ResultSize Unlimited).DisplayName
I have attempted to modify the ResultSize parameter, but what I would need is result 1-100 and then 100-200 from the same list.
Another caveat to this problem is that the list cannot be static. It is something that the script will have to update every time it is run.
There is a sub-string command that you can use on a particular username that I have utilized when I made something for AD, but I am not aware of any way to break up a list like this.
If anyone has any other ways to solve this issue I would be more than open to any suggestion.
Thanks for taking the time to read this!
There are many ways of doing it. I found it very readable.
My favorite one is this one:
$ObjectList = 1..1000
$Step = 100
$counter = [pscustomobject] #{ Value = 0 }
$ObjectListSplitted = $ObjectList | Group-Object -Property { math]::Floor($counter.Value++ / $step) }
Then if you want to show the third subset just use this format :
$ObjectListSplitted[3].Group
Have a look to this solution already explained.
As a note other languages are capable of slicing an array of object with a start, stop and a step, have a look here if you're curious.
So I am trying to learn Linked Lists using Perl. I am reading Mastering Algorithms with Perl by Jon Orwant. In the book he explains how to create a linked list.
I understand most of it, but I just simply fail to understand the command/index/key NEXT in the second last line of the code snippet.
$list=undef;
$tail=\$list;
foreach (1..5){
my $node = [undef, $_ * $_];
$$tail = $node;
$tail = \${$node->[NEXT]}; # The NEXT on this line?
}
What is he trying to do there?
Is $node a scalar, which stores the address of the unnamed array? Also even if we are dereferencing $node, should we not refer to the individual elements by an index number, such as (0,1). If we do use NEXT as a key, is $node a reference to a hash?
I am very confused.
Something in plain English will be highly appreciated.
NEXT is a constant, declared earlier in the script. It contains an integer value representing the index of the current node's member element that refers to the next node.
Under this scheme, each node is a small anonymous array. One element of this anonymous array contains the payload, and the other contains a reference pointing to the next node.
If you look at some of the earlier examples in that chapter you will see the following declarations:
use constant NEXT => 0;
use constant VAL => 1;
So $node->[NEXT] is synonymous to $node->[0], which contains a reference to the next node in the linked list chain, while $node->[VAL] is synonymous with $node->[1]; the value (or payload) stored in the current node.
I'll comment on the code snippet you provided:
foreach (1..5){
my $node = [undef, $_ * $_]; # Create a new node as an anon array.
# Set the previous node's "next node reference" to point to this new node.
$$tail = $node;
# Remember a reference to the new node's "next node reference" element.
# So that it can be updated when another new element is added on next iteraton.
$tail = \${$node->[NEXT]}; # The NEXT on this line?
}
Excellent book, by the way. I've got several algorithms books, and that one continues to be among my favorites after all these years.
Update: I do agree that the book isn't a model of current idiomatic Perl, or current "best practices" Perl, but do feel it is a nice resource for gaining an understanding of the application of classic algorithms with Perl. I still refer back to it from time to time.
NEXT is a constant, declared on an earlier page, that contains a number. Its being used instead of just the regular number to access the array ref $node so the reader knows that slot is where the next element in the linked list is stored.
It's a technique to use array references to store things other than lists. The technique was intended to save memory and CPU time compared to using a hash reference. In reality it doesn't make much performance difference and its awkward to work with. The book is quite a bit out of date in its ideas about how to write Perl code. Use a hash reference instead.
my $list;
my $tail = \$list;
foreach my $num (1..5) {
my $node = { data => $num };
$$tail = $node;
$tail = \$node->{next};
}