jeeswg's strings tutorial

Helpful script writing tricks and HowTo's
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

jeeswg's strings tutorial

11 Jun 2017, 23:24

==================================================

[updated: 2019-07-24]

jeeswg's strings tutorial

- Please make any comments, or any requests/suggestions as to further content.
- Do share any links that you found useful re. strings.

==================================================

> INTRO

- We will introduce the built-in string functions and operators.
- Then we will present some classic string examples.

==================================================

> SUMMARY OF FUNCTIONS/COMMANDS

- Chr/Ord [e.g. Chr(33) is '!', Ord("!") is 33]
- Format [useful for concatenating strings/numbers, and displaying numbers in a particular format][also useful for dec to hex/hex to dec]
- InStr [search for a string, return the position of the nth/nth-to-last occurrence]
- RegExMatch/RegExReplace [search for a string/replace strings using RegEx (regular expressions)]
- Sort [sort strings/numbers based on a delimiter character]
- SplitPath [split path/url to variables]
- StrCompare (AHK v2) [compare 2 strings, return positive/0/negative]
- String (AHK v2) [convert a number/object to a string]
- StringCaseSense [determines whether certain functions/operators do case sensitive/insensitive/locale comparisons]
- StringLower/StringUpper (AHK v1) [lower/upper/title case][note: Format can also change a string's case]
- StrLower/StrUpper (AHK v2) [lower/upper/title case][note: Format can also change a string's case]
- StrLen [get a string's length]
- StrPut/StrGet [convert between encodings e.g. ANSI/UTF-8/UTF-16]
- StrReplace [replace/count strings]
- StrSplit [string to array: split a string based on delimiter strings]
- SubStr [crop a string]
- Trim/LTrim/RTrim [remove leading/trailing characters]
- VarSetCapacity [prepare a string with a certain capacity, speeds up appending to a list][note: AHK automatically increases the size of a string buffer when needed]

- Also (control flow statements):
- Loop Parse, var [parse strings by delimiters]
- Loop Parse, var, CSV [parse strings by delimiters]
- for key, value in StrSplit(...) [parse strings by delimiters]

> SUMMARY OF DEPRECATED FUNCTIONS/COMMANDS

Asc -> Ord
AutoTrim -> [no AHK v2 equivalent]
SetFormat -> Format
StringGetPos -> InStr
StringLeft/StringRight -> SubStr
StringTrimLeft/StringTrimRight -> SubStr
StringLen -> StrLen
StringMid -> SubStr
StringReplace -> StrReplace
StringSplit -> StrSplit
Transform -> [various functions][note: Transform-Deref/Transform-HTML do not have AHK v2 equivalents]

- Note: it's very awkward to convert StringSplit to StrSplit.
- A quick fix is to replace a StringSplit one-liner with a Loop Parse two-liner. See here:
AHK v1 to AHK v2 conversion tips/changes summary - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=37&t=36787

- Note: 'var = value' style continuation sections will not be available in AHK v2.

> ADDITIONAL FUNCTIONALITY DEMONSTRATED IN EXAMPLES

- alphabetise/reverse/shuffle (randomise)
- contains/equals/starts/ends (matches) [e.g. var contains (at least one of) list]
- count [note: StrReplace can count string occurrences]
- repeat
- insert/overwrite
- join
- unused [find unused characters e.g. for use as temporary delimiters]
- between [is a string alphabetically between two other strings]
- min/max [biggest/smallest string in a list]
- get case [e.g. is it lower/upper/title/mixed]
- pad string [add text to start/beginning][see LINKS]
- Caesar cipher [see LINKS]

==================================================

> SUMMARY OF STRING PSEUDO-OPERATORS

- Code comparing the following operators/pseudo-operators in AHK v1:
= != <> < <= > >=
- In general, in AHK v1, it's safer to use if-statements with parentheses.

Code: Select all

;legacy syntax (pseudo-operators) (AHK v1):
if var = LiteralString
if var != LiteralString
if var <> LiteralString
if var < LiteralString
if var <= LiteralString
if var > LiteralString
if var >= LiteralString

if var = %var2%
if var != %var2%
if var <> %var2%
if var < %var2%
if var <= %var2%
if var > %var2%
if var >= %var2%

;expression syntax equivalents (operators) (AHK v1):
if (var = "LiteralText")
if (var != "LiteralText")
if (var <> "LiteralText")
if (var < "LiteralText")
if (var <= "LiteralText")
if (var > "LiteralText")
if (var >= "LiteralText")

if (var = var2)
if (var != var2)
if (var <> var2)
if (var < var2)
if (var <= var2)
if (var > var2)
if (var >= var2)
- Link:
IfEqual / IfLess / IfGreater - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/IfEqual.htm

- Code demonstrating the following pseudo-operators in AHK v1:
between / contains / in / is

Code: Select all

;pseudo-operators (AHK v1)
;note: AHK v2 does not have a 'between' operator
;these look like operators in AHK v1 but are not operators
between
contains
in
is

;note: the case sensitivity of between/contains/in is determined by StringCaseSense

;==================================================

;BETWEEN

if vNum between -3 and 3 ;AHK v1
if (vNum >= -3) && (vNum <= 3) ;AHK v1/v2

if vNum between %vLBound% and %vUBound% ;AHK v1
if (vNum >= vLBound) && (vNum <= vUBound) ;AHK v1/v2

;NOT BETWEEN

if vNum not between -3 and 3 ;AHK v1
if (vNum < -3) || (vNum > 3) ;AHK v1/v2

if vNum not between %vLBound% and %vUBound% ;AHK v1
if (vNum < vLBound) || (vNum > vUBound) ;AHK v1/v2

;==================================================

;CONTAINS

if var contains abc,def,ghi ;AHK v1
if var contains % "abc,def,ghi" ;AHK v1
;('contains' not yet implemented in AHK v2)

if var contains %vList% ;AHK v1
if var contains % vList ;AHK v1
;('contains' not yet implemented in AHK v2)

;NOT CONTAINS

if var not contains abc,def,ghi ;AHK v1
if var not contains % "abc,def,ghi" ;AHK v1
;('contains' not yet implemented in AHK v2)

if var not contains %vList% ;AHK v1
if var not contains % vList ;AHK v1
;('contains' not yet implemented in AHK v2)

;==================================================

;IN

if var in abc,def,ghi ;AHK v1
if var in % "abc,def,ghi" ;AHK v1
;('in' not yet implemented in AHK v2)

if var in %vList% ;AHK v1
if var in % vList ;AHK v1
;('in' not yet implemented in AHK v2)

;NOT IN

if var not in abc,def,ghi ;AHK v1
if var not in % "abc,def,ghi" ;AHK v1
;('in' not yet implemented in AHK v2)

if var not in %vList% ;AHK v1
if var not in % vList ;AHK v1
;('in' not yet implemented in AHK v2)

;==================================================

;IS

if var is number ;AHK v1
if var is "number" ;AHK v2
if (var is "number") ;AHK v2

if var is %vType% ;AHK v1
if var is % vType ;AHK v1
if var is vType ;AHK v2
if (var is vType) ;AHK v2

;IS NOT

if var is not number ;AHK v1
if !(var is "number") ;AHK v2

if var is not %vType% ;AHK v1
if var is not % vType ;AHK v1
if !(var is "number") ;AHK v2
;==================================================

> SUMMARY OF STRING OPERATORS

- Note: many of these operators apply to other variable types (e.g. integers/floats/objects) and to expressions generally.

~= RegExMatch [equivalent to function but with Haystack and Needle parameters only]
contains (AHK v2) [not implemented]
in (AHK v2) [not implemented]
is (AHK v2) [if var is type][if !(var is type)]
. [concatenate][can usually be omitted]
.= [append]
:= [assign]
[e.g. 'var := expression', note: 'var = value' is deprecated]

= [equals (case insensitive)][note: = for *comparison* not assignment][note: 'var = value', by itself, is assignment in AHK v1]
== [equals (case sensitive)]
!= (AHK v1) [not equals (StringCaseSense)]
!= (AHK v2) [not equals (case insensitive]
!== (AHK v2) [not equals (case sensitive)][not in AHK v1]
<> (AHK v1) [not equals (StringCaseSense)][currently not in AHK v2]
< [less-than (StringCaseSense)]
<= [less-than-or-equal-to (StringCaseSense)]
> [greater-than (StringCaseSense)]
>= [greater-than-or-equal-to (StringCaseSense)]

- For these operators: ?: (ternary operator) && || and not or !, a blank string or a string which looks like zero is considered false.
- See 'BASICS: IF (NON-ZERO) (NON-BLANK)', here:
jeeswg's mathematics tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=64545

==================================================

> GENERAL EXAMPLES

- Note: there may be other/better approaches for some of these tasks.

Code: Select all

;==================================================

;CONTENTS

;ASSIGN
;ASSIGN DIFFERENT CONTENTS TO MULTIPLE VARIABLES
;ASSIGN THE SAME CONTENTS TO MULTIPLE VARIABLES
;RETRIEVE CONTENTS
;CONCATENATE
;APPEND
;PREPEND
;REPEAT A STRING
;INSERT
;OVERWRITE
;MULTILINE TEXT
;COMPARE CASE SENSITIVE / INSENSITIVE
;MIN/MAX STRINGS
;SPECIAL CHARACTERS
;IF VAR (NOT) CONTAINS LIST
;IF VAR (NOT) IN LIST
;IF VAR (NOT) STARTS LIST
;IF VAR (NOT) ENDS LIST
;FIND TEXT IN STRING (FIND A NEEDLE IN A HAYSTACK)
;SEARCH FROM THE NTH-TO-LAST CHARACTER BACKWARDS
;SEARCH FROM THE NTH CHARACTER BACKWARDS
;GET SUBSTRING
;CROP STRING
;SUBSTR WITH 0 AS A PARAMETER
;PAD WITH LEADING ZEROS (STRING WITH EXACTLY N CHARACTERS)
;PAD WITH LEADING ZEROS (STRING WITH MINIMUM N CHARACTERS)
;COUNT OCCURRENCES
;REPLACE TEXT (REPLACE 'BEFORE' TEXT WITH 'AFTER' TEXT)
;SPLIT STRING: GET TEXT BEFORE/AFTER POSITION (NEEDLE)
;SPLIT PATH
;TRIM
;ARRAYS/OBJECTS: GET NTH ITEM (SPLIT STRING)
;ARRAYS/OBJECTS: LOOK UP ITEM
;ARRAYS/OBJECTS: PARSE TWO LISTS
;LOOPS: PARSE TWO LISTS (WITHOUT USING ARRAYS/OBJECTS)
;LOOPS: REPLACE MULTIPLE SPACES WITH SINGLES SPACES
;LOOPS: REMOVE LEADING SPACES FROM LINES/LIST ITEMS
;LOOPS: REMOVE TRAILING SPACES FROM LINES/LIST ITEMS
;LOOPS: PARSE STRING
;LOOPS: SLICE STRING
;LOOPS: SPLIT STRING
;LOOPS: SPLIT STRING (SWAP COLUMNS)
;LOOPS: SPLIT STRING (SWAP COLUMNS) (FURTHER EXAMPLE)
;LOOPS: LIST TO TABLE (BY ROWS)
;LOOPS: LIST TO TABLE (BY COLUMNS)
;CONTINUATION SECTIONS
;SORT: CUSTOM COMPARISON FUNCTIONS
;SORT: CUSTOM COMPARISON FUNCTIONS (UNSTABLE V. STABLE SORT)
;SORT: ALPHABETICAL (UNSTABLE)
;SORT: ALPHABETICAL (STABLE)
;SORT: REVERSE LIST
;SORT: BY NTH COLUMN
;SORT: RECORD COMPARISONS
;SORT STRING: ALPHABETISE
;SORT STRING: REVERSE
;SORT STRING: SHUFFLE (RANDOMISE)
;STRING/NUMERIC: COMPARE NUMERIC AS TEXT
;STRING/NUMERIC: CHECK IF VARIABLE LOOKS LIKE STRING/NUMERIC
;STRING/NUMERIC: CHECK IF VARIABLE IS STRING/NUMERIC
;STRING/NUMERIC: IF VAR IS TYPE
;STRING/NUMERIC: GET CASE
;COMMAND STYLE (=) V. EXPRESSION STYLE (:=)
;SENDINPUT (ESCAPING CHARACTERS)

;==================================================

;ASSIGN

var := "hello world"
var := 0
var := "" ;set variable to a blank string

oldvar := "hello world"
newvar := oldvar

var := "hello world"
MsgBox, % var

;store numbers as strings e.g. to preserve leading zeros
var := 000
MsgBox, % var ;000 (AHK v1), 0 (AHK v2)
var := "000"
MsgBox, % var ;000

;==================================================

;ASSIGN DIFFERENT CONTENTS TO MULTIPLE VARIABLES

var1 := "a", var2 := "b", var3 := "c"
MsgBox, % var1 " " var2 " " var3

;==================================================

;ASSIGN THE SAME CONTENTS TO MULTIPLE VARIABLES

var1 := var2 := var3 := "hello world"
var1 := var2 := var3 := 0

;equivalent to:
var3 := "hello world"
var2 := var3
var1 := var2

;==================================================

;RETRIEVE CONTENTS

var := "hello world"
MsgBox, % var

var1 := "hello world"
var2 := var1
MsgBox, % var2

var1 := "hello world"
varname := "var1"
var2 := %varname%
MsgBox, % var2 ;hello world

varname := "var1"
%varname% := "hello world"
MsgBox, % var1 ;hello world

;==================================================

;CONCATENATE

var1 := "a", var2 := "b", var3 := "c"
var4 := var1 "A" var2 var3 "B" "C"
var4 := var1 . "A" . var2 . var3 . "B" . "C" ;equivalent to line above
MsgBox, % var4

;the style without the dots is called 'auto-concat'

;==================================================

;APPEND

var := "hello"
var .= " world"
MsgBox, % var

var1 := "hello"
var2 := "world"
var1 .= " " var2
MsgBox, % var1

;==================================================

;PREPEND

var := "world"
var := "hello " var
MsgBox, % var

var1 := "world"
var2 := "hello"
var1 := var2 " " var1
MsgBox, % var1

;==================================================

;REPEAT A STRING

;via Loop
var := ""
Loop 10
	var .= "hello "
MsgBox, % var

;via a custom function
;StrRept(vText, vNum)
;{
;	if (vNum <= 0)
;		return ""
;	VarSetCapacity(vOutput, StrLen(vText)*vNum*(A_IsUnicode?2:1))
;	Loop % vNum
;		vOutput .= vText
;	return vOutput
;}
;
;var := StrRept("hello ", 10)
;MsgBox, % var

;via Format and StrReplace
MsgBox, % Format("{:5}", "") ;(5 spaces)
MsgBox, % Format("{:05}", 0) ;00000
MsgBox, % StrReplace(Format("{:5}", ""), " ", "a") ;aaaaa
MsgBox, % StrReplace(Format("{:5}", ""), " ", "a_") ;a_a_a_a_a_

vText := "abc ", vNum := 5
MsgBox, % StrReplace(Format("{:" vNum "}", ""), " ", vText)

;via VarSetCapacity and StrReplace
vText := "abc ", vNum := 5
vOutput := ""
VarSetCapacity(vOutput, vNum*(1+!!A_IsUnicode), 1)
vOutput := StrReplace(vOutput, Chr(A_IsUnicode?257:1), vText)
MsgBox, % vOutput

;==================================================

;INSERT

vText := "abcdefghijkl"
vText2 := "ZZZZ"
vPos := 7
vText := SubStr(vText, 1, vPos-1) vText2 SubStr(vText, vPos)
MsgBox, % vText

;==================================================

;OVERWRITE

;warning: this could cause a problem if the new string goes beyond the end of the old string
vText := "abcdefghijkl"
vText2 := "ZZZZ"
vPos := 5
;StrPut(vText2, &vText+(vPos-1), StrLen(vText2), "CP0") ;AHK ANSI versions
;StrPut(vText2, &vText+(vPos-1)*2, StrLen(vText2), "UTF-16") ;AHK Unicode versions
StrPut(vText2, &vText+(vPos-1)*(A_IsUnicode?2:1), StrLen(vText2), A_IsUnicode?"UTF-16":"CP0") ;AHK ANSI or Unicode versions
MsgBox, % vText

;alternatively:
vText := "abcdefghijkl"
vText2 := "ZZZZ"
vPos := 5
vText := SubStr(vText, 1, vPos-1) vText2 SubStr(vText, vPos+StrLen(vText2))
MsgBox, % vText

;==================================================

;MULTILINE TEXT

var := "line 1`r`nline 2`r`nline 3"
var := "line 1" Chr(13) Chr(10) "line 2" Chr(13) Chr(10) "line 3"
MsgBox, % var

;because you can get text like 'nline' above, when spellchecking,
;sometimes I split text up like so:
var := "line 1`r`n" "line 2`r`n" "line 3"

;this is another alternative:
var := "line 1" "`r`n" "line 2" "`r`n" "line 3"

;==================================================

;COMPARE CASE SENSITIVE / INSENSITIVE

var1 := "A"
var2 := "a"

;compare case sensitive
if (var1 == var2)
	MsgBox, % "same"
else
	MsgBox, % "different"

;compare case insensitive
if (var1 = var2)
	MsgBox, % "same"
else
	MsgBox, % "different"

;compare case sensitive/insensitive depending on A_StringCaseSense
StringCaseSense, On
if !(var1 <> var2)
	MsgBox, % "same"
else
	MsgBox, % "different"

StringCaseSense, Off
if !(var1 <> var2)
	MsgBox, % "same"
else
	MsgBox, % "different"

;AHK v1: compare case sensitive/insensitive depending on A_StringCaseSense
;note: in AHK v2, != is always case insensitive
StringCaseSense, On
if !(var1 != var2)
	MsgBox, % "same"
else
	MsgBox, % "different"

StringCaseSense, Off
if !(var1 != var2)
	MsgBox, % "same"
else
	MsgBox, % "different"

;[see StringCaseSense re. On/Off/Locale]
;StringCaseSense - Syntax & Usage | AutoHotkey
;https://autohotkey.com/docs/commands/StringCaseSense.htm

;==================================================

;MIN/MAX STRINGS

oArray := StrSplit("qwe,rty,uio,pas,dfg,hjk,lzx,cvb,nm", ",")
vMin := oArray.1
for _, vValue in oArray
{
	if ("" vValue < "" vMin)
		vMin := vValue
}
MsgBox, % vMin

oArray := StrSplit("qwe,rty,uio,pas,dfg,hjk,lzx,cvb,nm", ",")
vMax := oArray.1
for _, vValue in oArray
{
	if ("" vValue > "" vMax)
		vMax := vValue
}
MsgBox, % vMax

;==================================================

;SPECIAL CHARACTERS

var := "``"
var := "`%"
var := "`;" ;needs to be escaped in some circumstances
var := """"
var := "`," ;needs to be escaped in some circumstances
var := "`r" ;carriage return
var := "`n" ;linefeed
var := "`t" ;tab

;e.g. semicolon
;a semicolon preceded by a space/tab,
;marks the start of a comment
var := "a;b" ;doesn't require escaping
MsgBox, % var
var := "a `;b" ;requires escaping
MsgBox, % var

;e.g. comma
;Run, % "control desk.cpl,,2" ;doesn't require escaping
;Run, control desk.cpl`,`,2 ;requires escaping

;see also:
;[#EscapeChar (and explanation of escape sequences)]
;#EscapeChar - Syntax & Usage | AutoHotkey
;https://autohotkey.com/docs/commands/_EscapeChar.htm

;==================================================

;IF VAR (NOT) CONTAINS LIST

;equivalent:
if var contains a,b,c
if InStr(var, "a") || InStr(var, "b") || InStr(var, "c")
if InStr(var, "a") OR InStr(var, "b") OR InStr(var, "c")

;equivalent:
if var not contains % var1 "," var2 "," var3
if !InStr(var, var1) && !InStr(var, var2) && !InStr(var, var3)
if !InStr(var, var1) AND !InStr(var, var2) AND !InStr(var, var3)

;see also:
;['if var (not) in/contains' alternatives]
;Improve InStr or IfInString - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=13&t=31812&sid=0538bf13b8a638cac988c4b268cf11b0

;==================================================

;IF VAR (NOT) IN LIST

;equivalent:
if var in red,yellow,green,blue
if (var = "red") || (var = "yellow") || (var = "green") || (var = "blue")
if (var = "red") OR (var = "yellow") OR (var = "green") OR (var = "blue")

;equivalent:
if var not in % var1 "," var2 "," var3
if !(var = var1) && !(var = var2) && !(var = var3)
if !(var = var1) AND !(var = var2) AND !(var = var3)

;==================================================

;IF VAR (NOT) STARTS LIST

if (SubStr(var, 1, 3) = "red") || (SubStr(var, 1, 6) = "yellow") || (SubStr(var, 1, 5) = "green") || (SubStr(var, 1, 4) = "blue")

if !(SubStr(var, 1, 3) = "red") && !(SubStr(var, 1, 6) = "yellow") && !(SubStr(var, 1, 5) = "green") && !(SubStr(var, 1, 4) = "blue")

;==================================================

;IF VAR (NOT) ENDS LIST

vIsV1 := !!SubStr(1, 0) ;for AHK v1/v2 two-way compatibility

if (SubStr(var, vIsV1-3) = "red") || (SubStr(var, vIsV1-6) = "yellow") || (SubStr(var, vIsV1-5) = "green") || (SubStr(var, vIsV1-4) = "blue")

if !(SubStr(var, vIsV1-3) = "red") && !(SubStr(var, vIsV1-6) = "yellow") && !(SubStr(var, vIsV1-5) = "green") && !(SubStr(var, vIsV1-4) = "blue")

;==================================================

;FIND TEXT IN STRING (FIND A NEEDLE IN A HAYSTACK)

vPos := InStr(vText, vNeedle, vCaseSen, vPos, vOcc)

vPos := InStr(vText, " ") ;find first space
vPos := InStr(vText, " ", 0, 1, 1) ;find first space
vPos := InStr(vText, " ", 0, 1, 2) ;find second space
vPos := InStr(vText, " ", 0, 1, n) ;find nth space

vPos := InStr(vText, " ", 0, 0, 1) ;find last space (AHK v1)
vPos := InStr(vText, " ", 0, 0, 2) ;find second-to-last space (AHK v1)
vPos := InStr(vText, " ", 0, 0, n) ;find nth-to-last space (AHK v1)

vPos := InStr(vText, " ", 0, -1, 1) ;find last space (AHK v2)
vPos := InStr(vText, " ", 0, -1, 2) ;find second-to-last space (AHK v2)
vPos := InStr(vText, " ", 0, -1, n) ;find nth-to-last space (AHK v2)

vIsV1 := InStr(1, 1,, 0)
vPos := InStr(vText, " ", 0, vIsV1-1, 1) ;find last space (two-way compatible)
vPos := InStr(vText, " ", 0, vIsV1-1, 2) ;find second-to-last space (two-way compatible)
vPos := InStr(vText, " ", 0, vIsV1-1, n) ;find nth-to-last space (two-way compatible)

;find string (case sensitive)
vText := "aA"
MsgBox, % vPos := InStr(vText, "A", 1)
MsgBox, % vPos := InStr(vText, "a", 1)

;find string (case insensitive)
vText := "aA"
MsgBox, % vPos := InStr(vText, "A", 0)
MsgBox, % vPos := InStr(vText, "a", 0)

;find string (case insensitive)
vText := "aA"
MsgBox, % vPos := InStr(vText, "A")
MsgBox, % vPos := InStr(vText, "a")

;[see StringCaseSense re. On/Off/Locale]
;StringCaseSense - Syntax & Usage | AutoHotkey
;https://autohotkey.com/docs/commands/StringCaseSense.htm

;==================================================

;SEARCH FROM THE NTH-TO-LAST CHARACTER BACKWARDS

vText := "abcd abcd abcd abcd "

;AHK v1
MsgBox, % InStr(vText, " ", 0, 0) ;20 ;search from the last character backwards
MsgBox, % InStr(vText, " ", 0, -1) ;15 ;search from the 2nd-to-last character backwards
MsgBox, % InStr(vText, " ", 0, -5) ;15 ;search from the 6th-to-last character backwards
MsgBox, % InStr(vText, " ", 0, -6) ;10 ;search from the 7th-to-last character backwards
;MsgBox, % InStr(vText, " ", 0, 1-n) ;search from the nth-to-last character backwards

;AHK v1 (equivalent)
MsgBox, % InStr(vText, " ", 0, 1-1) ;20 ;search from the last character backwards
MsgBox, % InStr(vText, " ", 0, 1-2) ;15 ;search from the 2nd-to-last character backwards
MsgBox, % InStr(vText, " ", 0, 1-6) ;15 ;search from the 6th-to-last character backwards
MsgBox, % InStr(vText, " ", 0, 1-7) ;10 ;search from the 7th-to-last character backwards
;MsgBox, % InStr(vText, " ", 0, 1-n) ;search from the nth-to-last character backwards

;AHK v2
MsgBox, % InStr(vText, " ", 0, -1) ;20 ;search from the last character backwards
MsgBox, % InStr(vText, " ", 0, -2) ;15 ;search from the 2nd-to-last character backwards
MsgBox, % InStr(vText, " ", 0, -6) ;15 ;search from the 6th-to-last character backwards
MsgBox, % InStr(vText, " ", 0, -7) ;10 ;search from the 7th-to-last character backwards
;MsgBox, % InStr(vText, " ", 0, -n) ;search from the nth-to-last character backwards

;AHK v1/v2 two-way compatible
vIsV1 := InStr(1, 1,, 0)
MsgBox, % InStr(vText, " ", 0, vIsV1-1) ;20 ;search from the last character backwards
MsgBox, % InStr(vText, " ", 0, vIsV1-2) ;15 ;search from the 2nd-to-last character backwards
MsgBox, % InStr(vText, " ", 0, vIsV1-6) ;15 ;search from the 6th-to-last character backwards
MsgBox, % InStr(vText, " ", 0, vIsV1-7) ;10 ;search from the 7th-to-last character backwards
;MsgBox, % InStr(vText, " ", 0, vIsV1-n) ;search from the nth-to-last character backwards

;==================================================

;SEARCH FROM THE NTH CHARACTER BACKWARDS

;note: fwds (forwards), bwds (backwards), len (length of the string, in this example: 20 characters)

;vText := "abcd abcd abcd abcd "
;          |        |         |
;fwds:      1       10       20
;bwds v1: -19      -10        0
;bwds v1: [-len+1] [-len+10] [-len+20]
;bwds v2: -20      -11       -1
;bwds v2:[-len-1+1][-len-1+10][-len-1+20]

;to search forwards via the InStr function, you specify the positive 'forwards' index of the character
;to search backwards via the InStr function, you specify the zero/negative 'backwards' index of the character
;each character has both a 'forwards' and a 'backwards' index
;to convert from forwards 'F' to backwards 'B':
;B = - Len + F ;AHK v1
;B = - Len - 1 + F ;AHK v2
;B = - Len - !vIsV1 + F ;AHK v1/v2 two-way compatible

;vIsV1 = 1 if the AHK version is version 1
;vIsV1 = 0 if the AHK version is version 2 or some other version
;!vIsV1, is equivalent to 'not vIsV1'
;!1 = 0
;!0 = 1

;so to search backwards from character n:
;vLen := StrLen(vText)
;vIsV1 := InStr(1, 1,, 0)
;vPos := InStr(vText, vNeedle, 0, -vLen+n) ;AHK v1
;vPos := InStr(vText, vNeedle, 0, -vLen-1+n) ;AHK v2
;vPos := InStr(vText, vNeedle, 0, -vLen-!vIsV1+n) ;AHK v1/v2 two-way compatible

;==================================================

;GET SUBSTRING

vText := SubStr(vText, 1, 1) ;get first character
vText := SubStr(vText, 1, 5) ;get first 5 characters
vText := SubStr(vText, 1, n) ;get first n characters

vText := SubStr(vText, 1, 0) ;get last character (AHK v1)
vText := SubStr(vText, 1, -4) ;get last 5 characters (AHK v1)
vText := SubStr(vText, 1, 1-n) ;get last n characters (AHK v1)

vText := SubStr(vText, 1, 1-1) ;get last character (AHK v1) (alternative presentation)
vText := SubStr(vText, 1, 1-5) ;get last 5 characters (AHK v1) (alternative presentation)
vText := SubStr(vText, 1, 1-n) ;get last 5 characters (AHK v1) (alternative presentation)

vText := SubStr(vText, 1, -1) ;get last character (AHK v2)
vText := SubStr(vText, 1, -5) ;get last 5 characters (AHK v2)
vText := SubStr(vText, 1, -n) ;get last n characters (AHK v2)

vIsV1 := !!SubStr(1, 0)
vText := SubStr(vText, 1, vIsV1-1) ;get last character (two-way compatible)
vText := SubStr(vText, 1, vIsV1-5) ;get last 5 characters (two-way compatible)
vText := SubStr(vText, 1, vIsV1-n) ;get last n characters (two-way compatible)

SubStr(vText, 6, 5) ;get characters 6 to 10 inclusive
SubStr(vText, 6, 10-6+1) ;get characters 6 to 10 inclusive
SubStr(vText, vPos1, vPos2-vPos1+1) ;get characters a to b inclusive

;e.g. split date
vDate := A_Now
vYear := SubStr(vDate, 1, 4)
vMonth := SubStr(vDate, 5, 2)
vDay := SubStr(vDate, 7, 2)
vHour := SubStr(vDate, 9, 2)
vMin := SubStr(vDate, 11, 2)
vSec := SubStr(vDate, 13, 2)

;e.g. split date (via RegEx)
vDate := A_Now
MsgBox, % RegExReplace(vDate, "(....)(..)(..)(..)(..)(..)", "$1 $2 $3 $4 $5 $6")

;e.g. get substring
vText := "aaaaabbbbbcccccddddd"
vIsV1 := !!SubStr(1, 0)
MsgBox, % SubStr(vText, 1, 10) ;first 10 characters
MsgBox, % SubStr(vText, 6, 5) ;2nd lot of 5 characters (chars 6-10)
MsgBox, % SubStr(vText, vIsV1-10) ;last 10 characters
MsgBox, % SubStr(vText, vIsV1-10, 5) ;2nd-to-last lot of 5 characters (first 5 of the last 10 characters)

;e.g. get random substring
vText := "abcdefghijklmnopqrstuvwxyz"
vIsV1 := !!SubStr(1, 0)
Loop 10
{
	Random, vNum1, 0, 26
	Random, vNum2, 1, 26
	;random initial substring
	vText1 := SubStr(vText, 1, vNum1)
	;random final substring
	vText2 := vNum1 ? SubStr(vText, vIsV1-vNum1) : ""
	;random substring
	vText3 := SubStr(vText, vNum2, vNum1)
	MsgBox, % vText1 "`r`n" vText2 "`r`n" vText3
}

;==================================================

;CROP STRING

vText := SubStr(vText, 2) ;remove first character
vText := SubStr(vText, 6) ;remove first 5 characters
vText := SubStr(vText, n+1) ;remove first n characters

vText := SubStr(vText, 1, -1) ;remove last character
vText := SubStr(vText, 1, -5) ;remove last 5 characters
vText := SubStr(vText, 1, -n) ;remove last n characters

vText := SubStr(vText, 2, -1) ;remove first and last characters

;==================================================

;SUBSTR WITH 0 AS A PARAMETER

vText := "abcdefghijklmnopqrstuvwxyz"
MsgBox, % SubStr(vText, 0, 10) ;z (AHK v1), (blank) (AHK v2)
MsgBox, % SubStr(vText, 0, 0) ;(blank)
MsgBox, % SubStr(vText, 0, -10) ;(blank)
MsgBox, % SubStr(vText, 10, 0) ;(blank)
MsgBox, % SubStr(vText, 0, 0) ;(blank)
MsgBox, % SubStr(vText, -10, 0) ;(blank)

;==================================================

;PAD WITH LEADING ZEROS (STRING WITH EXACTLY N CHARACTERS)

;get substring and pad with leading zeros (if necessary)

vNum := 3
vIsV1 := !!SubStr(1, 0)
vText := SubStr("00" vNum, vIsV1-2) ;last 2 characters e.g. 03 ;string will be exactly 2 characters longs
vText := SubStr("0000" vNum, vIsV1-4) ;last 4 characters e.g. 0003 ;string will be exactly 4 characters longs

;==================================================

;PAD WITH LEADING ZEROS (STRING WITH MINIMUM N CHARACTERS)

;ensure at least n characters

vNum := 3
MsgBox, % Format("{:02}", vNum) ;03 ;string will be at least 2 digits long
MsgBox, % Format("{:04}", vNum) ;0003 ;string will be at least 4 digits long

vNum := 300
MsgBox, % Format("{:02}", vNum) ;300 ;string will be at least 2 digits long
MsgBox, % Format("{:04}", vNum) ;0300 ;string will be at least 4 digits long

MsgBox, % Format("{:0" vCount "}", vNum) ;vCount: number of leading zeros ;string will be at least vCount digits long

;==================================================

;COUNT OCCURRENCES

vText := "aaa"
StrReplace(vText, "a", "", vCount)
MsgBox, % vCount ;3

;==================================================

;REPLACE TEXT (REPLACE 'BEFORE' TEXT WITH 'AFTER' TEXT)

;replace (without counting replacements)
vText2 := StrReplace(vText1, " ", "",, 1) ;replace first occurrence
vText2 := StrReplace(vText1, " ", "",, 2) ;replace first 2 occurrences
vText2 := StrReplace(vText1, " ", "",, -1) ;replace all occurrences
vText2 := StrReplace(vText1, " ", "") ;replace all occurrences

;replace and count replacements
vText2 := StrReplace(vText1, " ", "", vCount, 1) ;replace first occurrence
vText2 := StrReplace(vText1, " ", "", vCount, 2) ;replace first 2 occurrences
vText2 := StrReplace(vText1, " ", "", vCount, -1) ;replace all occurrences

;==================================================

;SPLIT STRING: GET TEXT BEFORE/AFTER POSITION (NEEDLE)

;split text into 2 variables, before/after the first tab
vText := "abc`tdef"
vPos := InStr(vText, "`t")
vText1 := SubStr(vText, 1, vPos-1)
vText2 := SubStr(vText, vPos+1)
MsgBox, % vText1 "`r`n" vText2

;split text into 2 variables, before/after the first tab
;also handle the case where there is no tab
vText := "abc`tdef"
if (vPos := InStr(vText, "`t"))
	vText1 := SubStr(vText, 1, vPos-1), vText2 := SubStr(vText, vPos+1)
else
	vText1 := vText2, vText2 := ""
MsgBox, % vText1 "`r`n" vText2

;split text into 2 variables, before/after the first tab
vText := "abc`tdef`tghi"
oArray := StrSplit(vText, "`t",, 2)
MsgBox, % oArray.1 "`r`n" oArray.2
oArray := ""

;split text into 2 variables, before/after the first tab
;also handle the case where there is no tab
vText := "abc`tdef"
vPos := InStr(vText "`t", "`t")
	vText1 := SubStr(vText, 1, vPos-1), vText2 := SubStr(vText, vPos+1)
MsgBox, % vText1 "`r`n" vText2

;get root key/subkey
vKey := "HKEY_CURRENT_USER\Software\Microsoft\Notepad"
if (vPos := InStr(vKey, "\"))
	vRootKey := SubStr(vKey, 1, vPos-1), vSubKey := SubStr(vKey, vPos+1)
else
	vRootKey := vKey, vSubKey := ""
MsgBox, % vRootKey "`r`n" vSubKey

;get first line
if (vPos := InStr(vText1, "`r"))
	vText2 := SubStr(vText1, 1, vPos-1)
else
	vText2 := vText1

;get last entry in list in comma-separated list
vText1 := "aaa,bbb,ccc,ddd,eee,fff"
vIsV1 := !!SubStr(1, 0)
vText2 := SubStr(vText1, InStr(vText1, ",", 0, vIsV1-1, 1) + 1)

;==================================================

;SPLIT PATH

;vPath := A_AhkPath
vPath := "C:\Program Files\AutoHotkey\AutoHotkeyU32.exe"
SplitPath, vPath, vName, vDir, vExt, vNameNoExt, vDrive
vList := "Path,Name,Dir,Ext,NameNoExt,Drive"
vOutput := ""
Loop Parse, vList, % ","
{
	vOutput .= Format("{:L}", A_LoopField) ": " v%A_LoopField% "`r`n"
	if (A_Index = 1)
		vOutput .= "`r`n"
}
MsgBox, % vOutput

;==================================================

;TRIM

vText := A_Space A_Space A_Space "hello" A_Space A_Space A_Space
MsgBox, % "[" vText "]`r`n[" LTrim(vText) "]`r`n[" Trim(vText) "]`r`n[" RTrim(vText) "]"

vText := "-_-_hello_-_-"
MsgBox, % "[" vText "]`r`n[" LTrim(vText, "-_") "]`r`n[" Trim(vText, "-_") "]`r`n[" RTrim(vText, "-_") "]"

;==================================================

;ARRAYS/OBJECTS: GET NTH ITEM (SPLIT STRING)

vText := "aaa,bbb,ccc,ddd,eee,fff"
oTemp := StrSplit(vText, ",")
MsgBox, % oTemp[3]
MsgBox, % oTemp.3
MsgBox, % oTemp.Length()
oTemp := ""

vText := "a-b=c_d+e"
oTemp := StrSplit(vText, ["-","=","_","+"])
MsgBox, % oTemp.1 "|" oTemp.2 "|" oTemp.3 "|" oTemp.4 "|" oTemp.5
MsgBox, % oTemp.Length()
oTemp := ""

;==================================================

;ARRAYS/OBJECTS: LOOK UP ITEM

oArray1 := StrSplit("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec", ",")
oArray2 := ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
oArray3 := {1:"Jan", 2:"Feb", 3:"Mar", 4:"Apr", 5:"May", 6:"Jun", 7:"Jul", 8:"Aug", 9:"Sep", 10:"Oct", 11:"Nov", 12:"Dec"}
var := 3
MsgBox, % oArray1[var]
MsgBox, % oArray2[3]
MsgBox, % oArray3.3
oArray1 := oArray2 := oArray3 := ""

;==================================================

;ARRAYS/OBJECTS: PARSE TWO LISTS

;e.g. combine columns
vDelim := ","
vList1 := "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"
vList2 := "January,February,March,April,May,June,July,August,September,October,November,December"
oTemp1 := StrSplit(vList1, vDelim)
oTemp2 := StrSplit(vList2, vDelim)
vCount1 := oTemp1.Length()
vCount2 := oTemp2.Length()
;vMin := vCount1 < vCount2 ? vCount1 : vCount2
vMin := Min(vCount1, vCount2) ;equivalent to line above
vOutput := ""
Loop % vMin
{
	vOutput .= oTemp1[A_Index] "`t" oTemp2[A_Index] "`r`n"
}
MsgBox, % vOutput

;==================================================

;LOOPS: PARSE TWO LISTS (WITHOUT USING ARRAYS/OBJECTS)

;e.g. combine columns
vDelim := ","
vList1 := "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec"
vList2 := "January,February,March,April,May,June,July,August,September,October,November,December"

vPos1X := vPos2X := 0
StrReplace(vList1, vDelim, "", vCount1), vCount1++
StrReplace(vList2, vDelim, "", vCount2), vCount2++
;vMin := vCount1 < vCount2 ? vCount1 : vCount2
vMin := Min(vCount1, vCount2) ;equivalent to line above
vOutput := ""
Loop % vMin
{
	if !(A_Index = vMin)
	{
		vPos1 := InStr(vList1, vDelim, 0, vPos1X+1)
		vPos2 := InStr(vList2, vDelim, 0, vPos2X+1)
		vTemp1 := SubStr(vList1, vPos1X+1, vPos1-vPos1X-1)
		vTemp2 := SubStr(vList2, vPos2X+1, vPos2-vPos2X-1)
	}
	else
	{
		vTemp1 := SubStr(vList1, vPos1X+1)
		vTemp2 := SubStr(vList2, vPos2X+1)
	}

	vOutput .= vTemp1 "`t" vTemp2 "`r`n"

	vPos1X := vPos1
	vPos2X := vPos2
}
MsgBox, % vOutput

;==================================================

;LOOPS: REPLACE MULTIPLE SPACES WITH SINGLES SPACES

vText := "a" A_Space A_Space A_Space "a"
MsgBox, % vText
while InStr(vText, A_Space A_Space)
	vText := StrReplace(vText, A_Space A_Space, " ")
MsgBox, % vText

;via RegEx
vText := "a" A_Space A_Space A_Space "a"
MsgBox, % RegExReplace(vText, " {2,}", " ")

;==================================================

;LOOPS: REMOVE LEADING SPACES FROM LINES/LIST ITEMS

vText := " a`r`n b`r`n c`r`n"
MsgBox, % vText
while (SubStr(vText, 1, 1) = " ")
	vText := SubStr(vText, 2)
while InStr(vText, "`n ")
	vText := StrReplace(vText, "`n ", "`n")
MsgBox, % vText

vText := " a, b, c,"
MsgBox, % vText
while (SubStr(vText, 1, 1) = " ")
	vText := SubStr(vText, 2)
while InStr(vText, ", ")
	vText := StrReplace(vText, ", ", ",")
MsgBox, % vText

;==================================================

;LOOPS: REMOVE TRAILING SPACES FROM LINES/LIST ITEMS

vText := "a `r`nb `r`nc `r`n"
MsgBox, % vText
vIsV1 := !!SubStr(1, 0)
while (SubStr(vText, vIsV1-1) = " ")
	vText := SubStr(vText, 1, -1)
while InStr(vText, " `r")
	vText := StrReplace(vText, " `r", "`r")
MsgBox, % vText

vText := "a ,b ,c ,"
MsgBox, % vText
vIsV1 := !!SubStr(1, 0)
while (SubStr(vText, vIsV1-1) = " ")
	vText := SubStr(vText, 1, -1)
while InStr(vText, " ,")
	vText := StrReplace(vText, " ,", ",")
MsgBox, % vText

;==================================================

;LOOPS: PARSE STRING

vText := "a,b,c"
Loop Parse, vText, % ","
{
	vTemp := A_LoopField
	MsgBox, % vTemp
}

;==================================================

;LOOPS: SLICE STRING

vText := "abcdefghijklmnopqrstuvwxyz"
vLen := StrLen(vText)
vLenSlice := 5
Loop % Ceil(vLen / vLenSlice)
{
	vTemp := SubStr(vText, (A_Index-1)*vLenSlice+1, vLenSlice)
	MsgBox, % vTemp
}

;==================================================

;LOOPS: SPLIT STRING

vText := "a,b,c"
Loop Parse, vText, % ","
{
	vText%A_Index% := A_LoopField
}
MsgBox, % vText1 "`r`n" vText2 "`r`n" vText3

;==================================================

;LOOPS: SPLIT STRING (SWAP COLUMNS)

vText := " ;continuation section
(
1	A
2	B
3	C
)"

vOutput := ""
Loop Parse, vText, % "`n", % "`r"
{
	oTemp := StrSplit(A_LoopField, "`t")
	vOutput .= oTemp.2 "`t" oTemp.1 "`r`n"
}
oTemp := ""
MsgBox, % vOutput

;==================================================

;LOOPS: SPLIT STRING (SWAP COLUMNS) (FURTHER EXAMPLE)

vText := " ;continuation section
(Join`r`n
A	B	C	D	E
F	G	H	I	J
K	L	M	N	O
P	Q	R	S	T
U	V	W	X	Y
Z
1
1	2
1	2	3
1	2	3	4
1	2	3	4	5
)"

vOutput := ""
VarSetCapacity(vOutput, (StrLen(vText)+2)*2)
Loop Parse, vText, % "`n", % "`r"
{
	oTemp := StrSplit(A_LoopField, "`t")
	vTemp := oTemp.2, oTemp.2 := oTemp.3, oTemp.3 := vTemp
	vLine := ""
	Loop % oTemp.MaxIndex()
		vLine .= ((A_Index=1)?"":"`t") oTemp[A_Index]
	vOutput .= RTrim(vLine, "`t") "`r`n"
}
oTemp := ""
;Clipboard := vOutput
MsgBox, % vOutput

;==================================================

;LOOPS: LIST TO TABLE (BY ROWS)

;by rows, e.g.:
;abc
;def
;ghi

;list to table 'by rows' (specify column count)
vText := "abcdefghijklmnopqrstuvwxyz"
vNum := 5 ;column count
vSep := "`t"
oArray := StrSplit(vText)
vOutput := ""
Loop % oArray.Length()
{
	vOutput .= oArray[A_Index]
	vOutput .= !Mod(A_Index, vNum) ? "`r`n" : vSep
}
MsgBox, % vOutput

;==================================================

;LOOPS: LIST TO TABLE (BY COLUMNS)

;by columns, e.g.:
;adg
;beh
;cfi

;list to table 'by columns' (specify row count)
vText := "abcdefghijklmnopqrstuvwxyz"
vNum := 5 ;row count
vSep := "`t"
oArray := StrSplit(vText)
vOutput := ""
vIndex := 1
Loop % oArray.Length()
{
	vOutput .= oArray[vIndex]
	vIndex += vNum
	if (vIndex > oArray.Length())
	{
		vIndex := Mod(vIndex, vNum) + 1
		vOutput .= "`r`n"
	}
	else
		vOutput .= vSep
}
MsgBox, % vOutput

;==================================================

;CONTINUATION SECTIONS

vText := " ;continuation section
(
a
b
c
)"
MsgBox, % vText

vText := " ;continuation section
(Join`r`n
a
b
c
)"
;MsgBox, % vText

vText := " ;continuation section
(Join,
a
b
c
)"
MsgBox, % vText

vText := " ;continuation section
(
	a
	b
	c
)"
MsgBox, % vText

;LTrim, to allow continuation sections which are indented
;not to have leading whitespace
	vText := " ;continuation section
	(LTrim
	a
	b
	c
	)"
	MsgBox, % vText

;==================================================

;SORT: CUSTOM COMPARISON FUNCTIONS

;example from the documentation:

MyVar := "1,2,3,4"
Sort, MyVar, F ReverseDirection D,  ; Reverses the list so that it contains 4,3,2,1
ReverseDirection(a1, a2, offset)
{
    return offset  ; Offset is positive if a2 came after a1 in the original list; negative otherwise.
}

;understanding custom comparison functions:

;if want a1 to be earlier in the new list return a negative number
;if want a2 to be earlier in the new list return a positive number
;if want a1 to be later in the new list return a positive number
;if want a2 to be later in the new list return a negative number
;if offset is negative, a2 was earlier in the original list
;if offset is positive, a1 was earlier in the original list

;to reverse order: return offset
;to maintain order: return -offset

;MORE ON COMPARISON FUNCTIONS (COMPARATORS)

;based on this post:
;Need help to write the good example of using "return" - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=66307&p=285029#p285029

;comparators are the classic unintuitive thing in programming
;firstly, 0 ('false') indicates equality, 1/-1 ('true') indicates inequality
;secondly, re. 1/-1, it's hard to remember which way round it works
;1 indicates the first item is considered bigger
;(and in a sort, bigger items are typically put at the end,
;ascending order: e.g. ABC or 123)

;I remember it as a difference function, similar to AHK's DateDiff:
;cmp(4, 2) -> 4 - 2 -> 2 -> 1
;cmp(2, 4) -> 2 - 4 -> -2 -> -1
;cmp(3, 3) -> 3 - 3 -> 0
;although this analogy fails for strings, unless you convert the string to a number

;AHK CUSTOM COMPARISON FUNCTIONS AND THE OFFSET VALUE

;when the AHK Sort function uses a custom comparison function,
;it passes an offset value to the function,
;if arg1 is earlier in the list than (to the left of) arg2,
;then the offset value is positive

vList := "a,b,c,d,e"
Sort, vList, % "D, F MyCmpReportFunc"
return

MyCmpReportFunc(vText1, vText2, vOffset)
{
	MsgBox, % vText1 " " vText2 " " vOffset
}

;==================================================

;SORT: CUSTOM COMPARISON FUNCTIONS (UNSTABLE V. STABLE SORT)

;unstable v. stable sort:

;example from the documentation (unstable sort):

MyVar := "def`nabc`nmno`nFGH`nco-op`ncoop`ncop`ncon`n"
Sort, MyVar, F StringSort
StringSort(a1, a2)
{
    return a1 > a2 ? 1 : a1 < a2 ? -1 : 0  ; Sorts alphabetically based on the setting of StringCaseSense.
}

;example from the documentation (edited) (stable sort):

MyVar := "def`nabc`nmno`nFGH`nco-op`ncoop`ncop`ncon`n"
Sort, MyVar, F StringSort
StringSortStable(a1, a2, offset)
{
    return a1 > a2 ? 1 : a1 < a2 ? -1 : -offset  ; Sorts alphabetically based on the setting of StringCaseSense.
}

;==================================================

;SORT: ALPHABETICAL (UNSTABLE)

vText := "q`nw`ne`nr`nt`ny"
Sort, vText
MsgBox, % vText

vText := "q,w,e,r,t,y"
Sort, vText, D,
MsgBox, % vText

;==================================================

;SORT: ALPHABETICAL (STABLE)

;sort: unstable
;ordinarily sorting by the Sort command is unstable,
;i.e. in a case insensitive sort:
;two items that are regarded as identical may be moved
vText := "A,a,a,a,a"
Loop 5
{
	Sort, vText, D,
	MsgBox, % vText
}

;sort: stable
vText := "A,a,a,a,a"
Loop 5
{
	Sort, vText, D, F JEE_SortCasIns
	MsgBox, % vText
}

JEE_SortCasIns(vTextA, vTextB, vOffset) ;for use with AHK's Sort command
{
	vSCS := A_StringCaseSense
	StringCaseSense, Off
	vRet := ("" vTextA) > ("" vTextB) ? 1 : ("" vTextA) < ("" vTextB) ? -1 : -vOffset
	StringCaseSense, % vSCS
	return vRet
}

;==================================================

;SORT: REVERSE LIST

vText := "a,b,c,d,e"
Sort, vText, D, F JEE_SortReverseList
MsgBox, % vText

JEE_SortReverseList(vTextA, vTextB, vOffset) ;for use with AHK's Sort command
{
	return vOffset
}

;==================================================

;SORT: BY NTH COLUMN

vText := " ;continuation section
(
1	q
2	w
3	e
4	r
5	t
6	y
)"

vJeeSortCol := 2
vJeeSortColDelim := "`t"
Sort, vText, F JEE_SortByColSpecifyDelim
MsgBox, % vText

JEE_SortByColSpecifyDelim(vTextA, vTextB, vOffset) ;for use with AHK's Sort command
{
	global vJeeSortCol
	global vJeeSortColDelim
	oTemp := StrSplit(vTextA, vJeeSortColDelim)
	vTextAX := oTemp[vJeeSortCol]
	oTemp := StrSplit(vTextB, vJeeSortColDelim)
	vTextBX := oTemp[vJeeSortCol]
	oTemp := ""
	vRet := ("" vTextAX) > ("" vTextBX) ? 1 : ("" vTextAX) < ("" vTextBX) ? -1 : -vOffset
	return vRet
}

;==================================================

;SORT: RECORD COMPARISONS

vText := "a,b,c,d,e"
;vText := "a,a,a,A,a"

vJeeSortLog := ""
Sort, vText, D, F JEE_SortCasInsLog
MsgBox, % vText
MsgBox, % vJeeSortLog

vJeeSortLog := ""
Sort, vText, D, F JEE_SortCasInsUnstableLog
MsgBox, % vText
MsgBox, % vJeeSortLog

JEE_SortCasInsLog(vTextA, vTextB, vOffset) ;for use with AHK's Sort command
{
	global vJeeSortLog
	vJeeSortLog .= vTextA "`t" vTextB "`r`n"
	vSCS := A_StringCaseSense
	StringCaseSense, Off
	vRet := ("" vTextA) > ("" vTextB) ? 1 : ("" vTextA) < ("" vTextB) ? -1 : -vOffset
	StringCaseSense, % vSCS
	return vRet
}

JEE_SortCasInsUnstableLog(vTextA, vTextB, vOffset) ;for use with AHK's Sort command
{
	global vJeeSortLog
	vJeeSortLog .= vTextA "`t" vTextB "`r`n"
	vSCS := A_StringCaseSense
	StringCaseSense, Off
	vRet := ("" vTextA) > ("" vTextB) ? 1 : ("" vTextA) < ("" vTextB) ? -1 : 0
	StringCaseSense, % vSCS
	return vRet
}

;==================================================

;SORT STRING: ALPHABETISE

;q:: ;alphabetise string (case insensitive)
vText := "HELLO world"
oArray := {}
Loop Parse, vText
	oArray[Ord(Format("{:L}", A_LoopField))] .= A_LoopField
vOutput := ""
VarSetCapacity(vOutput, StrLen(vText)*2)
for _, vValue in oArray
	vOutput .= vValue
MsgBox, % vOutput
return

;q:: ;alphabetise string (case sensitive)
vText := "HELLO world"
oArray := {}
Loop Parse, vText
	oArray[Ord(A_LoopField)] .= A_LoopField
vOutput := ""
VarSetCapacity(vOutput, StrLen(vText)*2)
for _, vValue in oArray
	vOutput .= vValue
MsgBox, % vOutput
return

;note: this could also be done like so:
;determine an unused character, to be a delimiter,
;put the delimiter between each character,
;sort the string,
;remove the delimiter

;==================================================

;SORT STRING: REVERSE

;q:: ;reverse string (via DllCall)
;note: _strrev/_wcsrev overwrite the string
vText := "hello world"
vOutput := vText
MsgBox, % vOutput
vFunc := "msvcrt\" (A_IsUnicode ? "_wcsrev" : "_strrev")
DllCall(vFunc, "Str",vOutput, "CDecl")
MsgBox, % vOutput
return

;q:: ;reverse string (via Loop)
vText := "hello world"
vLen := StrLen(vText)
vOutput := ""
VarSetCapacity(vOutput, vLen*2)
Loop % vLen
	vOutput .= SubStr(vText, vLen-A_Index+1, 1)
	;vOutput .= SubStr(vText, vLen--, 1) ;could use
MsgBox, % vOutput
return

;==================================================

;SORT STRING: SHUFFLE (RANDOMISE)

;q:: ;shuffle string (randomise string)
vText := "0123456789"
vOutput := ""
VarSetCapacity(vOutput, StrLen(vText)*2)
oArray := StrSplit(vText)
while oArray.Length()
{
	Random, vNum, 1, % oArray.Length()
	vOutput .= oArray.RemoveAt(vNum)
}
MsgBox, % vOutput
return

;(repeating the text above)
;note: this could also be done like so:
;determine an unused character, to be a delimiter,
;put the delimiter between each character,
;sort the string,
;remove the delimiter

;==================================================

;STRING/NUMERIC: COMPARE NUMERIC AS TEXT

vTextA := 1
vTextB := "01"

MsgBox, % (vTextA = vTextB)
MsgBox, % ("" vTextA = vTextB)

vTextA := 1
vTextB := 0x1

MsgBox, % (vTextA = vTextB)
MsgBox, % ("" vTextA = vTextB)

;==================================================

;STRING/NUMERIC: CHECK IF VARIABLE LOOKS LIKE STRING/NUMERIC

vTextA := 0x1
vTextB := "a"
;note: only works in AHK v1
MsgBox, % !(vTextA + 0 = "") ;1=numeric appearance/0=string appearance
MsgBox, % !(vTextB + 0 = "")

;==================================================

;STRING/NUMERIC: CHECK IF VARIABLE IS STRING/NUMERIC

v1 := 3
v2 := "3"

;(blank)=numeric/30=string
MsgBox, % ObjGetCapacity([v1], 1) ;(blank)
MsgBox, % ObjGetCapacity([v2], 1) ;30

;1=numeric/0=string
MsgBox, % !ObjGetCapacity([v1], 1) ;1
MsgBox, % !ObjGetCapacity([v2], 1) ;0

;0=numeric/1=string
MsgBox, % !!ObjGetCapacity([v1], 1) ;0
MsgBox, % !!ObjGetCapacity([v2], 1) ;1

;see also:
;Determine the difference between a number in a string and a number? - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=5&t=32869

;==================================================

;STRING/NUMERIC: IF VAR IS TYPE

;vList := "10,-10,10.0,0xA,UPPER,Title,lower,, ,`t,a1,19990101000000,19991313000000"
vList := "10,-10,+10,--10,++10,10.0,0.0,0.,.0,.,0x,A,xA,0xA,UPPER,Title,lower,, ,`t,A,a,1,Aa,A1,a1,Aa1,19990101000000,19991313000000"
vListType := "integer,float,number,digit,xdigit,alpha,upper,lower,alnum,space,time"
vOutput := ""
Loop Parse, vList, % ","
{
	vTemp := A_LoopField
	vTemp2 := ""
	Loop Parse, vListType, % ","
	{
		if vTemp is %A_LoopField%
			vTemp2 .= A_LoopField ","
	}
	;vOutput .= "[" vTemp "] " SubStr(vTemp2, 1, -1) "`r`n"
	vOutput .= RTrim("[" vTemp "] " SubStr(vTemp2, 1, -1)) "`r`n"
}
;Clipboard := vOutput
MsgBox, % vOutput

;see also:
;If Var is Type - Syntax & Usage | AutoHotkey
;https://autohotkey.com/docs/commands/IfIs.htm

;==================================================

;STRING/NUMERIC: GET CASE

;'if var is upper/lower' can be used to identify a string's case,
;here is some code for a custom 'StrGetCase' function

vList := "autohotkey,Autohotkey,AUTOHOTKEY,AutoHotkey"
vOutput := ""
Loop Parse, vList, % ","
{
	vTemp := A_LoopField
	vOutput .= StrGetCase(vTemp) "`t" vTemp "`r`n"
}
MsgBox, % vOutput

;note: lower/title/upper/mixed
StrGetCase(ByRef vText)
{
	if (vText == Format("{:L}", vText))
		return "L"
	else if (vText == Format("{:T}", vText))
		return "T"
	else if (vText == Format("{:U}", vText))
		return "U"
	else
		return "X"
}

;==================================================

;COMMAND STYLE (=) V. EXPRESSION STYLE (:=)

;note: AutoTrim can affect command-style variables

;various examples of command-style code (deprecated)
;v. expression-style code

var =
var := ""

var = a
var := "a"

var = 1
var := 1
var := "1"

var = % 1+1
var := 1+1

var3 = %var1%A%var2%B
var3 := var1 "A" var2 "B"
var3 := var1 . "A" . var2 . "B"

var1 = %var2%
var1 := var2

MsgBox, %var%
MsgBox, % var

MsgBox, %var1% %var2% %var3%
MsgBox, % var1 " " var2 " " var3

MsgBox, %var1%`,%var2%`,%var3%
MsgBox, % var1 "," var2 "," var3

if var contains %vList%
if var contains % vList

if var contains a,b,c
if var contains % "a,b,c"

FileAppend, %vOutput%`r`n, %vPath%
FileAppend, % vOutput "`r`n", % vPath

if A_TimeIdlePhysical > %vDuration%
if (A_TimeIdlePhysical > vDuration)

;append
var1 = %var1%%var2%
var1 .= var2
var1 := var1 var2
var1 := var1 . var2

;prepend
var1 = %var2%%var1%
var1 := var2 var1
var1 := var2 . var1

;no equivalent with command-style variables
var1 := "a", var2 := "b", var3 := "c"
var1 := var2 := var3 := "hello world"

;double deref
varname = var1
var1 = a
var2 = % %varname%
MsgBox, % var2

varname := "var1"
var1 := "a"
var2 := %varname%
MsgBox, % var2
MsgBox, % varname ;single deref: varname -> var1
MsgBox, % %varname% ;double deref: varname -> var1 -> a

;continuation sections
vText := " ;continuation section
(
 a
 b
 c
)"
MsgBox, % vText
;equivalent to: vText := " a`n b`n c"

vText = ;continuation section
(
 a
 b
 c
)
MsgBox, % vText
;equivalent to: vText = a`n b`n c

AutoTrim, Off
vText = ;continuation section
(
%A_Space%a
 b
 c
)
MsgBox, % vText
;equivalent to: vText = %A_Space%a`n b`n c

;if (in AHK v1, the style without parentheses can sometimes give surprising results)
var := "a"
if var = a
	MsgBox, % "y1"
if (var = "a")
	MsgBox, % "y2"

var := 1
if var = 1
	MsgBox, % "y1"
if (var = 1)
	MsgBox, % "y2"
if (var = "1")
	MsgBox, % "y3"

var1 := "a"
var2 := "a"
if var1 = %var2%
	MsgBox, % "y1"
if (var1 = var2)
	MsgBox, % "y2"

if ("a" = "a")
	MsgBox, % "y1"

;==================================================

;SENDINPUT (ESCAPING CHARACTERS)

;literal text: !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

;command style, Raw on, escaped chars: `"
SendInput, % "{Raw}!""#$%&'()*+,-./:;<=>?@[\]^_``{|}~`n"

;command style, Raw off, escaped chars: `" +^#!{}
SendInput, % "{!}""{#}$%&'()*{+},-./:;<=>?@[\]{^}_``{{}|{}}~`n"

;expression style, Raw on, escaped chars: `%
SendInput, {Raw}!"#$`%&'()*+,-./:;<=>?@[\]^_``{|}~`n

;expression style, Raw off, escaped chars: `% +^#!{}
SendInput, {!}"{#}$`%&'()*{+},-./:;<=>?@[\]{^}_``{{}|{}}~`n

;note: {Text} mode treats `r`n as `n

;==================================================
==================================================

> FREQUENCY COUNT / REMOVE DUPLICATES

See:
jeeswg's objects tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=29232

These sections:
FREQUENCY COUNT (CASE INSENSITIVE) (SORT)
FREQUENCY COUNT (CASE INSENSITIVE) (MAINTAIN ORDER)
FREQUENCY COUNT (CASE SENSITIVE) (MAINTAIN ORDER/SORT)
REMOVE DUPLICATES (CASE INSENSITIVE) (MAINTAIN ORDER/SORT)
REMOVE DUPLICATES (CASE SENSITIVE) (MAINTAIN ORDER/SORT)

> LINKS (KEY STRING FUNCTIONS/COMMANDS):

InStr() - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/InStr.htm
SubStr() - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/SubStr.htm
StrReplace() / StringReplace - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/StringReplace.htm
StrSplit() / StringSplit - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/StringSplit.htm
StringLower / StringUpper - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/StringLower.htm
StrLen() / StringLen - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/StringLen.htm
Chr() - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/Chr.htm
Ord() - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/Ord.htm

[operators/pseudo-operators]
[if var [not] in/contains MatchList]
If Var in/contains MatchList - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/IfIn.htm
[if var is [not] type]
If Var is Type - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/IfIs.htm

[continuation sections]
Scripts - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/Scripts.htm#continuation

> LINKS (FURTHER STRING FUNCTIONS/COMMANDS):

Alphabetical Command and Function Index | AutoHotkey
https://autohotkey.com/docs/commands/
Format() - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/Format.htm
Loop - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/Loop.htm
Arrays - Definition & Usage | AutoHotkey
https://autohotkey.com/docs/misc/Arrays.htm
RegExMatch() - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/RegExMatch.htm
RegExReplace() - Syntax & Usage | AutoHotkey
https://autohotkey.com/docs/commands/RegExReplace.htm

==================================================

> LINKS (PROTOTYPE FUNCTIONS FROM WISH LIST 2.0)

[all from 'PROTOTYPE FUNCTIONS', here:]
Wish List 2.0 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=13&t=36789

Sort function + extra features - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=50431

[FileGetPart/(PathGetPart)]
file get part (SplitPath alternative/short-form/long-form/correct case) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=47709

[StrHatch/StrDelimit][add a string every n characters]
[StrPad][add characters to the start/end of a string]
slice string, pad characters - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=47079

[Combin/Permut]
combinations and permutations (and anagrams) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=34244&p=158871#p158871

==================================================

> LINKS

jeeswg's characters tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=26486
jeeswg's RegEx tutorial (RegExMatch, RegExReplace) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=28031
jeeswg's sort algorithms mini-tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=74&t=64447

jeeswg's mathematics tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=64545
jeeswg's dates tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=65544
jeeswg's objects tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=29232

text/list/table functions - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=27023
string hacks - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=34390
[Caesar cipher]
Change letters on a string - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=48704&p=217168#p217168
six case-insensitive comparison functions - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=83&t=66499

[unexpected behaviour, since AHK v1 stores numbers *and* strings for variables]
Object.Push is eating initial zeros - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=64932&p=278890#p278890
if statement influences floating point number - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=14&t=53498&p=232320#p232320

[combine columns]
How to combine text of two seperate files in a new file - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=26127
[swap columns]
how to change the column A with the column B in Text file? - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=27721
Match entire line - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=32602
[choose/case]
Selecting a tab in another program - Page 2 - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=5&t=32717&p=152641#p152641

AutoHotkey Expression Examples: "" %% () and all that
http://www.daviddeley.com/autohotkey/xprxmp/autohotkey_expression_examples.htm

==================================================

> NEW SECTIONS
Spoiler

==================================================
Last edited by jeeswg on 28 Aug 2019, 10:52, edited 39 times in total.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: jeeswg's strings tutorial

12 Jun 2017, 00:54

Generally speaking you advocate several techniques that I would not encourage people to use/learn.
Recommends AHK Studio
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: jeeswg's strings tutorial

12 Jun 2017, 01:22

Please elaborate. I have been quite careful in what I have written here, so it is difficult to imagine what you could have in mind.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: jeeswg's strings tutorial

21 Jun 2017, 05:18

Thorough work, might be useful for those who like to learn by example. :thumbup:
Missing: StrPut and StrGet.

Cheers.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: jeeswg's strings tutorial

21 Jun 2017, 10:13

@nnnik: Any more details, or even an alternative strings tutorial are quite welcome. I do like to consider all viewpoints.

@Helgef: Cheers. I did include a little on StrGet and StrPut in the link below, I had thought of it more of an 'encoding' issue than a strings issue, but I will add it to this tutorial in one or two places, e.g. at least in a list of string functions/commands and also re. overwriting text, which StrPut is useful for.
jeeswg's characters tutorial - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=7&t=26486
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
User avatar
nnnik
Posts: 4500
Joined: 30 Sep 2013, 01:01
Location: Germany

Re: jeeswg's strings tutorial

21 Jun 2017, 12:02

Oh I forgot
I only scammed the contents last time but it comes down to:

Code: Select all

If var [not] in/contains value1,value2,...
https://autohotkey.com/docs/commands/IfIn.htm
If var is [not] type
https://autohotkey.com/docs/commands/IfIs.htm
Recommends AHK Studio
Vusami
Posts: 2
Joined: 12 Aug 2020, 01:40

Re: jeeswg's strings tutorial

12 Aug 2020, 01:42

Thank you very much for the tutorial. It is very helpful.

Return to “Tutorials (v1)”

Who is online

Users browsing this forum: No registered users and 34 guests