How to avoid backslashitis? - list

I have the following code:
proc list_backslash {} {
array unset options
array set options {
-inputs {vdd}
-outputs {vss}
}
set inputs { vdd2 vdd dvdd }
set outputs { vss2 vss dvss }
set updateOptions [ list \
-inputs $inputs \
-outputs $outputs ]
array set options $updateOptions
foreach {k v} [array get options] {
puts "$k => $v"
}
}
Since I have a lot of key-value pairs in updateOptions, there is a severe backslashitis! Is there a better way to do code updateOptions? I tried subst + braces {} and realized it does not preserve the list structure thus dooming it.

Generally speaking, if you need to continue a line you have to use a quoting mechanism of some kind with Tcl. Otherwise, the command call ends when the line ends. The [brackets] can include multiple statements too; it's legal, but really not recommended.
But that does mean that sometimes you've got awkward alternatives. Perhaps you'll be best off with doing this:
set updateOptions {
-inputs $inputs
-outputs $outputs
}
foreach {key value} $updateOptions {
set options($key) [subst $value]
}
The array set command isn't especially efficient until you get to huge numbers of options (many thousands) when the code is inside a procedure.
Or if you've got Tcl 8.6, dict map is perhaps better:
array set options [dict map {key value} $updateOptions {subst $value}]
Be aware that subst is not a particularly efficient command in Tcl 8.6 except when used with a literal argument. That's because with variable arguments, it compiles them to bytecode at runtime.

option a) Put it all on one line.
option b) Structure the code as:
set options(-inputs) $inputs
set options(-outputs) $outputs
option c) Learn to like backslashes.

Related

Create list from list items

I am trying to crate a new array/list from an existing list of items. I am wanting to check if the item exist first, if it does not, create it then add a value to it. If it already exist just append a value. I also need a way to get the length of the total.
set Area {23401 23402 23403}
foreach Item $Area {
set ElExist [info exist ${Item}lst]
if {$ElExist == 0} {
set ${Item}lst {};
lappend ${Item}lst $TotalRecords
} else {
lappend ${Item}lst $TotalRecords
}
set CurrentOptinon [llength ${Item}lst]
}
If I was writing that code, I'd do it like this:
set Area {23401 23402 23403}
foreach Item $Area {
upvar 0 ${Item}lst lst
lappend lst $TotalRecords
set CurrentOptinon [llength $lst]
}
This will behave the same as your code, but it's so much shorter. Here's the tricks in use:
lappend creates a variable if it didn't already exist.
upvar 0 makes a local alias to a variable. So much simpler.
The alias removes the need for magic with llength, but otherwise you could have done:
set CurrentOptinon [llength [set ${Item}lst]]
The $ syntax is in many ways just a short-cut for a call to the single-argument version of set, which reads the named variable. Except if you write set then you can use substitutions in your variable name. As a rule of thumb, if you're extensively using variable names in variables without aliasing, you're probably doing something wrong (unless you really do need the name).
You're using weird variable names. Much better would be an array.
set Area {23401 23402 23403}
foreach Item $Area {
lappend lst($Item) $TotalRecords
set CurrentOptinon [llength $lst($Item)]
}
However, this is likely to require you to change code elsewhere.

Error in regex hyphen usage inside brackets utilizing perl

