So I'm doing billing for a doctors office, and we like to triple check that the flu shots we give are actually billed out. Right now, my AHK checks their entire chart for keys like the ICD-10 Code Z23, then checks for "Influenza". If it finds both of these, it adds the patients Medical Record Number to the age appropriate text file, either 65+ or Under 65. There are also paper sheets with 10 slots we record the MRNs by hand on, to double check. I want to create a script that will compare three files, a file of mixed MRNs (both 65+ and <65), and if the MRN is found in the mixed file, it should remove that line. (I've done a bunch of googling on the subject, but everyone is recommending using a library, something that would only slow down my program.) If the line isn't found, it should be left in the master file for my review. I cannot figure out how to remove lines, let alone a specific line. So far, to check the files against each other, I have this. In lieu of removing lines, I'm appending the unbilled numbers to a fourth text file, but ultimately, removing would be better.
FILENUM = 1
FILENU = 1
^L::
Loop
{
FileReadLine, MRN, E:\AHK & AHKS\flushot.txt, %LINENUM%
If ErrorLevel
{
break
}
Else
{
Loop
{
FileReadLine, MRNc, E:\AHK & AHKS\Billed65+Flushots.txt, LINENU
StringTrimRight, MRNc, 11
If (MRN != MRNc)
{
LINENU++
Match = 0
}
If (MRN == MRNc)
Match = 1
If ErrorLevel and Match = 0
{
FileAppend, MRN, E:\AHK & AHKS\Unbilledtester.txt
LINENU = 1
}
If (Match = 1)
GoTo Lab2
}
Lab2:
Loop
{
FileReadLine, MRNc, E:\AHK & AHKS\BilledAdultFlushots.txt, LINENU
StringTrimRight, MRNc, 11
If (MRN != MRNc)
{
LINENU++
Match = 0
}
If (MRN == MRNc)
Match = 1
If ErrorLevel and Match = 0
FileAppend, MRN, E:\AHK & AHKS\Unbilledtester.txt
If (Match = 1)
GoTo Lab2
}
LINENUM++
}
}
UPDATE!!!!!!!
Alright, so I grabbed your code, and I've been trying to run a reverse check. You check the associative arrays of the Adult and Senior flushot text files for the MRNS in flushot.txt, then if not found they're added to unbilled. Below are several of my attempts to reverse that, and check for the MRNs in Adult and Senior in flushot.txt, and if not found, to add it to a file called notrecorded.txt so I know what people got flushots and the MA who didn't record it (by Primary Care Provider).
FlushotsBilled()
{
Root_Dir := "E:\AHK & AHKS"
fName_Input := "flushot.txt"
fName_Backup := "flushot_old.txt"
fName_Output := "unbilled.txt"
fName_Billed_Adult := "billedAdultFlushots.txt"
fName_Billed_Senior := "billed65+Flushots.txt"
fName_Billed_NR := "notrecorded.txt"
fName_Mixed := "mixrecord.txt"
SetWorkingDir %Root_Dir%
FileDelete, %fName_Output%
AdultFlu := {}
SeniorFlu := {}
GivenFluA := {}
GivenFluB := {}
Mixed := {}
Loop, Read, %fName_Mixed%
Mixed[A_LoopReadLine] := True
Loop, Read, %fName_Billed_Adult%
{
;Loop, Parse, A_LoopReadLine, `t
; AdultFlu[A_LoopField] := True
AdultFlu[A_LoopReadLine] := True
If !Mixed[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n, %fName_Mixed%
}
Loop, Read, %fName_Billed_Senior%
{
;Loop, Parse, A_LoopReadLine, `t
; SeniorFlu[A_LoopField] := True
SeniorFlu[A_LoopReadLine] := True
If !Mixed[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n, %fName_Mixed%
}
Loop, Read, %fName_Input% ;check flushot.txt for
If !AdultFlu[SubStr(A_LoopReadLine, 1, InStr(A_LoopReadLine,"`t"))] && !SeniorFlu[SubStr(A_LoopReadLine, 1, InStr(A_LoopReadLine,"`t"))]
;If !AdultFlu[A_LoopReadLine] && !SeniorFlu[A_LoopReadLine] ;flushot is checking the associated arrays
FileAppend, %A_LoopReadLine%`n, %fName_Output% ;if not found, stick it into unbilled.
Loop, Read, %fName_Input%
GivenFluA[A_LoopReadLine] := True
/* Loop, Read, %fName_Billed_Senior%
If !Mixed[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n, %fName_Mixed%
Loop, Read, %fName_Billed_Adult%
If !Mixed[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n, %fName_Mixed%
Loop, Read, %fName_Mixed%
Mixed[A_LoopReadLine] := True
*/
Loop, Read, %fName_Mixed%
If !GivenFluA[SubStr(A_LoopReadLine, 1, InStr(A_LoopReadLine,"`t"))] ;a_Loopreadline is a line in mixed. it is looking through flushot.txt for that line, if it's there, it's givenflua's index
Loop, Read, %fName_Billed_NR%
If !GivenFluA[A_LoopReadLine]
FileAppend, %A_LoopReadLine%`n
/*
Loop, Read, %fName_Input% ;read flushot
If Mixed[A_LoopReadLine] Contains A_LoopReadLine ;check to see if each line in flushot is in mixed
GivenFluA[Mixed[A_LoopReadLine]] := True ;Mixed[A_LoopReadLine]
Loop, Read, %fName_Billed_NR%
If !GivenFluA[A_LoopReadLine]
FileAppend GivenFluA[%A_LoopReadLine%], %fName_Billed_NR%
*/
;if readline is in mixed, but not in flushot, check to see if it's in notrecorded, if not, append it
/*
Loop, Read, %fName_Mixed% ;open up the mixed record
IfNotInString, A_LoopReadLine, GivenFluA[A_LoopReadLine] ;see if inside each line, we can find the lines from flushot.txt, if not
;GivenFluB[GivenFluA[A_LoopReadLine]] := True ;if not we give the line from inside flushot to another associative array
GivenFluB[A_LoopReadLine] := True ;lets try this, put the value
Loop, Read, %fName_Mixed%
IfInString, A_LoopReadLine, GivenFluA[A_LoopReadLine]
GivenFluB[A_LoopReadLine]
Loop, Read, %fName_Billed_NR% ;open up not recorded
IfNotInString, A_LoopReadLine, GivenFluB[A_LoopReadLine] ;see if inside each line we can find that line from
FileAppend, %A_LoopReadLine%`n, %fName_Billed_NR%
*/
}
I think you were on the right track by creating an output file rather than deleting lines. Text files are usually treated as streams, not fixed-record-length data files. Deleting lines is error prone and not efficient; you'd have to compact the file somehow.
How big are your files? If there are only a few thousand patients, I would just load everything into associative arrays so we can use keyed lookups.
Program
^L:: medical_run()
medical_run()
{
root_dir := "E:\AHK & AHKS"
fname_input := "flushot.txt"
fname_backup := "flushot_old.txt"
fname_temp_output := "Unbilledtester.txt"
fname_billed_adult := "billedAdultFlushots.txt"
fname_billed_senior := "billed65+Flushots.txt"
SetWorkingDir %root_dir%
FileDelete %fname_output%
adults := {}
seniors := {}
unmatched := {}
loop READ, %fname_billed_adult%
adults[A_LoopReadLine] := true
loop READ, %fname_billed_senior%
seniors[A_LoopReadLine] := true
loop READ, %fname_input%
if !adults[A_LoopReadLine] && !seniors[A_LoopReadLine]
unmatched[A_LoopReadLine] := true
for code,dummy in unmatched
FileAppend %code%`n, %fname_output%
FileMove %fname_input%, %fname_backup%
FileMove %fname_temp_output%, %fname_input%
}
billedAdultFlushots.txt
101
102
103
104
billed65+Flushots.txt
205
206
207
208
flushot.txt BEFORE
301
101
103
302
205
301
207
103
303
301
flushot.txt AFTER
301
302
303
Related
I'm telling you, I had a previous version that worked with multiple conditions to execute an entry, I'll give you an example:
lo0 := deltaCloseMa0>=lo0CloseMa0Thresold and rsi>=lo0RsiThreshold
lo1 := deltaCloseMa0>=lo1CloseMa0Thresold and rsi>=lo1RsiThreshold and deltaCloseOpen>=lo1CloseOpenThreshold
lo2 := ....
lo3 := ....
lo4 := ....
....
lo25 := ....
The problem comes from the fact that the code has grown substantially and now it is impossible to continue adding conditions to infinity, because it is not scalable.
A possible solution has occurred to me, which deals with evaluating the conditions one by one and then if all the ones that are enabled are met, executing the input.
I put the code fragment that I have written but I am not able to make it compile the script.
var entryOrderConditions = false, constructorMa0Cond = false, constructorRsiCond = false
var orderBuilder = array.new_bool(0)
if orderType // If orderType is enable, then evaluate the conditions for long .. if disable, for short
// LONG
if barstate.isconfirmed and strategy.position_size == 0
// ## Setup constructor
if enableCloseToMa0
if deltaCloseMa0 >= ma0CloseThreshold
array.push(orderBuilder, true)
else
array.push(orderBuilder, false)
if enableRsi
if rsi >= rsiThreshold
array.push(orderBuilder, true)
else
array.push(orderBuilder, false)
for signal in orderBuilder
if signal
entryOrderConditions := true
else
entryOrderConditions := false
break
The question is that the same conditions are not always enabled, nor is the tolerance threshold always the same, so they are dynamized above with inputs (bool, int, float...)
So I have to look for a flexible system that allows me to evaluate as many conditions as I want and is scalable enough not to have 100 input types defined in the code.
Thank you very much for your help.
Dirty solution:
I have tried with various types of arrays, with various ways of traversing them, with different checks, and I have even looked for a "switch" to filter... But nothing worked.
In the end I have found a solution that works, it is not the most elegant and cleanest code in the world but, it is functional, and I think I can't improve it.
I'm open to suggestions if anyone reads this and thinks there's a better way to do it.
A little philosophy of own creation,
Sometimes, the best solution is not the most pleasant
Even so, it is still more scalable than at the beginning.
float constructorMa0Cond = na
float constructorRsiCond = na
if orderType
// LONG
if barstate.isconfirmed and strategy.position_size == 0
// ## Setup constructor
if enableCloseToMa0
if deltaCloseMa0 >= ma0CloseThreshold
constructorMa0Cond := 1
else
constructorMa0Cond := 0
if enableRsi
if rsi >= rsiThreshold
constructorRsiCond := 1
else
constructorRsiCond := 0
for i = 0 to 1
if i == 0
if not na(constructorMa0Cond)
if constructorMa0Cond
entryOrderConditions := true
else
entryOrderConditions := false
break
else if i == 1
if not na(constructorRsiCond)
if constructorRsiCond
entryOrderConditions := true
else
entryOrderConditions := false
break
This question was answered at the bottom of this post.
I have looked at 6 different web pages from the AHK forums asking this question, and another one on SO here:
String to Number using autohotkey
...but none of them are working for me. I'm just trying to subtract a number from a string that has been grabbed from the StringSplit function. This is my code:
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices32%.
; Assign Stop Loss variable
SLPrice := %prices32% -= 0.10
MsgBox, Your SLPrice is %SLPrice%.
I receive the error "The following variable name contains an illegal character" on the line "SLPrice := %prices32% -= 0.10", so then I try:
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices32%.
; Assign Stop Loss variable
SLPrice = %prices32% - 0.10
MsgBox, Your SLPrice is %SLPrice%.
...to which I get the output:
Your SLPrice is 7.450 - 0.10
So it just displays the formula as a text string, it doesn't actually do the calculation.
Thoughts? Thanks!
UPDATE
To continue working out this solution, here is my full code up to the part I'm having an issue with, along with screenshots of what's happening:
; Get the latest window text to parse values from it
WinGetText, Window1Text, ahk_class WindowsForms10.Window.8.app.0.f96fc5_r9_ad1
MsgBox, The text is: %Window1Text% ; Displays the window get text values
Sleep, 5
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices32%.
; Assign Stop Loss variable
SLPrice := prices32 - 0.10
MsgBox, Your SLPrice is %SLPrice%.
ANSWER
Thanks to the contributor below, we discovered that there was a "." from the first MsgBox messing up the SLPrice variable, so we updated the SLPrice variable to read:
SLPrice := SubStr(Trim(prices32), 1, 5) - 0.10 ; to pull the left 5 characters
Thanks!
You are on the right track. But, per my comment, note := implies expressions including variable expressions (hence no surrounding %'s):
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices32%.
; Assign Stop Loss variable
; Note, the 32 line also includes non printing characters
; so must be trimmed and then we take the left 5 characters
SLPrice := SubStr(Trim(prices32), 1, 5) - 0.10
MsgBox, Your SLPrice is %SLPrice%.
Should do it . . .
And note, using something := %myvariable% implies reading the contents of a variable named myvariable and using those contents as the variable name. So if myvariable is "test", you are really saying something := test (where something ends up being equal to the contents of the test variable).
Hth,
EDIT per below, here is a working example (BUT PER LATTER COMMENT, SEE BELOW, TOO):
Window1Text =
(
25
26
27
28
)
; Assign entry price variable.
StringSplit, prices, Window1Text, `n
MsgBox, Your entry price is %prices2%. ; using only 2nd line (26)
; Assign Stop Loss variable
SLPrice := prices2 - 0.10 ; again, changed to 2nd line
MsgBox, Your SLPrice is %SLPrice%. ; 25.900000
clipboard := SLPrice
HTH,
FURTHER EDIT: Because this is really cool and illustrates the several concepts as to how they relate to pseudo array variable expressions:
Window1Text =
(
25
26
27
28
)
; Assign entry price variable.
StringSplit, prices, Window1Text, `n ; (prices0 is the number of entries)
InputBox, num,, % "Pick an array number from 1 to " prices0 ; get the array number
; note the variable expression includes the num variable contents
MsgBox, % "Your entry price is " Trim(prices%num%) "." ; depends on array number
; Assign Stop Loss variable
SLPrice := Trim(prices%num%) - 0.10 ; uses the array number value
MsgBox, Your SLPrice is %SLPrice%. ; so depends on the array number
clipboard := SLPrice
Right?
But note, these testers work easily. The real life example from the OP is copied text and the line 32 contains non-printing characters dealt with by Trim(x) and taking only the first few characters from Left with SubStr(x,1,5).
I have a problem with Pascal, especially Lazarus.
First of all, I created two random arrays of integer:
procedure TForm1.b_arraycreate1Click(Sender: TObject);
begin
randomize;
for i := 1 to 5 do
arr1[i] := random(10);
end;
And
procedure TForm1.b_arraycreate2Click(Sender: TObject);
begin
randomize;
for j := 1 to 5 do
arr2[j] := random(10);
end;
I know, I could put it in one procedure as well but doesn't matter now.
I want to compare these two. I wrote the following code:
procedure TForm1.b_comparisonClick(Sender: TObject);
var v:boolean;
begin
for i := 1 to 5 do begin
for j := 1 to 5 do begin
if arr1[i] = arr2[j]
then
begin
v:=true;
end
else
begin
v:=false;
end;
end;
end;
if v = true
then
begin
ShowMessage('Yes, there is a similarity! You can find number ' +IntToStr(arr1[i])+ ' in array 1, position ' +IntToStr(i)+ ' and also in array 2, position ' +IntToStr(j)+ '.');
end
else
begin
ShowMessage('No similarities... Generate new ones!');
end
end;
In my own words: I want to push a button and then there should be a message window with the information if there is one number (for example 7) which exists in array 1 and array 2. If yes, it should also write the position (index) of this number.
Unfortunately, this program doesn't work and I don't know why. It always shows "No similarities" (and don't worry about the creation of the arrays. I also have a label where I can test the content of the arrays every time).
Is there a (silly) mistake in my code here?
As explained already by MartynA in his comment, your algorithm is wrong. Your words are:
if there is one number which exists in array 1 and array 2
To see if it is so, you must scan all array1 and, for each number, see if it exists somewhere in array2.
So yes, you need two cycles, one nested in the other. As soon as you find a correspondence, you must stop. Or, if you want more results (find multiple duplicates), show a message instead of stopping - and go ahead. Third possibility (more complicated): when found, store the couple of indexes (without overwrite old results...) and go ahead. I will only show the first option:
procedure TForm1.b_comparisonClick(Sender: TObject);
var
i,j: integer;
v: boolean;
begin
v := false;
for i := 1 to 5 do begin
for j := 1 to 5 do begin
if arr1[i] = arr2[j] then begin
v := true;
break
end
end // inner, j
end; // outer, i
if v = true
then ShowMessage(.....)
else ShowMessage('No similarities...');
end; // proc comparison
I tried to respect your code a bit, there are a few possible "shortcuts"; for example, if v is a boolean variable, it is better to write if v then instead of if v=true then, and some others, like
v := arr1[i]=arr[j];
...or... the outer loop does not need begin+end.
******* BEWARE (see comment below about break)
To stop/exit from two nested cycle is not so simple... perhaps a goto... the code above works, but the break does little work.
******* second update, as described by comment below. IT DOES NOT WORK, because if the break does not exit BOTH loops, the outer index gets modified. The correct cycle using TWO breaks is as follows:
for i := 1 to 5 do begin
for j := 1 to 5 do begin
if arr1[i] = arr2[j] then begin
v := true;
break
end
end; // inner, j
if v then break
end; // outer, i
Sorry for the mistakes... :-)
I would prefer a GOTO to exit both loops: it is faster, single instruction, and more clear ("goto found" instead of a generic break). But GOTOs are not very popular... so I've been afraid!
I work at a doctors office doing the billing, and I've been writing code to streamline the billing process. I must include all diagnoses in the billing software, so I copy the whole chart, and parse it into an array by newlines looking for the prefix ICD-10, and if two codes are on the same line, it separates those too (via a comma). Before that, it removes a part (if it exists) of the chart that includes canceled procedures so the canceled procedures aren't charged. Sometimes, multiple of the same diagnosis is included in the chart for the purpose of ordering a procedure (it's automatic), and I need to only add each diagnosis to the array once.
[...]
SendInput, ^a
ClipBoard :=
SendInput, ^c
ClipWait
BlockInput, MouseMoveOff
lString := ClipBoard
Sleep, 200
IfInString, lString, Canceled Orders
{
lStringLeft := SubStr(lString, 1, InStr(lString, "Canceled Orders")-1)
Sleep, 20
lStringRight := SubStr(lString, InStr(lString, "Allergies as of"))
Sleep, 20
lString :=
Sleep, 20
lString := lStringLeft
Sleep, 20
lString .= lStringRight
Sleep, 20
}
DxArr := []
numDx := 0
Loop, Parse, lString, `n
If InStr(A_LoopField, "ICD-10")
Loop, Parse, A_LoopField, `,
DxArr[++numDx] := Trim(SubStr(A_LoopField, InStr(A_LoopField, ":") + 1), " `t`n`r")
[...]
The ideal output for
Essential Hypertension
ICD-9-CM: 401.0
ICD-10-CM: I10
Essential Hypertension with Chronic Kidney Disease, Stage 3
ICD-9-CM: 585.3, 401.0
ICD-10-CM: N18.3, I10
is
I10 N18.3
I've been at this with several different solutions I found on the internet, but so far, they've just made a mess rather than actually fixing any problem. Any help is appreciated!
Use a hash to remove duplicates. Use your code as the key and a dummy for the value. I use "true" below as the dummy value. Duplicate records have the same key so they replace the previous key-value pair.
Output the hash's keys after you are done parsing the input.
DxHash := {}
Loop, Parse, lString, `n
If InStr(A_LoopField, "ICD-10")
Loop, Parse, A_LoopField, `,
DxHash[Trim(SubStr(A_LoopField, InStr(A_LoopField, ":") + 1), " `t`n`r")] := true
for diagnosis,dummy in DxHash
send %diagnosis%{SPACE}
I'm trying to program an addon for WoW (in lua). It's a chat filter based on specific words. I can't figure out how to get the array of these words to be case insensitive, so that any upper/lower case combination of the word matches the array. Any ideas would be greatly appreciated. Thanks!
local function wordFilter(self,event,msg)
local keyWords = {"word","test","blah","here","code","woot"}
local matchCount = 0;
for _, word in ipairs(keyWords) do
if (string.match(msg, word,)) then
matchCount = matchCount + 1;
end
end
if (matchCount > 1) then
return false;
else
return true;
end
end
Use if msg:lower():find ( word:lower() , 1 , true ) then
==> it lower cases both of the arguments to string.find: hence case insensitivity.
Also I used string.find because you probably want the 'plain' option, which doesn't exist for string.match.
Also you can easily return on the first word found:
for _ , keyword in ipairs(keywords) do
if msg:lower():find( keyword:lower(), 1, true ) then return true end
end
return false
Define keyWords outside of function. Otherwise you're recreating
table every time just to thorw it away moments latter, wasting time
on both creation and GC.
Convert keyWords to patter that match
both upper and lower case letters.
You don't need captured data
from string, so use string.find for speed.
According to your
logic, if you've got more than one match you signal 'false'. Since
you need only 1 match, you don't need to count them. Just return
false as soon as you hit it. Saves you time for checking all
remaining words too. If later you decide you want more than one
match, you still better check it inside loop and return as soon as
you've reached desired count.
Don't use ipairs. It's slower than simple for loop from 1 to array length and ipairs is deprecated in Lua 5.2 anyway.
local keyWords = {"word","test","blah","here","code","woot"}
local caselessKeyWordsPatterns = {}
local function letter_to_pattern(c)
return string.format("[%s%s]", string.lower(c), string.upper(c))
end
for idx = 1, #keyWords do
caselessKeyWordsPatterns[idx] = string.gsub(keyWords[idx], "%a", letter_to_pattern)
end
local function wordFilter(self, event, msg)
for idx = 1, #caselessKeyWordsPatterns do
if (string.find(msg, caselessKeyWordsPatterns[idx])) then
return false
end
end
return true
end
local _
print(wordFilter(_, _, 'omg wtf lol'))
print(wordFilter(_, _, 'word man'))
print(wordFilter(_, _, 'this is a tEsT'))
print(wordFilter(_, _, 'BlAh bLAH Blah'))
print(wordFilter(_, _, 'let me go'))
Result is:
true
false
false
false
true
You can also arrange this with metatables, in an entirely transparent way:
mt={__newindex=function(t,k,v)
if type(k)~='string' then
error'this table only takes string keys'
else
rawset(t,k:lower(),v)
end
end,
__index=function(t,k)
if type(k)~='string' then
error'this table only takes string keys'
else
return rawget(t,k:lower())
end
end}
keywords=setmetatable({},mt)
for idx,word in pairs{"word","test","blah","here","code","woot"} do
keywords[word]=idx;
end
for idx,word in ipairs{"Foo","HERE",'WooT'} do
local res=keywords[word]
if res then
print(("%s at index %d in given array matches index %d in keywords"):format(word,idx,keywords[word] or 0))
else
print(word.." not found in keywords")
end
end
This way the table can be indexed in whatever case. If you add new words to it, it will automatically lower-case them too. You can even adjust it to allow matching with patterns or whatever you'd like.