Tcl: Persistent increment inside a list - list

I am trying to set-up multiple incr's for each entry in a list. I thought that I could assign an integer to each list entry...
set list {
{/run 00}
{/run/shm 00}
{/boot 00}
}
and use the following code as part of a foreach loop to increment the value...
lset list 1 [expr {[lindex $list 1] + 1}]
What I am finding is that the value increments correctly but when the code executes a second and third time the value has reset to 00, so it never increases past 1 on each pass.
If I set up a basic increment for a standard variable as part of the code..
set counter 00
incr counter
it quite happily increments on each run of the code and the counter increases by 1 until I break the code.
Any advise or help in getting this working would be much appreciated. I am definitely not a tcl expert so if I am trying to accomplish this the wrong way please let me know. :)
Thanks in advance for your help.

If you change your data structure slightly to flatten it out instead of using a list of pairs, it becomes usable as a dict. And there's a dict incr command:
This adds the given increment value (an integer that defaults to 1 if not specified) to the value that the given key maps to in the dictionary value contained in the given variable, writing the resulting dictionary value back to that variable. Non-existent keys are treated as if they map to 0. It is an error to increment a value for an existing key if that value is not an integer. The updated dictionary value is returned.
Example usage:
% set list {/run 0 /run/shm 0 /boot 0}
/run 0 /run/shm 0 /boot 0
% dict incr list /boot
/run 0 /run/shm 0 /boot 1
% puts $list
/run 0 /run/shm 0 /boot 1
If you want to do this in a command, you have to pass by name and use upvar so the changes are made in the right stack frame:
% proc demo {fstab_} {
upvar 1 $fstab_ fstab
dict incr fstab /run
}
% demo list
/run 1 /run/shm 0 /boot 1
% puts $list
/run 1 /run/shm 0 /boot 1
And to update every value:
% foreach dir [dict keys $list] { dict incr list $dir }
% puts $list
/run 2 /run/shm 1 /boot 2

I would expect that doing this:
for {set idx 0} {$idx < [llength $list]} {incr idx} {
lset list $idx 1 [expr {[lindex $list $idx 1] + 1}]
}
would increment every numeric value in that list, which is what I believe you want to do. However, doing this:
foreach pair $list {
lset pair 1 [expr {[lindex $pair 1] + 1}]
}
will not work. Tcl conceptually copies the sublist-items out of the main list in foreach so that the changes to the pairs aren't reflected back. Also, conceptually Tcl also copies the value to hand to foreach in the first place. Of course, these copies are not actually real, as that would be very expensive! Instead Tcl uses shared references with copy-on-write-to-shared semantics, a system that works very well given that we can check the sharing status very cheaply (which is enabled by Tcl's threading model; values are never shared between threads, so sharing-state decisions can be lock-free and local).
A consequence of this is that Tcl explicitly rejects weird at-a-distance state changes of the kinds that cause weird bugs sometimes in languages with different semantics. If you're changing something, it'd better be a variable (as those are the main mutable things) and you'll have it right there in front of you when you do the change.

Related

XQuery get Random Text from a List

Suppose I have a list of 100 String Element and I want to get 50 of these random Text string to be returned Randomly.
I try to do this:
let $list := ("a","b",..."element number 100")
return xdmp:random(100)
This query return one string, I want to return back 50 strings that are distinct from each other.
Easiest to order by xdmp:random() and limit to the first 50:
(for $x in (1 to 100)
order by xdmp:random()
return $x
)[1 to 50]
xdmp:random() (as well as xdmp:elapsed-time()) return different values at each call. It would be rather impractical if it wouldn't. This opposed to for instance fn:current-dateTime() which gives the same value throughout one execution run.
Ghislain is making a good first attempt, but as also pointed out by BenW, even though xdmp:random() does return different results each time, it is not said they are unique throughout one execution run. Collisions on its 64-bit max scale are rare (though still possible), but on small scale like 10's or 100's it is likely to have some accidental repetition. It is wise to eliminate the texts from the list once chosen.
BenW beat me to posting an alternative to Ghislain, and it looks similar, but uses less lines. Posting it anyhow, in the hope someone finds it useful:
declare function local:getRandomTexts($list, $count) {
if ($count > 0 and exists($list)) then
let $random := xdmp:random(count($list) - 1) + 1
let $text := $list[$random]
return ($text, local:getRandomTexts($list[. != $text], $count - 1))
else ()
};
let $list :=
for $i in (1 to 26)
return fn:codepoints-to-string(64 + $i)
for $t in local:getRandomTexts($list, 100)
order by $t
return $t
HTH!
If you are saying the 50 strings must be distinct from one another, as in, no repeats allowed, then even if xdmp:random() does return different values when called repeatedly in the same query, getting 50 random positions in the same list is not sufficient, because there may be repeats. You need to get random positions from 50 different lists.
declare function local:pickSomeFromList($some as xs:integer, $listIn as xs:string*, $listOut as xs:string*) as xs:string* {
if($some = 0 or not($listIn)) then $listOut
else
let $random := xdmp:random(count($listIn) - 1) + 1
return local:pickSomeFromList(
$some - 1,
($listIn[fn:position() lt $random],$listIn[fn:position() gt $random]),
($listOut, $listIn[$random])
)
};
let $list := ("a","b","c","d","e","f","g","h","i","element number 10")
return local:pickSomeFromList(5, $list, ())
Assuming it returns a different result at every call (but I cannot tell from the documentation of xdmp:random whether it is the case), the following code returns 50 strings from the list picked at random (but not necessarily distinct):
let $list := ("a","b",..."element number 100")
for $i in 1 to 50
let $position = 1 + xdmp:random(99)
return $list[$position]
However, the exact behavior of xdmp:random, that is, whether it returns identical results across calls, depends on how the MarkLogic engine supports or treats nondeterministic behavior, which is outside of the scope of the XQuery specification. Strict adherence to the specification would actually return 50 times the same result with the above query.
XQuery 3.1 provides a random number generator with which you can control the seed. This allows you to generate as many numbers as you want by chaining calls, while only using interoperable behavior and staying within a fully deterministic realm.
Edit: here is a query (still assuming calls to xdmp:random are made each time) that should make sure that 50 distinct strings from the list are taken following grtjn's remark. It uses a group by clause and relies on a lazy evaluation for taking the first 50.
let $list := ("a","b",..."element number 100")
let $positions := (
for $i in 1 to 100000 (: can be adjusted to make sure we get 50 distinct :)
group by $position = 1 + xdmp:random(count($list) - 1)
return $position
)[position() le 50]
return $list[position() = $positions]
I think hunterhacker's proposal for computing the $positions is even better though.