I have this perl script that compares two arrays to give me back those results found in both of them. The problem arises I believe in a regular expression, where it encounters a hyphen ( - ) inside of brackets [].
I am getting the following error:
Invalid [] range "5-3" in regex; marked by <-- HERE in m/>gi|403163623|ref|XP_003323683.2| leucyl-tRNA synthetase [Puccinia graminis f. sp. tritici CRL 75-3 <-- HERE 6-700-3]
MAQSTPSSIQELMDKKQKEATLDMGGNFTKRDDLIRYEKEAQEKWANSNIFQTDSPYIENPELKDLSGEE
LREKYPKFFGTFPYPYMNGSLHLGHAFTISKIEFAVGFERMRGRRALFPVGWHATGMPIKSASDKIIREL
EQFGQDLSKFDSQSNPMIETNEDKSATEPTTASESQDKSKAKKGKIQAKSTGLQYQFQIMESIGVSRTDI
PKFADPQYWLQYFPPIAKNDLNAFGARVDWRRSFITTDINPYYDAFVRWQMNRLKEKGYVKFGERYTIYS
PKDGQPCMDHDRSSGERLGSQEYTCLKMKVLEWGPQAGDLAAKLGGKDVFFV at comparer line 21, <NUC> chunk 168.
I thought the error could be solved by just adding \Q..\E in the regex so as to bypass the [] but this has not worked. Here is my code, and thanks in advance for any and all help that you may offer.
#cyt = <CYT>;
#nuc = <NUC>;
$cyt = join ('',#cyt);
$cyt =~ /\[([^\]]+)\]/g;
#shared = '';
foreach $nuc (#nuc) {
if ($cyt =~ $nuc) {
push #shared, $nuc;
}
}
print #shared;
What I am trying to achieve with this code is compare two different lists loaded into the arrays #cyt and #nuc. I then compare the name in between the [] of one of the elements in list to to the name in [] of the other. All those finds are then pushed into #shared. Hope that clarifies it a bit.
Your question describes a set intersection, which is covered in the Perl FAQ.
How do I compute the difference of two arrays? How do I compute the intersection of two arrays?
Use a hash. Here's code to do both and more. It assumes that each
element is unique in a given array:
my (#union, #intersection, #difference);
my %count = ();
foreach my $element (#array1, #array2) { $count{$element}++ }
foreach my $element (keys %count) {
push #union, $element;
push #{ $count{$element} > 1 ? \#intersection : \#difference }, $element;
}
Note that this is the symmetric difference, that is, all elements in
either A or in B but not in both. Think of it as an xor operation.
Applying it to your problem gives the code below.
Factor out the common code to find the names in the data files. This sub assumes that
every [name] will be entirely contained within a given line rather than crossing a newline boundary
each line of input will contain at most one [name]
If these assumptions are invalid, please provide more representative samples of your inputs.
Note the use of the /x regex switch that tells the regex parser to ignore most whitespace in patterns. In the code below, this permits visual separation between the brackets that are delimiters and the brackets surrounding the character class that captures names.
sub extract_names {
my($fh) = #_;
my %name;
while (<$fh>) {
++$name{$1} if /\[ ([^\]]+) \]/x;
}
%name;
}
Your question uses old-fashioned typeglob filehandles. Note that the paramter extract_names expects is a filehandle. Convenient parameter passing is one of many benefits of indirect filehandles, such as those created below.
open my $cyt, "<", "cyt.dat" or die "$0: open: $!";
open my $nuc, "<", "nuc.dat" or die "$0: open: $!";
my %cyt = extract_names $cyt;
my %nuc = extract_names $nuc;
With the names from cyt.dat in the hash %cyt and likewise for nuc.dat and %nuc, the code here iterates over the keys of both hashes and increments the corresponding keys in %shared.
my %shared;
for (keys %cyt, keys %nuc) {
++$shared{$_};
}
At this point, %shared represents a set union of the names in cyt.dat and nuc.dat. That is, %shared contains all keys from either %cyt or %nuc. To compute the set difference, we observe that the value in %shared for a key present in both inputs must be greater than one.
The final pass below iterates over the keys in sorted order (because hash keys are stored internally in an undefined order). For truly shared keys (i.e., those whose values are greater than one), the code prints them and deletes the rest.
for (sort keys %shared) {
if ($shared{$_} > 1) {
print $_, "\n";
}
else {
delete $shared{$_};
}
}

Flatten list of negative numbers

I am trying to flatten the following type of Tcl lists:
-1587.500000 {} 1587.500000
or
15.78 18.56 {} {} {} {} 15.6
So I try:
[struct::list flatten -full $lineToFlatten]
But when lineToFlatten starts with the negative number the following type of error message is issued (for example):
Unknown option "-1587.500000 {} 1587.500000", should be either -full, or --
How to resolve this?
Normally, the correct way is to use the -- to denote the end of the options. But there is a bug that prevents you from doing that (missing lrange).
The correct way to do that is:
struct::list flatten -full -- {-1587.500000 {} 1587.500000}
But this does not work because struct has a bug.
If you want to fix it, open the package file for struct::list, you can get the filename with
package ifneeded struct::list [package require struct::list]
Then edit the proc ::struct::list::Lflatten.
Change the line with
-- {break}
to
-- {set args [::lrange $args 1 end];break}
Anyway, I suggest filling a bug record.
I just discovered how to use this function ^^
Okay, I can't say why it acts like this, but it throws me an error for anything like:
[struct::list flatten -full $lineToFlatten]
[struct::list flatten -full {-1587.500000 {} 1587.500000}]
It seems that it interprets the negative sign as a switch/flag instead of an element of the list. However, it works if I do:
[struct::list flatten -full {{-1587.500000} {} 1587.500000}]
[struct::list flatten -full [list $lineToFlatten]]
The first one isn't very practical, since you'll have a long command and you have to put the braces manually for the first negative number. I found the second workaround while testing a bit around.
I hope it helps :)

