I would like access to a hash table element created by a function without creating an intermediate variable.
Here's code to explain what I want to do:
Function bla($aParameter) {
$ret = #{}
for($i in XX) {
$ret.Add([int]$i, [string]$value)
}
$ret
}
$myVar = ""
$myVar += (bla $aParameter).1 + ","
I tried $myVar += ((bla $aParameter).1) + "," without much success.
You need to cast the result to string like this:
$myVar += [string]((bla $aParameter).1) + ","
Which is weird since I already cast the value of my HashTable to [string].
Related
Consider the following CSV:
email/1,email/2
abc#xyz.org,bob#pass.com
You can easily convert it to JSON (taking into account the paths defined by the keys) with Miller:
mlr --icsv --ojson --jflatsep '/' cat file.csv
[ { "email": ["abc#xyz.org", "bob#pass.com"] } ]
Now, if the paths are 0-indexed in the CSV (which is surely more common):
email/0,email/1
abc#xyz.org,bob#pass.com
Then, without prior knowledge of the fields names, it seams that you'll have to rewrite the whole conversion:
edit: replaced the hard-coded / with FLATSEP builtin variable:
mlr --icsv --flatsep '/' put -q '
begin { #labels = []; print "[" }
# translate the original CSV header from 0-indexed to 1-indexed
NR == 1 {
i = 1;
for (k in $*) {
#labels[i] = joinv( apply( splita(k,FLATSEP), func(e) {
return typeof(e) == "int" ? e+1 : e
}), FLATSEP );
i += 1;
}
}
NR > 1 { print #object, "," }
# create an object from the translated labels and the row values
o = {};
i = 1;
for (k,v in $*) {
o[#labels[i]] = v;
i += 1;
}
#object = arrayify( unflatten(o,FLATSEP) );
end { if (NR > 0) { print #object } print "]" }
' file.csv
I would like to know if I'm missing something obvious, like a command line option or a way to rename the fields with the put verb, or maybe something else? You're also welcome to give your insights about the previous code, as I'm not really confident in my Miller's programming skills.
Update:
With #aborruso approach of pre-processing the CSV header, this could be reduced to:
note: I didn't keep the regextract part because it means knowing the CSV header in advance.
mlr --csv -N --flatsep '/' put '
NR == 1 {
for (i,k in $*) {
$[i] = joinv( apply( splita(k,FLATSEP), func(e) {
return typeof(e) == "int" ? e+1 : e
}), FLATSEP );
}
}
' file.csv |
mlr --icsv --flatsep '/' --ojson cat
Even if there are workarounds like using the rename verb (when you know the header in advance) or pre-processing the CSV header, I still hope that Miller's author could add an extra command-line option that would deal with this kind of 0‑indexed external data; adding a DSL function like arrayify0 (and flatten0) could also prove useful in some cases.
I would like to know if I'm missing something obvious, like a command line option or a way to rename the fields with put verb, or maybe something else?
Starting from this
email/0,email/1
abc#xyz.org,bob#pass.com
you can use implicit CSV header and run
mlr --csv -N put 'if (NR == 1) {for (k in $*) {$[k] = "email/".string(int(regextract($[k],"[0-9]+"))+1)}}' input.csv
to have
email/1,email/2
abc#xyz.org,bob#pass.com
I have a powershell function that is not working the way it should. It is supposed to limit the choices given to it in $prm to a maximum of 5. More than 5 it should alert the user. If 0 is passed in the string then default to null.
Can someone advise what I need to do to fix this:
Function GetListValues($prm, $charCount){
$buildStr="Call db.Fruit("
#no selection
if ($charCount -eq 0 ){
$buildStr = $buildStr + "NULL,NULL,NULL,NULL,NULL);"
write $buildStr
}elseif($charCount -ge 1 -and $charCount -le 4 ){
#selections made with then 5 parameter range
$arr = $prm.split(",");
if ($arr[0]) { $buildStr = $buildStr + $arr[0] + "," } else { $buildStr = $buildStr + "Null," }
if ($arr[1]) { $buildStr = $buildStr + $arr[1] + "," } else { $buildStr = $buildStr + "Null," }
if ($arr[2]) { $buildStr = $buildStr + $arr[2] + "," } else { $buildStr = $buildStr + "Null," }
if ($arr[3]) { $buildStr = $buildStr + $arr[3] + "," } else { $buildStr = $buildStr + "Null," }
if ($arr[4]) { $buildStr = $buildStr + $arr[4] + ");" } else {$buildStr = $buildStr + "Null);" }
write $buildStr
}else{
# too many selections
[System.Windows.MessageBox]::Show('Too many selections! A maximum of 5 only!')
}
}
$prm = "'Apple','Orange','Pear','Banana','Grapes'"
$charCount = ($prm.ToCharArray() | Where-Object {$_ -eq ','} | Measure-Object).Count
GetListValues $prm, $charCount
Your problem is with your test code, not your Function. For powershell, you only use spaces to delimit parameters, not commas.
So if you change your test to
GetListValues $prm $charCount
Then the code works.
You can ignore my earlier comment, as I assumed that your $charCount value was being set to the number of elements. But on closer inspection I see that you are just counting the number of commas, and so the number of elements will be #commas + 1 (as long as you have >1 elements)
BTW, the $charCount is somewhat redundant, as the Function could work this out for itself, and would make the function more resilient as it would remove the possibility of the calling code passing inconsistent values.
DeanOC's helpful answer points out your immediate problem with the argument-passing syntax.
Additionally, as he suggests, you needn't determine the element count outside the function - it's easier and more robust to let the function itself handle that.
Here's a PowerShell-idiomatic reformulation of your function that does just that:
function GetListValues {
param(
[ValidateCount(0,5)] # Allow between 0 and 5 values.
[string[]] $Columns
)
# Create a 5-element array filled with the input column names
# and 'Null' for any remaining elements.
$allColumns = New-Object string[] 5
for ($i = 0; $i -lt $allColumns.Count; ++$i) {
$allColumns[$i] = if ($i -lt $Columns.Count) { $Columns[$i] } else { 'Null' }
}
# Use string expansion (interpolation) to construct the output string.
"Call db.Fruit($($allColumns -join ','))"
}
Defining the parameter as [string[]] allows you to (a) pass the column names individually and (b) easily gives you access to their count and allows you to constrain the acceptable range of column names via the ValidateCount attribute.
Therefore you can call the function above as follows:
# Pass 5 column names.
# Note that with the simple names at hand you needn't even quote them.
PS> GetListValues Apple, Orange, Pear, Banana, Grapes
Call db.Fruit(Apple,Orange,Pear,Banana,Grapes)
# Pass no column names at all.
PS> GetListValues
Call db.Fruit(Null,Null,Null,Null,Null)
# Pass too many names -> ValidateCount triggers an error.
PS> GetListValues Apple, Orange, Pear, Banana, Grapes, TooMuch
GetListValues : Cannot validate argument on parameter 'Columns'.
The parameter requires at least 0 value(s) and no more than 5 value(s)
- 6 value(s) were provided.
A variant solution (requested later by the OP) that:
allows passing the max. number of columns as a parameter
passes the column names as a single string with embedded quoting (e.g., "'Apple', 'Orange', 'Pear', 'Banana', 'Grapes'").
function GetListValues {
param(
[string] $ColumnList,
[int] $MaxColumnCount
)
# Split something like "'Apple', 'Orange', 'Pear', 'Banana', 'Grapes'"
# into an array of tokens.
$Columns = $ColumnList -split "[, ']" -ne ''
if ($Columns.Count -gt $MaxColumnCount) { Throw "Too many columns passed." }
# Create an N-element array filled with the input column names
# and 'Null' for any remaining elements.
$allColumns = New-Object string[] $MaxColumnCount
for ($i = 0; $i -lt $allColumns.Count; ++$i) {
$allColumns[$i] = if ($i -lt $Columns.Count) { $Columns[$i] } else { 'Null' }
}
# Use string expansion (interpolation) to construct the output string.
"Call db.Fruit($($allColumns -join ','))"
}
Example calls:
PS> GetListValues "'Apple', 'Orange', 'Pear', 'Banana', 'Grapes'" 5
Call db.Fruit(Apple,Orange,Pear,Banana,Grapes)
PS> GetListValues "" 3
Call db.Fruit(Null,Null,Null)
I used an anonymous hash to pass value from two different subroutines to a new subroutine. But, now I'm not able to perform calculations using the passed variables.
use warnings;
use strict;
use feature 'say';
use DBI;
use autodie;
use Data::Dumper;
use CGI;
print "Enter sequence";
my $seq = <STDIN>;
chomp $seq;
$len = length $seq;
my $f = nuc($seq);
perc({ len => $len });
sub nuc {
my ($c) = #_;
chomp $c;
my $len = length $c;
for (my $i = 0; $i< = $len; $i++) {
my $seq2 = substr($c, $i, 1);
$nuc=$nuc . $seq2;
chomp $nuc;
}
my $l = perc({nuc => $nuc});
}
sub perc {
my $params = shift;
my $k = $params->{nuc};
my $w = $params->{len};
my $db = "hnf1a";
my $user = "root";
my $password = "";
my $host = "localhost";
my $dbh = DBI->connect("DBI:mysql:database=$db:$host",$user,$password);
my $sth = $dbh->prepare('SELECT COUNT(*) FROM mody where nm = ?');
for (1..100) {
$sth->execute(int(rand(10)));
}
chomp (my $input = $k);
my #num = split /':'/, $input;
for my $num(#num) {
say "rows matching input nuc <$num>:";
$sth->execute($num);
my $count = $sth->fetchrow_array;
say "$count";
$u += $count;
}
}
$h = $u / $w;
print $h;
I passed the variables : $nuc and $len to the last subroutine 'perc' by declaring an anonymous hash.
When I use these variables to perform calculations I don't get a proper answer.
For the above division performed I got a statement as 'Illegal division'.
Please help me out. Thanks in advance.
You are making two separate calls to perc, each with only one of the required values in the hash. You can't do that: the subroutine won't "remember" a value passed to it across separate calls unless you write the code to do that
You need to collect all the values and pass them in a single call to perc
There are rather a lot of misunderstandings here. Let's go through your code.
use CGI;
Using CGI.pm is a bit dated, but it's not a terrible idea if you're writing a CGI program. But this isn't a CGI program, so this isn't necessary.
print "Enter sequence";
my $seq = <STDIN>;
chomp $seq;
$len = length $seq;
my $f = nuc($seq);
This looks OK. You prompt the user, get some input, remove the newline from the end of the input, get the length of the input and then pass your input into nuc().
So, let's look at nuc() - which could probably have a better name!
sub nuc {
my ($c) = #_;
chomp $c;
my $len = length $c;
for (my $i = 0; $i< = $len; $i++) {
my $seq2 = substr($c, $i, 1);
$nuc=$nuc . $seq2;
chomp $nuc;
}
my $l = perc({nuc => $nuc});
}
You get the parameter that has been passed in and remove the newline from the end of it (which does nothing as this is $seq which has already had its newline removed). You then get the length of this string (again!)
Then it gets very strange. Firstly, there's a syntax error (< = should be <=). Then you use a C-style for loop together with substr() too... well, basically you just copy $c to $nuc in a really inefficient manner. So this subroutine could be written as:
sub nuc {
my ($c) = #_;
$nuc = $c;
my $l = perc({ nuc => $nuc });
}
Oh, and I don't know why you chomp($nuc) each time round the loop.
Two more strange things. Firstly, you don't declare $nuc anywhere, and you have use strict in your code. Which means that this code doesn't even compile. (Please don't waste our time with code that doesn't compile!) And secondly, you don't explicitly return a value from nuc(), but you store the return value in $f. Because of the way Perl works, this subroutine will return the value in $l. But it's best to be explicit.
Then there's your perc() subroutine.
sub perc {
my $params = shift;
my $k = $params->{nuc};
my $w = $params->{len};
my $db = "hnf1a";
my $user = "root";
my $password = "";
my $host = "localhost";
my $dbh = DBI->connect("DBI:mysql:database=$db:$host",$user,$password);
my $sth = $dbh->prepare('SELECT COUNT(*) FROM mody where nm = ?');
for (1..100) {
$sth->execute(int(rand(10)));
}
chomp (my $input = $k);
my #num = split /':'/, $input;
for my $num(#num) {
say "rows matching input nuc <$num>:";
$sth->execute($num);
my $count = $sth->fetchrow_array;
say "$count";
$u += $count;
}
}
You get the hash ref which is passed in an store that in $params. You then extract the nuc and len values from that hash and store them in variables called $k and $w (you really need to improve your variable and subroutine names!) But each call to perc only has one of those values set - so only one of your two variables get a value, the other will be undef.
So then you connect to the database. And you run a select query a hundred times passing in random integers between 0 and 9. And ignore the value returned from the select statement. Which is bizarre and pointless.
Eventually, you start doing something with one of your input parameters, $k (the other, $w, is completely ignored). You copy it into another scalar variable before splitting it into an array. You then run the same SQL select statement once for each element in that array and add the number you get back to the running total in $u. And $u is another variable that you never declare, so (once again) this code doesn't compile.
Outside of your subroutines, you then do some simple maths with $u (an undeclared variable) and $w (a variable that was declared in a different scope) and store the result in $h (another undeclared variable).
I really don't understand what this code is supposed to do. And, to be honest, I don't think you do too. If you're at school, then you need to go back to your teacher and say that you have no idea what you are doing. If you're in a job, you need to tell your boss that you're not the right person for this task.
Either way, if you want to be a programmer, you need to go right back to the start and cover the very basics again.
I have a series of JSON objects and I need to replace all the commas at the end of each object with a pipe |
Obviously I can't use Find & Replace because that would replace every comma in the JSON, but I only want to replace those at the end of each object.
For example:
{
"id":123,
"name":Joe,
"last":Smith
} , <----- I want to replace this comma only
{"id":454
"name":Bill,
"last":Smith
}
You could parse the JSON by adding '[]' around it and then re-serialize it.
With a PHP script you could do something like this:
$content = file_get_contents('/path/to/yourfile.json');
// Add [] around the JSON to make it valid:
$json = json_decode('[' . $json . ']', true);
$result = '';
foreach ($json as $j) {
if ($result != '') $result .= '|';
$result .= json_encode($j);
}
echo $result;
There is already a PHP solution. Here's a Regex solution in case.
string s1 = "{\"id\":123,\"name\":Joe,\"last\":Smith} , {\"id\":454,\"name\":Bill,\"last\":Smith}";
string pattern = "} , {";
var s2 = Regex.Split(s1, pattern);
string s3 = string.Join(" | ", s2);
I am a newcomer to Perl (Strawberry Perl v5.12.3 on Windows 7), trying to write a script to aid me with a repetitive HTML formatting task. The files need to be hand-edited in future and I want them to be human-friendly, so after processing using the HTML package (HTML::TreeBuilder etc.), I am writing the result to a file using HTML::PrettyPrinter. All of this works well and the output from PrettyPrinter is very nice and human-readable. However, PrettyPrinter is not handling self-closing tags well; basically, it seems to be treat the slash as an HTML attribute. With input like:
<img />
PrettyPrinter returns:
<img /="/" >
Is there anything I can do to avoid this other than preprocessing with a regex to remove the backslash?
Not sure it will be helpful, but here is my setup for the pretty printing:
my $hpp = HTML::PrettyPrinter->new('linelength' => 120, 'quote_attr' => 1);
$hpp->allow_forced_nl(1);
my $output = new FileHandle ">output.html";
if (defined $output) {
$hpp->select($output);
my $linearray_ref = $hpp->format($internal);
undef $output;
$hpp->select(undef),
}
You can print formatted human readable html with TreeBuilder method:
$h = HTML::TreeBuilder->new_from_content($html);
print $h->as_HTML('',"\t");
but if you still prefer this bugged prettyprinter try to remove problem tags, no idea why someone need ...
$h = HTML::TreeBuilder->new_from_content($html);
while(my $n = $h->look_down(_tag=>img,'src'=>undef)) { $n->delete }
UPD:
well... then we can fix the PrettyPrinter. It's pure perl module so lets see...
No idea where on windows perl modules are for me it's /usr/local/share/perl/5.10.1/HTML/PrettyPrinter.pm
maybe not an elegant solution, but will work i hope.
this sub parse attribute/value pairs, a little fix and it will add single '/' at the end
~line 756 in PrettyPrinter.pm
I've marked the stings that i added with ###<<<<<< at the end
#
# format the attributes
#
sub _attributes {
my ($self, $e) = #_;
my #result = (); # list of ATTR="value" strings to return
my $self_closing = 0; ###<<<<<<
my #attrs = $e->all_external_attr(); # list (name0, val0, name1, val1, ...)
while (#attrs) {
my ($a,$v) = (shift #attrs,shift #attrs); # get current name, value pair
if($a eq '/') { ###<<<<<<
$self_closing=1; ###<<<<<<
next; ###<<<<<<
} ###<<<<<<
# string for output: 1. attribute name
my $s = $self->uppercase? "\U$a" : $a;.
# value part, skip for boolean attributes if desired
unless ($a eq lc($v) &&
$self->min_bool_attr &&.
exists($HTML::Tagset::boolean_attr{$e->tag}) &&
(ref($HTML::Tagset::boolean_attr{$e->tag}).
? $HTML::Tagset::boolean_attr{$e->tag}{$a}.
: $HTML::Tagset::boolean_attr{$e->tag} eq $a)) {
my $q = '';
# quote value?
if ($self->quote_attr || $v =~ tr/a-zA-Z0-9.-//c) {
# use single quote if value contains double quotes but no single quotes
$q = ($v =~ tr/"// && $v !~ tr/'//) ? "'" : '"'; # catch emacs ");
}
# add value part
$s .= '='.$q.(encode_entities($v,$q.$self->entities)).$q;
}
# add string to resulting list
push #result, $s;
}
push #result,'/' if $self_closing; ###<<<<<<
return #result; # return list ('attr="val"','attr="val"',...);
}