TheSwamp
Code Red => AutoLISP (Vanilla / Visual) => Topic started by: ziko30 on March 18, 2010, 02:28:50 PM
-
I want to do something like this:
Line1TextFile1 x Line1TextFile2 = Line1TextFile3 (where x is not multiplication but a formula) and then repeat with line 2 etc. I would like pointers and not complete solutions for now. Question is am I on the right track doing it this way and if not where did I screw up?
I get this on the command line ( bad argument type: stringp 2.49887 :error#2*Cancel* ) which has to do with data types But I don't know how to fix it.
(defun c:Conv (/ M_Open A_Open M_Read A_Read Converted M_Line1
A_Line1 Convert)
(setq M_Open (getfiled "Select M_File" "" "txt" 16)
M_Read (open M_Open "r")
A_Open (getfiled "Select A_File" "" "txt" 16)
A_Read (open M_Open "r")
Converted (open (strcat M_Open "Converted.txt") "w")
)
(while (setq M_Line1 (atof (read-line M_Read)))
(setq A_Line1 (atof (read-line A_Read)))
(setq Convert (atof (* 100 (/ A_Line1 (- 100 M_Line1)))))
(write-line Convert Converted)
)
(close Converted)
(close M_Open)
(close A_Open)
(princ)
)
-
Ziko,
You want pointers, so I shan't rewrite the code, but here are a few things to consider:
- Allow for the user not selecting a file to use, it can be as simple as an IF statement.
- (atof nil) will return an error, so check that the read-line returns true before trying to convert it to a float
- (atof (* 100 (/ A_Line1 (- 100 M_Line1)))) I'm not sure why you are trying to convert your calculations (float type) from a string type to a float type using atof, better the other way around. Look into the rtos function.
That should get you more on the right tracks,
Lee
-
You might also want to read in each file to a list before you process them. Check to see if they are the same length. I would only have one file open at a time, as I think that would speed up the process also.
-
Yes, that way you can use a subroutine to open/read/close the file & return the list of data.
-
Thanks all for the advice.
Taking this little by little working with one file first; I am able to read a file to a list and write the list to another file. Except it is an infinity loop and it keeps writing to the file. Why is the while loop not terminating? The errors/misconceptions might make you cringe... but I would like for them to be pointed out. I have also added two sample txt files I am using for testing. Eventually I will check length and reverse lists before processing.
(Defun C:Conv2 (/ M_Open M_Read M_Line1 M_line2 Conv)
(if (setq M_Open (getfiled "Select M_File" "" "txt" 16))
(setq M_Read (open M_Open "r"))
(prompt "\nUnable to open file.")
)
(while (Setq M_Line1 (read-line M_Read))
(Setq Listfile1 (cons M_Line1 Listfile))
)
(Close M_Read)
(setq conv (open "C:\\Projects\\Converted.txt" "w"))
(while (setq M_Line2 (car Listfile1))
(write-line M_Line2 conv))
(close conv)
)
-
you're missing a --
(setq listFile1 (cdr Listfile1))
can you figure out where it should go?
-
you're missing a --
(setq listFile1 (cdr Listfile1))
can you figure out where it should go?
Now, that's my style of help :wink:
-
Teach a man to fish and all that... :roll:
-
Rolling eyes? wtf, I did something wrong?
-
Rolling eyes? wtf, I did something wrong?
Nope, not as far as I'm concerned.
I like to see people encouraged to help themselves.
.... thought you knew that .. sorry for the confusion. :|
-
Rolling eyes? wtf, I did something wrong?
No you are ok...Its the help I asked for and at this point I will eat humble pie and say stick it in there for me if you would. I think it should go in the last while block so that my list is reduced by an element with every iteration. I have tried putting it where I think it should but no success.
-
Nope, not as far as I'm concerned.
I like to see people encouraged to help themselves.
.... thought you knew that .. sorry for the confusion. :|
I understood your comments, and generally pretend to know your bent on teaching vs hand outs, I just didn't get the rolling eyes.
-
No you are ok...Its the help I asked for and at this point I will eat humble pie and say stick it in there for me if you would. I think it should go in the last while block so that my list is reduced by an element with every iteration. I have tried putting it where I think it should but no success.
... an infinity loop -- keeps writing to the file. Why is the while loop not terminating? ...
Based solely on that ^ comment I assume the problem is in the "write" loop:
(while (setq M_Line2 (car Listfile1))
(write-line M_Line2 conv)
)
Looking at it, the exit condition is "when (setq M_Line2 (car Listfile1)) returns nil". Examining said loop, no code modifies Listfile1, therefore (setq M_Line2 (car Listfile1)) will always be non nil, ergo infinite loop. Clear as mud? PS: It's the only part of the proggy I looked at, other errors may abound.
-
Teach a man to fish and all that... :roll:
Lee FYI,
Eventually, I want to skip the first 6 lines of TextFile1 and TextFile2 but copy the 6 lines to TextFile3 (The first 6 lines should have same values for all 3 files and not to be processed). All 3 files will have the same total number of lines. I will also want the routine to read file 1 and 2 till it encounters a string or blank then stop. Last time I posted something I simply used your code, but Iam hoping I can work my way through this one with swampers. Failing that I will ask for a complete solution. :oops:
-
No you are ok...Its the help I asked for and at this point I will eat humble pie and say stick it in there for me if you would. I think it should go in the last while block so that my list is reduced by an element with every iteration. I have tried putting it where I think it should but no success.
... an infinity loop -- keeps writing to the file. Why is the while loop not terminating? ...
Based solely on that ^ comment I assume the problem is in the "write" loop:
(while (setq M_Line2 (car Listfile1))
(write-line M_Line2 conv)
)
Looking at it, the exit condition is "when (setq M_Line2 (car Listfile1)) returns nil". Examining said loop, no code modifies Listfile1, therefore (setq M_Line2 (car Listfile1)) will always be non nil, ergo infinite loop. Clear as mud? PS: It's the only part of the proggy I looked at, other errors may abound.
Ok thanks... I am moving along. Here is how I have it and it worked.
(while (setq M_Line2 (car Listfile))
(write-line M_Line2 conv) (setq listFile (cdr Listfile))
)
-
Ok thanks... I am moving along. Here is how I have it and it worked.
awesome, lucky guess, glad you're on your way
-
How do I apply something like this: (setq Conv3 (* 100.0 (/ B (- 100.0 A)))) where A and B are lists. Both lists have real numbers in them so I think I don't have to do any data type conversion. I tried this:
(if (/= (car ListfileM) null)
(setq Conv3 (* 100 (/ (car listfileA) (- 100 (car ListfileM)))))
(prompt "nUnable to convert")
)
=============AND THIS=========
(setq A (car ListfileM))
(setq B (car ListfileA))
(setq Conv3 (* 100.0 (/ B (- 100.0 A))))
.
Just to see if I can get it to work with the first elements...Nothing
-
Rolling eyes? wtf, I did something wrong?
No not at all - I'm in agreement with Kerry :-)
-
Ziko,
Bear in mind that you are reading strings from the files, these will need to be converted to Reals before proceeding with your calculations, then back to strings to write to the output.
Lee
-
One tip. If you are processing a list ( or more than one list ), and you want a list returned, then look at ' mapcar '.
-
I got this to work
(setq Output (mapcar '(lambda (x y)
(* x y)
)
'(1 2 3 4) '(6 6 6 6)
)
)
But not this
(setq Output (mapcar '(lambda (x y)
(* 100 (/ x (- 100 y)))
)
'(1 2 3 4) '(6 6 6 6)
)
)
I get (0 0 0 0). It's probably not a simple plug in. Any more pointers?
-
Beware of the difference between Integers and Reals
(setq Output (mapcar '(lambda (x y)
(* 100. (/ x (- 100. y)))
)
'(1 2 3 4) '(6 6 6 6)
)
)
-
Thanks all, this works
(setq Output (mapcar '(lambda (x y) (* (/ x (- 100.0 y)) 100.0)) ListA ListM))
-
A while back I got help putting this together. I now have time to improve it. It does what I needed it to do, which is (A x B = C) where A, B, C are text files and 'x' is a formula. It works as
follows.
1-copy the first 6 lines of A or B to new file, C (indentical in both A and B)
2-Start applying formula at the 7th lines of both A and B and write to C starting at 7th line.
3-strip out of C anything that is negative or zero (Formula applied to text in files: need help on how to stop calculation where it says "Make file" in A and B and copy everything to C after that
line)
I would like to see other ways of getting this done if anyone has the time to have a go at it. Alan suggested I use a subroutine to open/read/close a file and return the data; I am not sure how to
apply this when I need to read from two separate files... will be glad of you (Alan) could explain little more. I have attached the complete text files for anyone who would have a go at it. You
could simply provide an alternative routine to do this without going through my routine. It gets the job done for me but I am sure there better ways of doing this.
Would like to incoporate error trap but I am not sure how to set it up. Whether when user selects wrong file type, or exist before selecting file etc...Thanks for you continued help.
(defun C:Conv (/) ;Localize later
;===============Tell User To Select Moisture First====================
(alert
"1- Select Moisture Grid File First \n2- Select Ash Grid File Second "
)
;======Read first file to list======================================
(if (setq M_Open
(getfiled "Select Moisture_Grid" "" "txt" 16)
)
(setq M_Read (open M_Open "r")) ; read text file
(prompt "\n...Unable to open file."); tell use if unable to get textfile
)
(while (Setq M_Line1 (read-line M_Read)) ; read textfile to list
(Setq M_Listfile (cons M_Line1 M_Listfile))
; create list of data read
)
(setq M_List
(reverse
(mapcar 'atof M_Listfile)
)
)
;Convert to real for calculations
(repeat 6 (setq M_list (cdr M_List))) ; remove first 6 lines
;===========Read Second file to list==============================
(if (setq M_OpenA (getfiled "Select Ash_Grid" "" "txt" 16))
(setq M_ReadA (open M_OpenA "r"))
(prompt "\n...Unable to open file.")
)
(while
(Setq M_Line1A
(read-line M_ReadA)
)
(Setq M_ListfileA
(cons M_Line1A M_ListfileA)
)
)
(setq M_ListA
(reverse
(mapcar 'atof M_ListfileA);Convert to real for calculations
)
)
(repeat 6 (setq M_listA (cdr M_ListA))) ; remove first 6 lines
;===Create Output file and Add Coordinates and grid sizes======
(setq M_Output (open "C:\\Projects\\Output.txt" "w"))
(setq M_List_out (reverse M_Listfile))
(setq m 0)
(while (< m 6); use repeat
(setq M_line (nth m M_List_out))
(write-line M_line M_Output)
(setq m (1+ m))
)
(close M_Output)
;==================== Process Files==========================================
(setq M_Outfile
(mapcar '(lambda (x y) (* (/ x (- 100.0000 y)) 100.0000))
M_List
M_ListA
)
)
(setq M_Outfile1
(vl-remove-if 'Zerop (vl-remove-if 'Minusp M_Outfile))
)
(setq M_Output (open "C:\\Projects\\Output.txt" "a"))
(setq M_Outfile1 (mapcar 'rtos M_Outfile1))
; Convert to string for output
(while (setq M_O_Line (car M_Outfile1))
(write-line M_O_Line M_Output)
(setq M_Outfile1 (cdr M_Outfile1))
)
(close M_Output)
(princ "\nFinished...File Converted")
(princ)
)
-
(setq Convert (atof (* 100 (/ A_Line1 (- 100 M_Line1)))))
(write-line Convert Converted)
Convert is not a string, so use PRINC instead...
e.fernal
-
(setq Convert (atof (* 100 (/ A_Line1 (- 100 M_Line1)))))
(write-line Convert Converted)
Convert is not a string, so use PRINC instead...
e.fernal
Thanks e.fernal... I did not want to start a new thread, although I see that might cause some misunderstanding... but what you have noted has already been taken care. I have something that works in my last post. I just want to see different versions of the same process. You can use the attached text files if you have the time to have a go.
-
My revision but no subroutine. See how the cond is used to end the routine if any failures occur.
Still needs an error handler though.
(defun C:Conv (/ M M_LINE M_LINE1 M_LINE1A M_LIST M_LISTA
M_LISTFILE M_LISTFILEA M_LIST_OUT M_OPEN M_OPENA M_OUTFILE M_OUTFILE1
M_OUTPUT M_O_LINE M_READ M_READA X Y
)
;;===============Tell User To Select Moisture First====================
(alert "1- Select Moisture Grid File First \n2- Select Ash Grid File Second ")
(cond
;;======Read first file to list======================================
((not
(and
(setq M_Open (getfiled "Select Moisture_Grid" "" "txt" 16))
(setq M_Read (open M_Open "r")) ; read text file
(progn
(while (setq M_Line1 (read-line M_Read)) ; read textfile to list
(setq M_Listfile (cons M_Line1 M_Listfile)) ; create list of data read
)
(setq M_List (reverse (mapcar 'atof M_Listfile))) ; Convert to real for calculations
(repeat 6 (setq M_list (cdr M_List))) ; remove first 6 lines
)
)
)
(prompt "\n...Unable to open file.") ; tell use if unable to get textfile
)
;;===========Read Second file to list==============================
((not
(and
(setq M_OpenA (getfiled "Select Ash_Grid" "" "txt" 16))
(setq M_ReadA (open M_OpenA "r"))
(progn
(while (setq M_Line1A (read-line M_ReadA))
(setq M_ListfileA (cons M_Line1A M_ListfileA))
)
(setq M_ListA (reverse (mapcar 'atof M_ListfileA))) ;Convert to real for calculations
(repeat 6 (setq M_listA (cdr M_ListA))) ; remove first 6 lines
)
)
)
(prompt "\n...Unable to open file.")
)
;;===Create Output file and Add Coordinates and grid sizes======
((not
(and
(setq M_Output (open "C:\\Projects\\Output.txt" "w"))
;;(setq M_Output (open "Output.txt" "w"))
(setq M_List_out (reverse M_Listfile))
(setq m 0)
(progn
(while (< m 6) ; use repeat
(setq M_line (nth m M_List_out))
(write-line M_line M_Output)
(setq m (1+ m))
)
;;==================== Process Files==========================================
(setq M_Outfile
(mapcar '(lambda (x y) (* (/ x (- 100.0 y)) 100.0)) M_List M_ListA)
)
(setq M_Outfile1 (vl-remove-if 'zerop (vl-remove-if 'minusp M_Outfile)))
(setq M_Outfile1 (mapcar 'rtos M_Outfile1)) ; Convert to string for output
(while (setq M_O_Line (car M_Outfile1))
(write-line M_O_Line M_Output)
(setq M_Outfile1 (cdr M_Outfile1))
)
(null (close M_Output))
(princ "\nFinished...File Converted")
)
)
)
(prompt "\n...Unable to open output file.")
)
)
(princ)
)
-
Thanks for the time CAB... I am going to look at it with my autolisp help files open to see how COND, AND, NOT, NULL are being applied in your revision. I also can see how your formatting helps in following the routine. Up to now, I 've felt until it works, no need to make it pretty, but I think that is not he right approach.
-
What happens to this data from file 'A'?
Make File
Method: Inverse Distance
Strata: CO_EG E_MOIS_AR
Modeling Method: Inverse Distance
Search Radius: 10000.0 Max Samples: 20 Min Quadrant: 0 Max Quadrant: 20
Pinch Out: OFF
Conformance: OFF
Northing Easting Value Point Source
416306.400 1847269.240 Point KE-41-2005 Not Used
413646.530 1845818.530 Point KE-40-2005 Not Used
414752.500 1846111.160 Point KE-39-2005 Not Used
415615.370 1845844.340 Point KE-38-2005 Not Used
-
Another version:
(defun C:Conv (/ M M_LINE M_LINE1 M_LINE1A M_LISTA M_LISTB
M_LISTFILE M_LISTFILEA M_LIST_OUT M_OPEN M_OPENA M_OUTFILE M_OUTFILE1
M_OUTPUT M_O_LINE M_READ M_READA 6_lines
)
;; return a list of text string for each line in the file
(defun ReadFile (fname / fn ln AllLines)
(if (setq fn (findfile fname)) ; only if the file is found
(progn ; fn contains the full path
(setq fn (open fn "r")) ; open file for reading
(while (setq ln (read-line fn)) ; read all the lines in the file
(setq AllLines (cons ln AllLines))
)
(close fn) ; close the open file handle
)
)
(reverse AllLines)
)
;;===============Tell User To Select Moisture First====================
(alert "1- Select Moisture Grid File First \n2- Select Ash Grid File Second ")
(cond
;;======Read first file to list======================================
((not
(and
(setq M_Open (getfiled "Select Moisture_Grid" "" "txt" 16))
(setq M_ListA (ReadFile M_Open)) ; read textfile to list
(repeat 6 (setq 6_lines (cons (car M_ListA) 6_lines)
M_listA (cdr M_ListA))) ; remove first 6 lines
(setq M_ListA (mapcar (function (lambda(x) (distof x 2))) M_ListA)) ; Convert to real for calculations
(setq M_ListA (vl-remove-if 'listp M_ListA))
)
)
(prompt "\n...Unable to open file.") ; tell use if unable to get textfile
)
;;===========Read Second file to list==============================
((not
(and
(setq M_Open (getfiled "Select Ash_Grid" "" "txt" 16))
(setq M_ListB (ReadFile M_Open)) ; read textfile to list
(repeat 6 (setq M_listB (cdr M_ListB))) ; remove first 6 lines
(setq M_ListB (mapcar (function (lambda(x) (distof x 2))) M_ListB)) ; Convert to real for calculations
(setq M_ListB (vl-remove-if 'listp M_ListB))
)
)
(prompt "\n...Unable to open file.")
)
;;===Create Output file and Add Coordinates and grid sizes======
((not
(and
(setq M_Output (open "C:\\Projects\\Output.txt" "w"))
(progn
(mapcar (function (lambda (x) (write-line x M_Output))) (reverse 6_lines))
(setq M_Outfile
(mapcar
(function
(lambda (x y) (* (/ x (- 100.0 y)) 100.0))) M_ListA M_ListB)
)
(mapcar (function (lambda(x) (write-line (rtos x 2) M_Output))) M_Outfile)
(null (close M_Output))
(princ "\nFinished...File Converted")
)
)
)
(prompt "\n...Unable to open output file.")
)
)
(princ)
)
-
What happens to this data from file 'A'?
Make File
Method: Inverse Distance
Strata: CO_EG E_MOIS_AR
Modeling Method: Inverse Distance
Search Radius: 10000.0 Max Samples: 20 Min Quadrant: 0 Max Quadrant: 20
Pinch Out: OFF
Conformance: OFF
Northing Easting Value Point Source
416306.400 1847269.240 Point KE-41-2005 Not Used
413646.530 1845818.530 Point KE-40-2005 Not Used
414752.500 1846111.160 Point KE-39-2005 Not Used
415615.370 1845844.340 Point KE-38-2005 Not Used
If I can, I would like to copy it to C (output file), but if I lose it that is not a problem.
-
My revision but no subroutine. See how the cond is used to end the routine if any failures occur.
Still needs an error handler though.
THIS IS THE WAY I UNDERSTAND THE CODE
=================================
"COND" has 3 tests & 3 expressions to execute:
Test
((not
(and .....
.....
)
)
On failure execute expression
(Prompt "\n... Unable to open file)
"AND"
Should return T when all arguments are true
===========================
"NOT"
make sure I don't have NIL here? But am I checking whether a file is selected or am I checking if any of the variables within that expression have something in them ? I am finding the "NOT" function confusing.
Working backwards....
AND returns T, NOT returns NIL, COND executes PROMPT ??? I am thinking when it works they all return T
-
Another version:
I will take the weekend to study this one... appreciate your time.
-
Working backwards....
AND returns T, NOT returns NIL, COND executes PROMPT ??? I am thinking when it works they all return T
Almost correct:
AND returns T, NOT returns NIL, therefore COND does not execute the PROMPT, just goes on to the next COND test. 8-)
-
If I can, I would like to copy it to C (output file), but if I lose it that is not a problem.
In the next version I'll add this back to the output file.
-
You need to test this routine as I don't have time this morning.
Pseudo Code
** Do this twice
>>>>>>>>>>>
Get file choice from user
Read the file
Remove first 6 lines (save them from file A)
Split remaining list into two list, data and non-data (file A only)
Quit if there is a problem
<<<<<<<<<<<<<
Then test data list length & option to quit
Test file length & option to quit
Open output file, quit if there is a problem
Calculate combined data
Write first 6 lines
Write new data
Write non-data
Close and all done
(defun C:Conv (/ 6_lines flag flength1 flength2 list_suffix m_lista m_listb m_open
m_outfile m_output raw_data tmp1 tmp2
)
;; return a list of text string for each line in the file
(defun ReadFile (fname / fn ln AllLines)
(if (setq fn (findfile fname)) ; only if the file is found
(progn ; fn contains the full path
(setq fn (open fn "r")) ; open file for reading
(while (setq ln (read-line fn)) ; read all the lines in the file
(setq AllLines (cons ln AllLines))
)
(close fn) ; close the open file handle
)
)
(reverse AllLines)
)
;;===============Tell User To Select Moisture First====================
(alert "1- Select Moisture Grid File First \n2- Select Ash Grid File Second ")
(cond
;;======Read first file to list======================================
((not
(and
(setq M_Open (getfiled "Select Moisture_Grid" "" "txt" 16))
(princ "\n>>> Reading Moisture File <<<\n")
(setq Raw_Data (ReadFile M_Open)) ; read textfile to list
(setq flength1 (length Raw_Data))
(repeat 6 (setq 6_lines (cons (car Raw_Data) 6_lines) ; save the first 6 lines
Raw_Data (cdr Raw_Data))) ; remove first 6 lines
;; split Raw_Data into two lists
(setq flag t) ; this avoids the real conversion once one failure occurs
(not ; prevent the WHILE from stopping the process
(while (setq tmp1 (car Raw_Data))
(if (and flag (setq tmp2 (distof tmp1 2))) ; got a real number
(setq M_ListA (cons tmp2 M_ListA)) ; save it
(setq List_Suffix (cons tmp1 List_Suffix)
flag nil)
)
(setq Raw_Data (cdr Raw_Data))
)
)
)
)
(prompt "\n...Unable to open Moisture file.") ; tell use if unable to get textfile
)
;;===========Read Second file to list==============================
((not
(and
(setq M_Open (getfiled "Select Ash_Grid" "" "txt" 16))
(princ "\n>>> Reading Ash_Grid File <<<\n")
(setq Raw_Data (ReadFile M_Open)) ; read textfile to list
(setq flength2 (length Raw_Data))
(repeat 6 (setq Raw_Data (cdr Raw_Data))) ; remove first 6 lines
(setq flag t) ; this avoids the real conversion once one failure occurs
(not ; prevent the WHILE from stopping the process
(while (setq tmp1 (car Raw_Data))
(if (and flag (setq tmp2 (distof tmp1 2))) ; got a real number
(setq M_ListB (cons tmp2 M_ListB)) ; save it
(setq flag nil)
)
(setq Raw_Data (cdr Raw_Data))
)
)
)
)
(prompt "\n...Unable to open Ash_Grid file.")
)
((and ; Option to quit if number of file entries do not match
(/= (length M_ListA) (length M_ListB))
(null (initget "Yes No")) ; return T
(= (getkword "\nUnequal # of data entries, Process anyway? [Yes/No] <Yes>: ") "No")
)
(prompt "\n...User quit. Files entries not equal.")
)
((and ; Option to quit if number of file entries do not match
(/= flength1 flength2)
(null (initget "Yes No")) ; return T
(= (getkword "\nUnequal file length, Process anyway? [Yes/No] <Yes>: ") "No")
)
(prompt "\n...User quit. Files entries not equal.")
)
;;===Create Output file and Add Coordinates and grid sizes======
;; write the first 6 lines, then the data, then the suffix lines
((not
(and
(princ "\n>>> Creating Output,txt file <<<\n")
(setq M_Output (open "C:\\Projects\\Output.txt" "w"))
(progn
(mapcar (function (lambda (x) (write-line x M_Output))) (reverse 6_lines))
(setq M_Outfile
(mapcar
(function
(lambda (x y) (* (/ x (- 100.0 y)) 100.0))) M_ListA M_ListB)
)
(mapcar (function (lambda(x) (write-line (rtos x 2) M_Output))) M_Outfile)
(mapcar (function (lambda(x) (write-line x M_Output))) (reverse List_Suffix))
(null (close M_Output))
(princ "\nFinished...File Converted")
(princ (strcat "\n" (itoa (length M_ListA)) " data points processed."))
)
)
)
(prompt "\n...Unable to open output file.")
)
)
(princ)
)
-
You need to test this routine as I don't have time this morning.
Works great CAB... thanks a bunch. I will study your code and may have some specific question. Your explanation helped on the "NOT" function.
-
Glad it works for you.
Questions welcome. :-)
-
Oops, corrected code above.
Needed a space between YesNo like this
(null (initget "Yes No")) ; return T