converting list to tuple

I have few command-line options (5 for example) and I want to convert them to tuple. The problem is that I expect them to appear in correct order, so tuple can be easily built from list using pattern-match, but in real life options can be provided in random order, so I don't know if head of the list contain Verbose option or log file name?
I tried to think how to do that using continuation-passing style, however nothing useful comes into my mind.
Is that ever possible?
I think that I can "sort" the list to have it in predicted order, but it does not look good.
Also I could get rid of the tuple and create data record - however that will still lead up to checking the type of attribute and set the correct field of the record. Still a lot of typing.
Given what you describe, I think you have two options. Of the two, I would say that converting to a dictionary would be easiest, but converting to a Tuple would work and only be a little clumsy
So, take this definition:
options :: [OptDescr (String, String)]
options = [Option ['a'] ["alpha"] (ReqArg (\a -> ("alpha", a)) "empty") "",
Option ['b'] ["beta"] (ReqArg (\a -> ("beta", a)) "empty") "",
Option ['g'] ["gamma"] (ReqArg (\a -> ("gamma", a)) "empty") ""]
main = do
args <- getArgs
let (opts, nonopts, errs) = getOpt Permute options args
putStrLn $ show opts
From this, a couple of my example outputs are:
[("beta","b"),("alpha","a")]
[("alpha","a"),("gamma","g"),("beta","b")]
and so on. Same order as on the command line. But, because of the way I set it up above, I basically have an association list, so... if I in particular want a Tuple that has the values (alpha, beta, gamma), then my best option is...
(lookup "alpha" opts, lookup "beta" opts, lookup "gamma" opts)
You resulting data type would be (Maybe String, Maybe String, Maybe String), in the order of "alpha", "beta", and "gamma".

Is there a data structure similar to std::set in TCL?

TCL has a data structure called dict which maintains a collection of key-value pairs.
Is there another data structure which maintains a collection of keys (with no values)?
If no, then maybe someone already wrote a simple adapter on dict with empty values?
You could use the tcllib package ::struct::set.
http://tcllib.sourceforge.net/doc/struct_set.html
Just use a single list.
set example [list "key1" "key2" "key3"]
if {[lsearch -exact $example "key3"] != -1} {
puts "found your key!"
} else {
puts "your key is not in the list"
}
Maybe you should ask a more specific question to get a more accurate answer.
An alternative for dict is array which doesn't preserve the order of keys.
Another approach would be to accumulate everything into say, $bucket.
Then do:
set uniqueItems [lsort -unique $bucket]