Impute missing covariates at random in Stata

I am trying to randomly impute missing data for several covariates using Stata. I have never done this, and I am trying to use this code from a former employee:
local covarall calc_age educcat ipovcat_bl US_born alc_yn2 drug_yn lnlpcbsum tot_iod
local num = 0
foreach j of local covarall {
gen iflag_`j'=0
replace iflag_`j'=1 if `j'==.
local num = `num'+1000
forvalues i = 1/476 {
sort `j'
count if `j'==.
di r(N)
local num2 = `num'+`i'
set seed `num2'
replace `j' in `i'=`j'[1+int((400-r(N))*runiform())] if iflag_`j'[`i']==1
}
}
When I run this, Stata just gives me this over and over forever:
(0 real changes made)
0
0
What am I doing wrong?
The three messages seem interpretable as follows:
replace iflag_`j' = 1 if `j' == .
will lead to a message (0 real changes made) whenever that is so, meaning that the variable in question is never equal to system missing, the requirement for replacement.
count if `j' == .
will lead to the display of 0 in the same circumstance.
di r(N)
ditto. count shows a result by default and then the code insists that it be shown again. Strange style, but not a bug.
All that said the line
replace `j' in `i'=`j'[1+int((400-r(N))*runiform())] if iflag_`j'[`i'] == 1
is quite illegal. My best guess is that you have copied it incorrectly somehow and that it should have been
replace `j' =`j'[1+int((400-r(N))*runiform())] in `i' if iflag_`j'[`i'] == 1
but this too should produce the same message as the first if a value is not missing.
I add that it is utterly pointless to enter the innermost loop if there are no missing values in a variable: there is then nothing to impute.
Changing the seed every time a change is made is strange, but that is partly a matter of taste.

Looping through every value

I was trying to run a loop through a variable and was unsure how to code up my thoughts. So, I have variable called newid that goes as
newid
1
1
2
2
3
3
and so on.
foreach x in newid2 {
replace switchers = 1 if doc[_n] != doc[_n+1]
}
I want to modify this code so that this code will run for each two values (in this case run for 1 and 1, 2 and 2). What would be the best way to modify this? Please help me
Something like this can be done with levelsof:
clear
input id str1 doc
1 "A"
1 "B"
2 "A"
3 "C"
3 "A"
end
gen switcher1 = 0
levelsof id
foreach i in `r(levels)' {
quietly tab doc if id==`i'
replace switcher1 = 1 if r(r)>1 & id==`i'
}
However, you there are certainly more efficient ways to accomplish your goal. Here's one example that tags ids that switch doctors:
ssc install egenmore
bysort id: egen num_docs = nvals(doc)
generate switcher2 = cond(num_docs>1,1,0)
The underlying idea is the same. You count the number of distinct values of doc for each id. If that number exceeds one, the id is tagged as a switcher. The second version is arguably more efficient since it does not involve looping over each value of id.

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 when putting variable in table, only constants allowed?

Currently I am working on a Netlogo program where I need to use nodes and links for vehicle routing problem. (links are called streets in the program)
Here I have some practical problems of how to input variable linkspeed in a table with another node. Constants like 200 etc are fine. Online I found some examples where variables are used, but I do not know why I keep getting the following error:
Expected a constant.
(or why netlogo expects a constant)
Here is the relevant piece of code:
extensions [table]
streets-own [linkspeed linktoll]
nodes-own [netw]
;; In another piece of code linkspeed is assigned successfully to the links
to cheapcalc
;; start conditions set costs very high 300000
;; state 3 unsearched state 2 searching state 1 searched (for later purposes)
ask nodes [
set i 0 set j count nodes set netw table:make
while [i < j][
table:put netw (i) [3000000 3] set i (i + 1)]]
set i 0 let k 0
ask node 35 ;; here i use node 35 as an example.
;; node 35 is connected to node 34, 36, 20 and 50
[table:put netw (35) [0 1] ;; node need to search costs to travel to itself
;; putting constants is ok.
while [i < j]
[ask my-links
[ask both-ends
[if (who != 35) [set color blue
;; set temp ([linkspeed] of street 35 who) ;; here my real goal is to put this in stat of i. but i is easier than linkspeed.
table:put netw (who) [ i 2 ]
]
] ]
set i (i + 1)] ] ;; next node for later, no it is just repetition of the same.
end
I hope somebody knows what is going on...
The problem is most likely not putting a variable in a table, but putting a variable in a list (which you're then putting in a table).
Change the line below:
table:put netw (who) [ i 2 ]
to:
table:put netw (who) (list i 2)
This - (list i 2) - allows you to generate a list with variables in it, you can't do it the other way - [i 2].
Hope this helps.