Array lowercase - arrays

I'm new to Perl and I've ran into a little problem. I'm trying to pick one value out of an array to make it lowercase.
E.g.
my #letters = qw(A B C D E F F A S D F E S F);
Now I want to pick the letter F and make it lowercase, how can I go about doing this?
Keep in mind my array is very large and it should be able to take any changes made by the user.
Thanks in advance.

for my $c (#letters) {$c = lc($c) if $c eq"F"}
I absolutely don't know what you mean by pick and make it.

This is my approach:
my #letters = qw(A B C D E F F A S D F E S F);
#letters = map { tr/F/f/; $_ } #letters;
UPDATE: as #TLP commented, this is a simpler way:
my #letters = qw(A B C D E F F A S D F E S F);
tr/F/f/ for #letters;

Unless you want to keep the orginal list
tr/F/f/ for #letters

Short inplace modification is
my #letters = qw(A B C D E F F A S D F E S F);
map {s/(F)/\l$1/;} #letters;
print "#letters";
Output:
A B C D E f f A S D f E S f

If you already know the location in the array, you would do: $array[5] = lc($array[5]). If you don't there are several ways. One of them uses C style looping:
for (my $i=0; $i < #array; $i++) {
$array[$i] = lc($array[$i]) if $array[$i] eq 'F';
}
another is map:
map {$_ = lc if $_ eq 'F'} #array;

Related

Perl remove same value back to back with splice

I am trying to remove, the same values twice in an array, it is located back to back, this is my code
#{$tmp_h->{'a'}} = qw/A B B C/;
print Dumper ($tmp_h);
my $j = 0;
foreach my $cur (#{$tmp_h->{'a'}}) {
if ($cur eq 'B') {
splice(#{$tmp_h->{'a'}}, $j, 1);
}
$j++;
}
print Dumper $tmp_h;
However what got is,
$VAR1 = {
'a' => [
'A',
'B',
'B',
'C'
]
};
$VAR1 = {
'a' => [
'A',
'B',
'C'
]
};
I am expecting both 'B' to be removed in this case, what could possibly went wrong?
That code is removing from an array while iterating over it, pulling the carpet from underneath itself; is that necessary?
Instead, iterate and put elements on another array if the adjacent ones aren't equal. So iterate over the index, looking up an element and the next (or previous) one.†
I presume that B is just an example while in fact it can be any value, equal to its adjacent one.
It's interesting that regex can help too, with its simple way to find repeated patterns using backreferences
my #ary = qw(a b b c d d e f f f g);
my $str_ary = join '', #ary;
$str_ary =~ s/(.)\g{-1}//g;
my #new_ary = split //, $str_ary;
say "#new_ary"; #--> a c e f g
This removes pairs of adjacent values, so if there is an odd number of equal adjacent values it leaves the odd one (f above). As a curiosity note that it can be written in one statement
my #new_ary = split //, join('', #ary) =~ s/(.)\g{-1}//gr;
The join-ed array, forming a string, is bound to the substitution operator where /r modifier is crucial, for allowing this and returning the changed string which is then split back into a list.
To change an array in place have it assign to itself.‡
But single-letter elements are only an example, likely. With multiple characters in elements we can't join them by empty string because we wouldn't know how to split that back into an array; we have to join by something that can't be in any one element, clearly a tricky proposition. A reasonable take is a line-feed, as one can expect to know whether elements are/not multiline strings
my #ary = qw(aa no no way bah bah bah go);
my $str_ary = join "\n", #ary ;
$str_ary =~ s/([^\n]+)\n\g{-1}//g;
my #new = grep { $_ } split /\n/, $str_ary;
say "#new"; #--> aa way bah go
This would still have edge cases with interesting elements, like spaces and empty strings (but then any approach would).
† For example
use warnings;
use strict;
use feature 'say';
my #ary = qw(a b b c d d e f f f g);
my #new_ary;
my $i = 0;
while (++$i <= $#ary) {
if ($ary[$i] ne $ary[$i-1]) {
push #new_ary, $ary[$i-1]
}
else { ++$i }
}
push #new_ary, $ary[-1] if $ary[-1] ne $ary[-2];
say "#new_ary"; #--> a c e f g
‡ Done for the arrayref in the question
#{ $hr->{a} } = qw/A B B C/;
#{$hr->{a}} = split //, join('', #{$hr->{a}}) =~ s/(.)\g{-1}//gr;
say "#{$hr->{a}}"; #--> A C
The Perl documentation tells you in perlsyn under Foreach Loops:
If any part of LIST is an array, foreach will get very confused if you
add or remove elements within the loop body, for example with splice. So
don't do that.
You can iterate over the indices instead, but don't forget to not increment the index when removing a value:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my $tmp_h = {a => [qw[ A B B C ]]};
print Dumper($tmp_h);
my $j = 0;
while ($j <= $#{ $tmp_h->{a} }) {
my $cur = $tmp_h->{a}[$j];
if ($cur eq 'B') {
splice #{ $tmp_h->{a} }, $j, 1;
} else {
++$j;
}
}
print Dumper($tmp_h);
Or start from the right so you don't have to worry:
my $j = $#{ $tmp_h->{a} };
while ($j-- >= 0) {
my $cur = $tmp_h->{a}[$j];
splice #{ $tmp_h->{a} }, $j, 1 if $cur eq 'B';
}
But the most straight forward way is to use grep:
#{ $tmp_h->{a} } = grep $_ ne 'B', #{ $tmp_h->{a} };

Two-dimensional Array of Same Character

I am experimenting on two-dimensional arrays. I want some sort of a matrix, all with the same character. I can define a blank multi-dimensional array with fixed elements, and supply it with a character using loops. However, I can also do #(something)*n to directly define an array already supplied with something.
From what I understood so far, this is how to do it:
> $arr = ,(,'E'*3)*3
These seems alright:
> $arr[1]
E
E
E
> $arr[1][2]
E
But when I try to replace a character somewhere, like $arr[1][2] = 'D', many characters are replaced:
> $arr
E
E
D
E
E
D
E
E
D
Is my array definition wrong? Added: Then, how to correctly define it 'quickly'?
Using the * operator on non-numeric values creates copies of the original value. However, if the item you're copying isn't of a primitive(-ish) type like String or Char, the result will not be a duplicate of that object, but a copy of the object reference. Since all instances will then be pointing to the same object, changing one will change all.
To create distinct instances you need to repeat the array instantiation in a loop, as PetSerAl showed in the comments:
$arr = 1..3 | ForEach-Object { ,(,'E' * 3) }
In this particular case you could also create a "template" array and clone it:
$a0 = ,'E' * 3
$arr = 1..3 | ForEach-Object { ,$a0.Clone() }
Note, however, that cloning an object will not clone nested object references, so the latter is not a viable approach in all scenarios.
Something like this won't work the way you intend (because the references of the nested hashtable objects are still pointing to the same actual hashtables after cloning the array object):
PS C:\> $a0 = ([PSCustomObject]#{'x'='E'}),([PSCustomObject]#{'x'='E'})
PS C:\> $arr = 1..2 | ForEach-Object { ,$a0.Clone() }
PS C:\> $arr
x
-
E
E
E
E
PS C:\> $arr[1][1].x = 'F'
PS C:\> $arr
x
-
E
F
E
F
But something like this will work:
PS C:\> $arr = 1..2 | ForEach-Object { ,(([PSCustomObject]#{'x'='E'}),([PSCustomObject]#{'x'='E'})) }
PS C:\> $arr
x
-
E
E
E
E
PS C:\> $arr[1][1].x = 'F'
PS C:\> $arr
x
-
E
E
E
F

How dow I stringify an array without spaces between its elements?

$readFile = get-content $readInput
#create an empty array to be filled with bank account numbers
$fNameArray = #()
for($i = 0; $i -lt $readFile.length; $i++){
#assigns a random letter from the list to $letter.
#$letter = get-random -inputobject ("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z") -count $readFile.length
$letter = $readFile[$i] | foreach-object{get-random -inputobject ("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z") -count $readFile[$i].length}
$fnameArray += "$letter"
}
$fnameArray
the code is reading in a file that has a list of names and randomizing the letters for Data Masking. The only problem I am running into is the output is like such:
L R Y E B
R O M I
U Q N G R
H K Y
M G A W Q
J G W Y D K T
X E Q
J Y P I G
It looks like it is output with spaces between the letters. How do I eliminate them?
The unary form of the -join operator joins (concatenates) all array elements without a separator.
> -join ('a', 'b', 'c')
abc
Therefore, simply use:
$fnameArray += -join $letter
By contrast, "$letter" stringifies the array using $OFS (the output field separator) as the separator, which defaults to a space, which explains your output.
Therefore, you could alternatively set $OFS to '' (the empty string) and use "$letter".
However, the -join approach is simpler and doesn't require you to create a local scope for / restore the previous $OFS value.

Move element in perl array

I have an element in an array that I'd like to move accordingly.
#array = ("a","b","d","e","f","c");
Basically I'd like to find the index of "c" and then place it before "d" again based on "d"'s index. I'm using these characters as an example. It has nothing to do with sorting alphabetically.
Try doing this using array slice and List::MoreUtils to find array elements indexes :
use strict; use warnings;
use feature qw/say/;
# help to find an array index by value
use List::MoreUtils qw(firstidx);
my #array = qw/a b d e f c/;
# finding "c" index
my $c_index = firstidx { $_ eq "c" } #array;
# finding "d" index
my $d_index = firstidx { $_ eq "d" } #array;
# thanks ysth for this
--$d_index if $c_index < $d_index;
# thanks to Perleone for splice()
splice( #array, $d_index, 0, splice( #array, $c_index, 1 ) );
say join ", ", #array;
See splice()
OUTPUT
a, b, c, d, e, f
my #array = qw/a b d e f c/;
my $c_index = 5;
my $d_index = 2;
# change d_index to what it will be after c is removed
--$d_index if $c_index < $d_index;
splice(#array, $d_index, 0, splice(#array, $c_index, 1));
Well, here's my shot at it :-)
#!/usr/bin/perl
use strict;
use warnings;
use List::Util qw/ first /;
my #array = ("a","b","d","e","f","c");
my $find_c = 'c';
my $find_d = 'd';
my $idx_c = first {$array[$_] eq $find_c} 0 .. $#array;
splice #array, $idx_c, 1;
my $idx_d = first {$array[$_] eq $find_d} 0 .. $#array;
splice #array, $idx_d, 0, $find_c;
print "#array";
This prints
C:\Old_Data\perlp>perl t33.pl
a b c d e f
Another solution using array slices. This assumes you know the desired of the elements in the array.
use strict;
use warnings;
my #array = qw(a b d e f c);
print #array;
my #new_order = (0, 1, 5, 2, 3, 4);
my #new_list = #array[#new_order];
print "\n";
print #new_list;
See this link to PerlMonks for details.
You can use splice to insert an element at a specific index in an array. And a simple for loop to find the indexes you seek:
my #a = qw(a b d e f c);
my $index;
for my $i (keys #a) {
if ($a[$i] eq 'c') {
$index = $i;
last;
}
}
if (defined $index) {
for my $i (keys #a) {
if ($a[$i] eq 'd') {
splice #a, $i, 1, $a[$index];
}
}
}
use Data::Dumper;
print Dumper \#a;
Output:
$VAR1 = [
'a',
'b',
'c',
'e',
'f',
'c'
];
Note that this code does not remove the c element. To do that you need to keep track of whether you insert the c before or after d, since you are changing the indexes of the array.
U can try this
my $search = "element";
my %index;
#index{#array} = (0..$#array);
my $index = $index{$search};
print $index, "\n";

Access array elements of passed hash

I have got two hashes with a foldername as key and its respective files as an array. But I cannot acces the array elements of the passed hash in the getMissingFiles sub (see comments for error message).
Hashes to be compared:
# contains all files
%folderWithFiles1 =
(
foldername1 => [ qw(a b c d e f g h i j k l m n o p) ],
foldername2 => [ qw(a b c d e f g h i j k l m n ) ],
)
%folderWithFiles2 =
(
foldername1 => [ qw(a b d e h i l m n p) ],
foldername2 => [ qw(a d f g h j m ) ],
)
Compare subroutine (get missing files from hash2 that are not in hash1):
sub getMissingFiles()
{
my ($hash1, $hash2) = shift; # is it working?
#my $hash1 = shift; # or do you have to do it separately?
#my $hash2 = shift;
my $flag = 0;
my #missingFiles;
foreach my $folder (sort(keys %{$hash1}))# (sort(keys %hash1)) not possible?
{
for (my $i = 0; $i < #$hash1{$folder}; $i++)
{
foreach my $folder2 (sort(keys %{$hash2}))
{
foreach my $file2 (#$hash2{$folder2})
{
if ($hash1{$folder}[$i] == $file2) # Error: Global symbol "%hash1" requires explicit package name
{
$flag = 1;
last;
}
}
if (0 == $flag)
{
push(#missingFiles, $hash1{$folder}[$i]); # Error: Global symbol "%hash1" requires explicit package name
}
else
{
$flag = 0;
}
}
}
}
return #missingFiles;
}
Calling function:
#missingFiles = &getMissingFiles(\%hash1, \%hash2);
Is: "my ($hash1, $hash2) = shift;" correct or do you have to do it separately?
Why is "foreach my $folder (sort(keys %hash1))" not possible?
Is there a more efficient way than using 4 loops?
In getMissingFiles(), just as you dereference $hash1 and $hash2 to get the keys, you also need to dereference them to get the values:
#folder_files = #{ $hash1->{$folder1} };
or alternatively,
#folder_files = #{ $$hash1{$folder} };
And you can do this to get individual files:
$file = $hash1->{$folder}[$i];
That call syntax isn't quite right - you want
my ($hash1, $hash2) = #_;
or perhaps
my $hash1 = shift;
my $hash2 = shift;
The shift function will only give you the first value, so you need to call twice as you suggest, or access the parameter list #_ if you want to pluck more than value in one go.

Resources