Is there a name for this sort? - perl

What is the name for the sort used in this answer? I Googled for "perfect insertion sort" but didn't find anything. Here is the code from that answer:
#this is O(n) instead of O(n log n) or worse
sub perfect_insert_sort {
my $h = shift;
my #k;
for my $k (keys %$h) {
$k[$h->{$k}{order}] = $k;
}
return #k;
}

I think I probably should have named that perfect_bucket_sort instead of perfect_insertion_sort.

This isn't insertion sort, in fact it's not even a comparison sort because the theoretical lowest bound for those is O(nlogn).
So it's probably bucket sort; also notice there are no comparisons made :)

It's not really a sort at all. it is, in fact, primarily a map or a transformation. This is an example of the data structure they have:
my $hash = {
foo => { order => 3 },
bar => { order => 20 },
baz => { order => 66 },
};
It's simply a translation of 'order' to elements in an array. For example, if you pass in this $hash to perfect_insert_sort, it will return a 67 element array, with three items (one at index 3, one at 20, and one at 66) and the rest being undef, and entirely in an unsorted order.
Nothing about that function does any sorting of any kind. If there is any sorting going on in that other answer, it's happening before the function is called.
#downvoter:
And looking at the other answer, the sorting happens at insertion time. THAT component might be considered a sort. This subroutine, however, does not create that order - it merely reconstitutes it.
Take a look at the classical definition for a sort:
The output is in nondecreasing order (each element is no smaller than the previous element according to the desired total order)
The output is a permutation, or reordering, of the input.
Part 2 is certainly being satisfied: there is a transformation of the hash structure to a list going on. However, part 1 is not satisfied. There is no determining of order going on. The order has been predetermined during insertion. If this were a 'sort', then the following would also be a sort:
my #data = ... ;
my $index = -1;
my %stored = map { ++$index; $_ => { order => $index } } #data;
my #remade_data;
#remade_data[(map { $stored{$_}{order} } keys %stored)] = keys %stored;
As you can see, there is no sorting going on in that chunk of code, merely transformation.

I think it's nothing but an insertion sort.

Related

Raku: Trouble Accessing Value of a Multidimensional Hash

I am having issues accessing the value of a 2-dimensional hash. From what I can tell online, it should be something like: %myHash{"key1"}{"key2"} #Returns value
However, I am getting the error: "Type Array does not support associative indexing."
Here's a Minimal Reproducible Example.
my %hash = key1-dim1 => key1-dim2 => 42, key2-dim1 => [42, 42];
say %hash{'key1-dim1'}{'key1-dim2'}; # 42
say %hash{'key2-dim1'}{'foo bar'}; # Type Array does not support associative indexing.
Here's another reproducible example, but longer:
my #tracks = 'Foo Bar', 'Foo Baz';
my %count;
for #tracks -> $title {
$_ = $title;
my #words = split(/\s/, $_);
if (#words.elems > 1) {
my $i = 0;
while (#words.elems - $i > 1) {
my %wordHash = ();
%wordHash.push: (#words[$i + 1] => 1);
%counts.push: (#words[$i] => %wordHash);
say %counts{#words[$i]}{#words[$i+1]}; #===============CRASHES HERE================
say %counts.kv;
$i = $i + 1;
}
}
}
In my code above, the problem line where the 2-d hash value is accessed will work once in the first iteration of the for-loop. However, it always crashes with that error on the second time through. I've tried replacing the array references in the curly braces with static key values in case something was weird with those, but that did not affect the result. I can't seem to find what exactly is going wrong by searching online.
I'm very new to raku, so I apologize if it's something that should be obvious.
After adding the second elements with push to the same part of the Hash, the elment is now an array. Best you can see this by print the Hash before the crash:
say "counts: " ~ %counts.raku;
#first time: counts: {:aaa(${:aaa(1)})}
#second time: counts: {:aaa($[{:aaa(1)}, {:aaa(1)}])}
The square brackets are indicating an array.
Maybe BagHash does already some work for you. See also raku sets without borders
my #tracks = 'aa1 aa2 aa2 aa3', 'bb1 bb2', 'cc1';
for #tracks -> $title {
my $n = BagHash.new: $title.words;
$n.raku.say;
}
#("aa2"=>2,"aa1"=>1,"aa3"=>1).BagHash
#("bb1"=>1,"bb2"=>1).BagHash
#("cc1"=>1).BagHash
Let me first explain the minimal example:
my %hash = key1-dim1 => key1-dim2 => 42,
key2-dim1 => [42, 42];
say %hash{'key1-dim1'}{'key1-dim2'}; # 42
say %hash{'key2-dim1'}{'key2-dim2'}; # Type Array does not support associative indexing.
The problem is that the value associated with key2-dim1 isn't itself a hash but is instead an Array. Arrays (and all other Positionals) only support indexing by position -- by integer. They don't support indexing by association -- by string or object key.
Hopefully that explains that bit. See also a search of SO using the [raku] tag plus 'Type Array does not support associative indexing'.
Your longer example throws an error at this line -- not immediately, but eventually:
say %counts{...}{...}; # Type Array does not support associative indexing.
The hash %counts is constructed by the previous line:
%counts.push: ...
Excerpting the doc for Hash.push:
If a key already exists in the hash ... old and new value are both placed into an Array
Example:
my %h = a => 1;
%h.push: (a => 1); # a => [1,1]
Now consider that the following code would have the same effect as the example from the doc:
my %h;
say %h.push: (a => 1); # {a => 1}
say %h.push: (a => 1); # {a => [1,1]}
Note how the first .push of a => 1 results in a 1 value for the a key of the %h hash, while the second .push of the same pair results in a [1,1] value for the a key.
A similar thing is going on in your code.
In your code, you're pushing the value %wordHash into the #words[$i] key of the %counts hash.
The first time you do this the resulting value associated with the #words[$i] key in %counts is just the value you pushed -- %wordHash. This is just like the first push of 1 above resulting in the value associated with the a key, from the push, being 1.
And because %wordHash is itself a hash, you can associatively index into it. So %counts{...}{...} works.
But the second time you push a value to the same %counts key (i.e. when the key is %counts{#words[$i]}, with #words[$i] set to a word/string/key that is already held by %counts), then the value associated with that key will not end up being associated with %wordHash but instead with [%wordHash, %wordHash].
And you clearly do get such a second time in your code, if the #tracks you are feeding in have titles that begin with the same word. (I think the same is true even if the duplication isn't the first word but instead later ones. But I'm too confused by your code to be sure what the exact broken combinations are. And it's too late at night for me to try understand it, especially given that it doesn't seem important anyway.)
So when your code then evaluates %counts{#words[$i]}{#words[$i+1]}, it is the same as [%wordHash, %wordHash]{...}. Which doesn't make sense, so you get the error you see.
Hopefully the foregoing has been helpful.
But I must say I'm both confused by your code, and intrigued as to what you're actually trying to accomplish.
I get that you're just learning Raku, and that what you've gotten from this SO might already be enough for you, but Raku has a range of nice high level hash like data types and functionality, and if you describe what you're aiming at we might be able to help with more than just clearing up Raku wrinkles that you and we have been dealing with thus far.
Regardless, welcome to SO and Raku. :)
Well, this one was kind of funny and surprising. You can't go wrong if you follow the other question, however, here's a modified version of your program:
my #tracks = ['love is love','love is in the air', 'love love love'];
my %counts;
for #tracks -> $title {
$_ = $title;
my #words = split(/\s/, $_);
if (#words.elems > 1) {
my $i = 0;
while (#words.elems - $i > 1) {
my %wordHash = ();
%wordHash{#words[$i + 1]} = 1;
%counts{#words[$i]} = %wordHash;
say %counts{#words[$i]}{#words[$i+1]}; # The buck stops here
say %counts.kv;
$i = $i + 1;
}
}
}
Please check the line where it crashed before. Can you spot the difference? It was kind of a (un)lucky thing that you used i as a loop variable... i is a complex number in Raku. So it was crashing because it couldn't use complex numbers to index an array. You simply had dropped the $.
You can use sigilless variables in Raku, as long as they're not i, or e, or any of the other constants that are already defined.
I've also made a couple of changes to better reflect the fact that you're building a Hash and not an array of Pairs, as Lukas Valle said.

Initialize empty Array of Hashes of a given length - one-liner

I'd like to pre-initialize the elements of an array of hashes so that when it comes time to filling in the data, I don't need to check for the existence of various members and initialize them every loop. If I can, I'd like to pre-initialize the general form of the datastructure which should look like this:
$num_sections = 4;
# Some magic to initialize $VAR1 to this:
$VAR1 = {
'sections' => [
{},
{},
{},
{}
]
};
I would like this to work,
$ph->{sections} ||= [({} x $num_sections )];
but it results in
$VAR1 = {
'sections' => 'HASH(0x21b6110)HASH(0x21b6110)HASH(0x21b6110)HASH(0x21b6110)'
};
And no amount of playing with the () list context and {} empty hash reference seem to make it work.
This works but it's not quite a one-liner
unless ($ph->{sections})
{
push #{ $ph->{sections}}, {} foreach (1..$num_sections);
}
There's probably some perl magic that I can use to add the unless to the end, but I haven't quite figured it out.
I feel I'm so close, but I just can't quite get it.
Update Oleg points out that this probably isn't necessary at all. See comments below.
If the left-hand side of x is not in parens, x repeats the string on its LHS and returns the concatenation of those strings.
If the left-hand side of x is in parens, x repeats the value on its LHS and returns the copies.
This latter approach is closer to what you want, but it's still wrong as you'll end up with multiple references to a single hash. You want to create not only new references, but new hashes as well. For that, you can use the following:
$ph->{sections} ||= [ map { +{} } 1..$num_sections ];

Hash value is not re-initialized when loop is terminated with 'last' keyword

Consider the following nested loops:
my %deleted_documents_names = map { $_ => 1 }
$self->{MANUAL}->get_deleted_documents();
while($sth->fetch){
.....
.....
.....
while(my ($key, $value) = each(%deleted_documents_names)){
{
if($document_name eq $key){
$del_status=1;
last;
}
}
if($del_status==1){next;}
.....
.....
.....
.....
}
Now, I take a sample case where three values (A,B,C) will be compared against two values (B,C).
First scan:
A compared to B
A compared to C
Second scan:
B compared to B
Loop is terminated.
Third scan:
C is compared with C.
In this case, C should be compared first with B, being first value, but this comparison is skipped, and it only scans from the next element after the one that was found equal. If I remove last termination condition and let the loop run for total number of scans, then it works all fine, but I need to find out why in this case, $key refers to the next compared value and not to the first value once loop is restarted after getting terminated with last keyword.
Any help will be appreciated.
Use
keys %deleted_documents_names ; # Reset the "each" iterator.
See keys.
But, why are you iterating over the hash? Why don't you just
if (exists $deleted_documents_names{$document_name}) {
each() is a function that returns key-value pairs from a hash until it reaches the end. It is not aware of the scope it was called in, and doesn't know anything about your while loop logic. See the documentation here.
It can be reset by calling keys %hash or values %hash.
Update: however, as Choroba points out, you don't really need this loop. Your loop and accompanying logic could be replaced by this:
next if (exists $deleted_documents_names{$document_name});
(Hashes are designed with a structure that allows a key to be quickly found. In fact, this structure is what gives them the name "hashes". So doing it this way will be much more efficient than looping through all elements and testing each one).

Perl - how do I get the previous key in a hash?

I have a hash that has been filled with an unknown number of keys that are strings. Each key's value is zero to start.
I want to iterate through this hash and for each element except the first, make its value equal the equivalent in another hash I have plus the previous value in this hash. So mathematically something like:
hash1:a = hash2:a;
hash1:b = hash2:b + hash1:a;
hash1:c = hash2:c + hash1:b;
hash1:d = hash2:d + hash1:c;
hash1:e = hash2:e + hash1:d;
...
I'm not even really sure I know how to do the first one as I don't know what the value or key is. I plan to sort it first, but as the keys could be any in a list I have I cannot specify what would be first exactly. I don't know how to reference the previous value based on the key. Is this possible?
(I don't have any code to show I'm afraid, I tried but I just realised that it is very wrong haha)
Hashes themselves are unordered, so they have no idea of a 'previous' key. You'd need to extract the keys using the keys function, sort them (as you said) and then refer to the previous element of the list of keys to find out what the previous key in your chosen order was.
Once you have that previous key, you can then access the value from the hash using the usual $hash{$key} syntax.
The trick then becomes accessing the previous key in the list, which probably entails iterating over the list by index, as a foreach loop doesn't give you any idea where in the list you currently are. Something like
for (my $i = 0; $i <= $#keys; ++$i) {
if ($i == 0) { ... } # first key
else { ... } # all other keys
}
might be appropriate.
This is basically a cumulative sum over all hash values:
a’ = (0) + a
b’ = (0 + a) + b
c’ = (0 + a + b) + c
d’ = 0 + ...
Iterating over the hash, storing the sum and always adding the current value (or adding the sum to the current value, doesn't matter, addition is commutative) should get you the desired result.
Note that you must sort your keys first, because hashes are unordered by definition.
$sum = 0;
foreach my $k (sort keys %hash) {
$hash{$k} += $sum;
}
The resulting hash will not be sorted either, so you have two options:
Every time you work with the hash (iterate over it) you have to iterate over the sorted keys and then access the hash at that key position. Just use the loop I have given above and swap its body with whatever you want.
Store your sums in a list/array. These data structures don't change their order of values (but you won't have key value pairs; only values).

Secondary Order in Heap::Simple

How do I define a secondary ordering to the Heap::Simple interface in Perl?
The documentation states that the constructor takes a code reference to define
the order, so you can specify any sort method you like:
my $heap = Heap::Simple->new(order => \&sort_method);
Every time two keys need to be compared, the given code reference will be called like:
$less = $code_reference->($key1, $key2);
This should return a true value if $key1 is smaller than $key2 and a false
value otherwise. $code_reference should imply a total order relation, so it
needs to be transitive.
By "secondary ordering" I assume you mean that a second comparison is used if
the first one shows the values to be equal. Let's say the first comparison is
of values found via the "method1" method, and the second comparison is of
values from "method2". So, if by method1 the values are different, return
that result, and otherwise fall back to method2:
sub sort_method
{
my ($val1, $val2) = #_;
my $result = ($val1->method1 <=> $val2->method1)
||
($val1->method2 <=> $val2->method2);
return 1 if $result == -1;
}
If method1 and method2 return strings instead of numeric values, simply use
the cmp operator instead of <=>. You can use anything you like, as long
as the operator returns the right values. Most sort functions like using the
values -1, 0 and 1 to indicate whether value1 is less than, equal to, or
greater than value2, but this module likes 1 to mean val1 < val2, so after
gathering the -1, 0, 1 result, one then returns 1 if the result is -1 (where
value1 is less than value2).
First of all, you write a function that takes two of the objects you want to put in the heap, and returns a true value if the first one is smaller than the second, and false otherwise.
Then supply that as a coderef to Heap::Simple.
The example from the Heap::Simple docs is as follows:
use Heap::Simple;
sub more { return $_[0] > $_[1] }
my $heap = Heap::Simple->new(order => \&more);
$heap->insert(8, 3, 14, -1, 3);
print $heap->extract_top, " " for 1..$heap->count;
print "\n";
# Will print: 14 8 3 3 -1