Jump to content

czardas

MVPs
  • Posts

    10,103
  • Joined

  • Last visited

  • Days Won

    68

Reputation Activity

  1. Thanks
    czardas got a reaction from TimRude in CSVSplit   
    This UDF introduces one or two small tweaks to the functions I posted in Snippet Dump (nothing major). Terminating the CSV with a break is now the default behaviour (although I think it's just a lazy convention). The inclusion of a new function _ArrayToSubItemCSV() got me excited. Thanks for an awesome idea Chimaera. I have included the option to sort the returned CSV formatted strings ascending using any column.
    ;
    #include-once #include <Array.au3> ; #INDEX# ======================================================================================================================= ; Title .........: CSVSplit ; AutoIt Version : 3.3.8.1 ; Language ......: English ; Description ...: CSV related functions ; Notes .........: CSV format does not have a general standard format, however these functions allow some flexibility. ;                  The default behaviour of the functions applies to the most common formats used in practice. ; Author(s) .....: czardas ; =============================================================================================================================== ; #CURRENT# ===================================================================================================================== ;_ArrayToCSV ;_ArrayToSubItemCSV ;_CSVSplit ; =============================================================================================================================== ; #INTERNAL_USE_ONLY#============================================================================================================ ; __GetSubstitute ; =============================================================================================================================== ; #FUNCTION# ==================================================================================================================== ; Name...........: _ArrayToCSV ; Description ...: Converts a two dimensional array to CSV format ; Syntax.........: _ArrayToCSV ( $aArray [, $sDelim [, $sNewLine [, $bFinalBreak ]]] ) ; Parameters ....: $aArray      - The array to convert ;                  $sDelim      - Optional - Delimiter set to comma by default (see comments) ;                  $sNewLine    - Optional - New Line set to @LF by default (see comments) ;                  $bFinalBreak - Set to true in accordance with common practice => CSV Line termination ; Return values .: Success  - Returns a string in CSV format ;                  Failure  - Sets @error to: ;                 |@error = 1 - First parameter is not a valid array ;                 |@error = 2 - Second parameter is not a valid string ;                 |@error = 3 - Third parameter is not a valid string ;                 |@error = 4 - 2nd and 3rd parameters must be different characters ; Author ........: czardas ; Comments ......; One dimensional arrays are returned as multiline text (without delimiters) ;                ; Some users may need to set the second parameter to semicolon to return the prefered CSV format ;                ; To convert to TSV use @TAB for the second parameter ;                ; Some users may wish to set the third parameter to @CRLF ; =============================================================================================================================== Func _ArrayToCSV($aArray, $sDelim = Default, $sNewLine = Default, $bFinalBreak = True)     If Not IsArray($aArray) Or Ubound($aArray, 0) > 2 Or Ubound($aArray) = 0 Then Return SetError(1, 0 ,"")     If $sDelim = Default Then $sDelim = ","     If $sDelim = "" Then Return SetError(2, 0 ,"")     If $sNewLine = Default Then $sNewLine = @LF     If $sNewLine = "" Then Return SetError(3, 0 ,"")     If $sDelim = $sNewLine Then Return SetError(4, 0, "")     Local $iRows = UBound($aArray), $sString = ""     If Ubound($aArray, 0) = 2 Then ; Check if the array has two dimensions         Local $iCols = UBound($aArray, 2)         For $i = 0 To $iRows -1             For $j = 0 To $iCols -1                 If StringRegExp($aArray[$i][$j], '["\r\n' & $sDelim & ']') Then                     $aArray[$i][$j] = '"' & StringReplace($aArray[$i][$j], '"', '""') & '"'                 EndIf                 $sString &= $aArray[$i][$j] & $sDelim             Next             $sString = StringTrimRight($sString, StringLen($sDelim)) & $sNewLine         Next     Else ; The delimiter is not needed         For $i = 0 To $iRows -1             If StringRegExp($aArray[$i], '["\r\n' & $sDelim & ']') Then                 $aArray[$i] = '"' & StringReplace($aArray[$i], '"', '""') & '"'             EndIf             $sString &= $aArray[$i] & $sNewLine         Next     EndIf     If Not $bFinalBreak Then $sString = StringTrimRight($sString, StringLen($sNewLine)) ; Delete any newline characters added to the end of the string     Return $sString EndFunc ;==> _ArrayToCSV ; #FUNCTION# ==================================================================================================================== ; Name...........: _ArrayToSubItemCSV ; Description ...: Converts an array to multiple CSV formated strings based on the content of the selected column ; Syntax.........: _ArrayToSubItemCSV($aCSV, $iCol [, $sDelim [, $bHeaders [, $iSortCol [, $bAlphaSort ]]]]) ; Parameters ....: $aCSV       - The array to parse ;                  $iCol       - Array column used to search for unique content ;                  $sDelim     - Optional - Delimiter set to comma by default ;                  $bHeaders   - Include csv column headers - Default = False ;                  $iSortCol   - The column to sort on for each new CSV (sorts ascending) - Default = False ;                  $bAlphaSort - If set to true, sorting will be faster but numbers won't always appear in order of magnitude. ; Return values .: Success   - Returns a two dimensional array - col 0 = subitem name, col 1 = CSV data ;                  Failure   - Returns an empty string and sets @error to: ;                 |@error = 1 - First parameter is not a 2D array ;                 |@error = 2 - Nothing to parse ;                 |@error = 3 - Invalid second parameter Column number ;                 |@error = 4 - Invalid third parameter - Delimiter is an empty string ;                 |@error = 5 - Invalid fourth parameter - Sort Column number is out of range ; Author ........: czardas ; Comments ......; @CRLF is used for line breaks in the returned array of CSV strings. ;                ; Data in the sorting column is automatically assumed to contain numeric values. ;                ; Setting $iSortCol equal to $iCol will return csv rows in their original ordered sequence. ; =============================================================================================================================== Func _ArrayToSubItemCSV($aCSV, $iCol, $sDelim = Default, $bHeaders = Default, $iSortCol = Default, $bAlphaSort = Default)     If Not IsArray($aCSV) Or UBound($aCSV, 0) <> 2 Then Return SetError(1, 0, "") ; Not a 2D array     Local $iBound = UBound($aCSV), $iNumCols = UBound($aCSV, 2)     If $iBound < 2 Then Return SetError(2, 0, "") ; Nothing to parse     If IsInt($iCol) = 0 Or $iCol < 0 Or $iCol > $iNumCols -1 Then Return SetError(3, 0, "") ; $iCol is out of range     If $sDelim = Default Then $sDelim = ","     If $sDelim = "" Then Return SetError(4, 0, "") ; Delimiter can not be an empty string     If $bHeaders = Default Then $bHeaders = False     If $iSortCol = Default Or $iSortCol == False Then $iSortCol = -1     If IsInt($iSortCol) = 0 Or $iSortCol < -1 Or $iSortCol > $iNumCols -1 Then Return SetError(5, 0, "") ; $iSortCol is out of range     If $bAlphaSort = Default Then $bAlphaSort = False     Local $iStart = 0     If $bHeaders Then         If $iBound = 2 Then Return SetError(2, 0, "") ; Nothing to parse         $iStart = 1     EndIf     Local $sTestItem, $iNewCol = 0     If $iSortCol <> -1 And ($bAlphaSort = False Or $iSortCol = $iCol) Then ; In this case we need an extra Column for sorting         ReDim $aCSV [$iBound][$iNumCols +1]         ; Populate column         If $iSortCol = $iCol Then             For $i = $iStart To $iBound -1                 $aCSV[$i][$iNumCols] = $i             Next         Else             For $i = $iStart To $iBound -1                 $sTestItem = StringRegExpReplace($aCSV[$i][$iSortCol], "\A\h+", "") ; Remove leading horizontal WS                 If StringIsInt($sTestItem) Or StringIsFloat($sTestItem) Then                     $aCSV[$i][$iNumCols] = Number($sTestItem)                 Else                     $aCSV[$i][$iNumCols] = $aCSV[$i][$iSortCol]                 EndIf             Next         EndIf         $iNewCol = 1         $iSortCol = $iNumCols     EndIf     _ArraySort($aCSV, 0, $iStart, 0, $iCol) ; Sort on the selected column     Local $aSubItemCSV[$iBound][2], $iItems = 0, $aTempCSV[1][$iNumCols + $iNewCol], $iTempIndex     $sTestItem = Not $aCSV[$iBound -1][$iCol]     For $i = $iBound -1 To $iStart Step -1         If $sTestItem <> $aCSV[$i][$iCol] Then ; Start a new csv instance             If $iItems > 0 Then ; Write to main array                 ReDim $aTempCSV[$iTempIndex][$iNumCols + $iNewCol]                 If $iSortCol <> -1 Then _ArraySort($aTempCSV, 0, $iStart, 0, $iSortCol)                 If $iNewCol Then ReDim $aTempCSV[$iTempIndex][$iNumCols]                 $aSubItemCSV[$iItems -1][0] = $sTestItem                 $aSubItemCSV[$iItems -1][1] = _ArrayToCSV($aTempCSV, $sDelim, @CRLF)             EndIf             ReDim $aTempCSV[$iBound][$iNumCols + $iNewCol] ; Create new csv template             $iTempIndex = 0             $sTestItem = $aCSV[$i][$iCol]             If $bHeaders Then                 For $j = 0 To $iNumCols -1                     $aTempCSV[0][$j] = $aCSV[0][$j]                 Next                 $iTempIndex = 1             EndIf             $iItems += 1         EndIf         For $j = 0 To $iNumCols + $iNewCol -1 ; Continue writing to csv             $aTempCSV[$iTempIndex][$j] = $aCSV[$i][$j]         Next         $iTempIndex += 1     Next     ReDim $aTempCSV[$iTempIndex][$iNumCols + $iNewCol]     If $iSortCol <> -1 Then _ArraySort($aTempCSV, 0, $iStart, 0, $iSortCol)     If $iNewCol Then ReDim $aTempCSV[$iTempIndex][$iNumCols]     $aSubItemCSV[$iItems -1][0] = $sTestItem     $aSubItemCSV[$iItems -1][1] = _ArrayToCSV($aTempCSV, $sDelim, @CRLF)     ReDim $aSubItemCSV[$iItems][2]     Return $aSubItemCSV EndFunc ;==> _ArrayToSubItemCSV ; #FUNCTION# ==================================================================================================================== ; Name...........: _CSVSplit ; Description ...: Converts a string in CSV format to a two dimensional array (see comments) ; Syntax.........: CSVSplit ( $aArray [, $sDelim ] ) ; Parameters ....: $aArray  - The array to convert ;                  $sDelim  - Optional - Delimiter set to comma by default (see 2nd comment) ; Return values .: Success  - Returns a two dimensional array or a one dimensional array (see 1st comment) ;                  Failure  - Sets @error to: ;                 |@error = 1 - First parameter is not a valid string ;                 |@error = 2 - Second parameter is not a valid string ;                 |@error = 3 - Could not find suitable delimiter replacements ; Author ........: czardas ; Comments ......; Returns a one dimensional array if the input string does not contain the delimiter string ;                ; Some CSV formats use semicolon as a delimiter instead of a comma ;                ; Set the second parameter to @TAB To convert to TSV ; =============================================================================================================================== Func _CSVSplit($string, $sDelim = ",") ; Parses csv string input and returns a one or two dimensional array     If Not IsString($string) Or $string = "" Then Return SetError(1, 0, 0) ; Invalid string     If Not IsString($sDelim) Or $sDelim = "" Then Return SetError(2, 0, 0) ; Invalid string     $string = StringRegExpReplace($string, "[\r\n]+\z", "") ; [Line Added] Remove training breaks     Local $iOverride = 63743, $asDelim[3] ; $asDelim => replacements for comma, new line and double quote     For $i = 0 To 2         $asDelim[$i] = __GetSubstitute($string, $iOverride) ; Choose a suitable substitution character         If @error Then Return SetError(3, 0, 0) ; String contains too many unsuitable characters     Next     $iOverride = 0     Local $aArray = StringRegExp($string, '\A[^"]+|("+[^"]+)|"+\z', 3) ; Split string using double quotes delim - largest match     $string = ""     Local $iBound = UBound($aArray)     For $i = 0 To $iBound -1         $iOverride += StringInStr($aArray[$i], '"', 0, -1) ; Increment by the number of adjacent double quotes per element         If Mod ($iOverride +2, 2) = 0 Then ; Acts as an on/off switch             $aArray[$i] = StringReplace($aArray[$i], $sDelim, $asDelim[0]) ; Replace comma delimeters             $aArray[$i] = StringRegExpReplace($aArray[$i], "(\r\n)|[\r\n]", $asDelim[1]) ; Replace new line delimeters         EndIf         $aArray[$i] = StringReplace($aArray[$i], '""', $asDelim[2]) ; Replace double quote pairs         $aArray[$i] = StringReplace($aArray[$i], '"', '') ; Delete enclosing double quotes - not paired         $aArray[$i] = StringReplace($aArray[$i], $asDelim[2], '"') ; Reintroduce double quote pairs as single characters         $string &= $aArray[$i] ; Rebuild the string, which includes two different delimiters     Next     $iOverride = 0     $aArray = StringSplit($string, $asDelim[1], 2) ; Split to get rows     $iBound = UBound($aArray)     Local $aCSV[$iBound][2], $aTemp     For $i = 0 To $iBound -1         $aTemp = StringSplit($aArray[$i], $asDelim[0]) ; Split to get row items         If Not @error Then             If $aTemp[0] > $iOverride Then                 $iOverride = $aTemp[0]                 ReDim $aCSV[$iBound][$iOverride] ; Add columns to accomodate more items             EndIf         EndIf         For $j = 1 To $aTemp[0]             If StringLen($aTemp[$j]) Then                 If Not StringRegExp($aTemp[$j], '[^"]') Then ; Field only contains double quotes                     $aTemp[$j] = StringTrimLeft($aTemp[$j], 1) ; Delete enclosing double quote single char                 EndIf                 $aCSV[$i][$j -1] = $aTemp[$j] ; Populate each row             EndIf         Next     Next     If $iOverride > 1 Then         Return $aCSV ; Multiple Columns     Else         For $i = 0 To $iBound -1             If StringLen($aArray[$i]) And (Not StringRegExp($aArray[$i], '[^"]')) Then ; Only contains double quotes                 $aArray[$i] = StringTrimLeft($aArray[$i], 1) ; Delete enclosing double quote single char             EndIf         Next         Return $aArray ; Single column     EndIf EndFunc ;==> _CSVSplit ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name...........: __GetSubstitute ; Description ...: Searches for a character to be used for substitution, ie one not contained within the input string ; Syntax.........: __GetSubstitute($string, ByRef $iCountdown) ; Parameters ....: $string   - The string of characters to avoid ;                  $iCountdown - The first code point to begin checking ; Return values .: Success   - Returns a suitable substitution character not found within the first parameter ;                  Failure   - Sets @error to 1 => No substitution character available ; Author ........: czardas ; Comments ......; This function is connected to the function _CSVSplit and was not intended for general use ;                  $iCountdown is returned ByRef to avoid selecting the same character on subsequent calls to this function ;                  Initially $iCountown should be passed with a value = 63743 ; =============================================================================================================================== Func __GetSubstitute($string, ByRef $iCountdown)     If $iCountdown < 57344 Then Return SetError(1, 0, "") ; Out of options     Local $sTestChar     For $i = $iCountdown To 57344 Step -1         $sTestChar = ChrW($i)         $iCountdown -= 1         If Not StringInStr($string, $sTestChar) Then             Return $sTestChar         EndIf     Next     Return SetError(1, 0, "") ; Out of options EndFunc ;==> __GetSubstitute ;
    See examples in post #8 below.
    ;
  2. Like
    czardas got a reaction from hudsonhock in CSVSplit   
    This UDF introduces one or two small tweaks to the functions I posted in Snippet Dump (nothing major). Terminating the CSV with a break is now the default behaviour (although I think it's just a lazy convention). The inclusion of a new function _ArrayToSubItemCSV() got me excited. Thanks for an awesome idea Chimaera. I have included the option to sort the returned CSV formatted strings ascending using any column.
    ;
    #include-once #include <Array.au3> ; #INDEX# ======================================================================================================================= ; Title .........: CSVSplit ; AutoIt Version : 3.3.8.1 ; Language ......: English ; Description ...: CSV related functions ; Notes .........: CSV format does not have a general standard format, however these functions allow some flexibility. ;                  The default behaviour of the functions applies to the most common formats used in practice. ; Author(s) .....: czardas ; =============================================================================================================================== ; #CURRENT# ===================================================================================================================== ;_ArrayToCSV ;_ArrayToSubItemCSV ;_CSVSplit ; =============================================================================================================================== ; #INTERNAL_USE_ONLY#============================================================================================================ ; __GetSubstitute ; =============================================================================================================================== ; #FUNCTION# ==================================================================================================================== ; Name...........: _ArrayToCSV ; Description ...: Converts a two dimensional array to CSV format ; Syntax.........: _ArrayToCSV ( $aArray [, $sDelim [, $sNewLine [, $bFinalBreak ]]] ) ; Parameters ....: $aArray      - The array to convert ;                  $sDelim      - Optional - Delimiter set to comma by default (see comments) ;                  $sNewLine    - Optional - New Line set to @LF by default (see comments) ;                  $bFinalBreak - Set to true in accordance with common practice => CSV Line termination ; Return values .: Success  - Returns a string in CSV format ;                  Failure  - Sets @error to: ;                 |@error = 1 - First parameter is not a valid array ;                 |@error = 2 - Second parameter is not a valid string ;                 |@error = 3 - Third parameter is not a valid string ;                 |@error = 4 - 2nd and 3rd parameters must be different characters ; Author ........: czardas ; Comments ......; One dimensional arrays are returned as multiline text (without delimiters) ;                ; Some users may need to set the second parameter to semicolon to return the prefered CSV format ;                ; To convert to TSV use @TAB for the second parameter ;                ; Some users may wish to set the third parameter to @CRLF ; =============================================================================================================================== Func _ArrayToCSV($aArray, $sDelim = Default, $sNewLine = Default, $bFinalBreak = True)     If Not IsArray($aArray) Or Ubound($aArray, 0) > 2 Or Ubound($aArray) = 0 Then Return SetError(1, 0 ,"")     If $sDelim = Default Then $sDelim = ","     If $sDelim = "" Then Return SetError(2, 0 ,"")     If $sNewLine = Default Then $sNewLine = @LF     If $sNewLine = "" Then Return SetError(3, 0 ,"")     If $sDelim = $sNewLine Then Return SetError(4, 0, "")     Local $iRows = UBound($aArray), $sString = ""     If Ubound($aArray, 0) = 2 Then ; Check if the array has two dimensions         Local $iCols = UBound($aArray, 2)         For $i = 0 To $iRows -1             For $j = 0 To $iCols -1                 If StringRegExp($aArray[$i][$j], '["\r\n' & $sDelim & ']') Then                     $aArray[$i][$j] = '"' & StringReplace($aArray[$i][$j], '"', '""') & '"'                 EndIf                 $sString &= $aArray[$i][$j] & $sDelim             Next             $sString = StringTrimRight($sString, StringLen($sDelim)) & $sNewLine         Next     Else ; The delimiter is not needed         For $i = 0 To $iRows -1             If StringRegExp($aArray[$i], '["\r\n' & $sDelim & ']') Then                 $aArray[$i] = '"' & StringReplace($aArray[$i], '"', '""') & '"'             EndIf             $sString &= $aArray[$i] & $sNewLine         Next     EndIf     If Not $bFinalBreak Then $sString = StringTrimRight($sString, StringLen($sNewLine)) ; Delete any newline characters added to the end of the string     Return $sString EndFunc ;==> _ArrayToCSV ; #FUNCTION# ==================================================================================================================== ; Name...........: _ArrayToSubItemCSV ; Description ...: Converts an array to multiple CSV formated strings based on the content of the selected column ; Syntax.........: _ArrayToSubItemCSV($aCSV, $iCol [, $sDelim [, $bHeaders [, $iSortCol [, $bAlphaSort ]]]]) ; Parameters ....: $aCSV       - The array to parse ;                  $iCol       - Array column used to search for unique content ;                  $sDelim     - Optional - Delimiter set to comma by default ;                  $bHeaders   - Include csv column headers - Default = False ;                  $iSortCol   - The column to sort on for each new CSV (sorts ascending) - Default = False ;                  $bAlphaSort - If set to true, sorting will be faster but numbers won't always appear in order of magnitude. ; Return values .: Success   - Returns a two dimensional array - col 0 = subitem name, col 1 = CSV data ;                  Failure   - Returns an empty string and sets @error to: ;                 |@error = 1 - First parameter is not a 2D array ;                 |@error = 2 - Nothing to parse ;                 |@error = 3 - Invalid second parameter Column number ;                 |@error = 4 - Invalid third parameter - Delimiter is an empty string ;                 |@error = 5 - Invalid fourth parameter - Sort Column number is out of range ; Author ........: czardas ; Comments ......; @CRLF is used for line breaks in the returned array of CSV strings. ;                ; Data in the sorting column is automatically assumed to contain numeric values. ;                ; Setting $iSortCol equal to $iCol will return csv rows in their original ordered sequence. ; =============================================================================================================================== Func _ArrayToSubItemCSV($aCSV, $iCol, $sDelim = Default, $bHeaders = Default, $iSortCol = Default, $bAlphaSort = Default)     If Not IsArray($aCSV) Or UBound($aCSV, 0) <> 2 Then Return SetError(1, 0, "") ; Not a 2D array     Local $iBound = UBound($aCSV), $iNumCols = UBound($aCSV, 2)     If $iBound < 2 Then Return SetError(2, 0, "") ; Nothing to parse     If IsInt($iCol) = 0 Or $iCol < 0 Or $iCol > $iNumCols -1 Then Return SetError(3, 0, "") ; $iCol is out of range     If $sDelim = Default Then $sDelim = ","     If $sDelim = "" Then Return SetError(4, 0, "") ; Delimiter can not be an empty string     If $bHeaders = Default Then $bHeaders = False     If $iSortCol = Default Or $iSortCol == False Then $iSortCol = -1     If IsInt($iSortCol) = 0 Or $iSortCol < -1 Or $iSortCol > $iNumCols -1 Then Return SetError(5, 0, "") ; $iSortCol is out of range     If $bAlphaSort = Default Then $bAlphaSort = False     Local $iStart = 0     If $bHeaders Then         If $iBound = 2 Then Return SetError(2, 0, "") ; Nothing to parse         $iStart = 1     EndIf     Local $sTestItem, $iNewCol = 0     If $iSortCol <> -1 And ($bAlphaSort = False Or $iSortCol = $iCol) Then ; In this case we need an extra Column for sorting         ReDim $aCSV [$iBound][$iNumCols +1]         ; Populate column         If $iSortCol = $iCol Then             For $i = $iStart To $iBound -1                 $aCSV[$i][$iNumCols] = $i             Next         Else             For $i = $iStart To $iBound -1                 $sTestItem = StringRegExpReplace($aCSV[$i][$iSortCol], "\A\h+", "") ; Remove leading horizontal WS                 If StringIsInt($sTestItem) Or StringIsFloat($sTestItem) Then                     $aCSV[$i][$iNumCols] = Number($sTestItem)                 Else                     $aCSV[$i][$iNumCols] = $aCSV[$i][$iSortCol]                 EndIf             Next         EndIf         $iNewCol = 1         $iSortCol = $iNumCols     EndIf     _ArraySort($aCSV, 0, $iStart, 0, $iCol) ; Sort on the selected column     Local $aSubItemCSV[$iBound][2], $iItems = 0, $aTempCSV[1][$iNumCols + $iNewCol], $iTempIndex     $sTestItem = Not $aCSV[$iBound -1][$iCol]     For $i = $iBound -1 To $iStart Step -1         If $sTestItem <> $aCSV[$i][$iCol] Then ; Start a new csv instance             If $iItems > 0 Then ; Write to main array                 ReDim $aTempCSV[$iTempIndex][$iNumCols + $iNewCol]                 If $iSortCol <> -1 Then _ArraySort($aTempCSV, 0, $iStart, 0, $iSortCol)                 If $iNewCol Then ReDim $aTempCSV[$iTempIndex][$iNumCols]                 $aSubItemCSV[$iItems -1][0] = $sTestItem                 $aSubItemCSV[$iItems -1][1] = _ArrayToCSV($aTempCSV, $sDelim, @CRLF)             EndIf             ReDim $aTempCSV[$iBound][$iNumCols + $iNewCol] ; Create new csv template             $iTempIndex = 0             $sTestItem = $aCSV[$i][$iCol]             If $bHeaders Then                 For $j = 0 To $iNumCols -1                     $aTempCSV[0][$j] = $aCSV[0][$j]                 Next                 $iTempIndex = 1             EndIf             $iItems += 1         EndIf         For $j = 0 To $iNumCols + $iNewCol -1 ; Continue writing to csv             $aTempCSV[$iTempIndex][$j] = $aCSV[$i][$j]         Next         $iTempIndex += 1     Next     ReDim $aTempCSV[$iTempIndex][$iNumCols + $iNewCol]     If $iSortCol <> -1 Then _ArraySort($aTempCSV, 0, $iStart, 0, $iSortCol)     If $iNewCol Then ReDim $aTempCSV[$iTempIndex][$iNumCols]     $aSubItemCSV[$iItems -1][0] = $sTestItem     $aSubItemCSV[$iItems -1][1] = _ArrayToCSV($aTempCSV, $sDelim, @CRLF)     ReDim $aSubItemCSV[$iItems][2]     Return $aSubItemCSV EndFunc ;==> _ArrayToSubItemCSV ; #FUNCTION# ==================================================================================================================== ; Name...........: _CSVSplit ; Description ...: Converts a string in CSV format to a two dimensional array (see comments) ; Syntax.........: CSVSplit ( $aArray [, $sDelim ] ) ; Parameters ....: $aArray  - The array to convert ;                  $sDelim  - Optional - Delimiter set to comma by default (see 2nd comment) ; Return values .: Success  - Returns a two dimensional array or a one dimensional array (see 1st comment) ;                  Failure  - Sets @error to: ;                 |@error = 1 - First parameter is not a valid string ;                 |@error = 2 - Second parameter is not a valid string ;                 |@error = 3 - Could not find suitable delimiter replacements ; Author ........: czardas ; Comments ......; Returns a one dimensional array if the input string does not contain the delimiter string ;                ; Some CSV formats use semicolon as a delimiter instead of a comma ;                ; Set the second parameter to @TAB To convert to TSV ; =============================================================================================================================== Func _CSVSplit($string, $sDelim = ",") ; Parses csv string input and returns a one or two dimensional array     If Not IsString($string) Or $string = "" Then Return SetError(1, 0, 0) ; Invalid string     If Not IsString($sDelim) Or $sDelim = "" Then Return SetError(2, 0, 0) ; Invalid string     $string = StringRegExpReplace($string, "[\r\n]+\z", "") ; [Line Added] Remove training breaks     Local $iOverride = 63743, $asDelim[3] ; $asDelim => replacements for comma, new line and double quote     For $i = 0 To 2         $asDelim[$i] = __GetSubstitute($string, $iOverride) ; Choose a suitable substitution character         If @error Then Return SetError(3, 0, 0) ; String contains too many unsuitable characters     Next     $iOverride = 0     Local $aArray = StringRegExp($string, '\A[^"]+|("+[^"]+)|"+\z', 3) ; Split string using double quotes delim - largest match     $string = ""     Local $iBound = UBound($aArray)     For $i = 0 To $iBound -1         $iOverride += StringInStr($aArray[$i], '"', 0, -1) ; Increment by the number of adjacent double quotes per element         If Mod ($iOverride +2, 2) = 0 Then ; Acts as an on/off switch             $aArray[$i] = StringReplace($aArray[$i], $sDelim, $asDelim[0]) ; Replace comma delimeters             $aArray[$i] = StringRegExpReplace($aArray[$i], "(\r\n)|[\r\n]", $asDelim[1]) ; Replace new line delimeters         EndIf         $aArray[$i] = StringReplace($aArray[$i], '""', $asDelim[2]) ; Replace double quote pairs         $aArray[$i] = StringReplace($aArray[$i], '"', '') ; Delete enclosing double quotes - not paired         $aArray[$i] = StringReplace($aArray[$i], $asDelim[2], '"') ; Reintroduce double quote pairs as single characters         $string &= $aArray[$i] ; Rebuild the string, which includes two different delimiters     Next     $iOverride = 0     $aArray = StringSplit($string, $asDelim[1], 2) ; Split to get rows     $iBound = UBound($aArray)     Local $aCSV[$iBound][2], $aTemp     For $i = 0 To $iBound -1         $aTemp = StringSplit($aArray[$i], $asDelim[0]) ; Split to get row items         If Not @error Then             If $aTemp[0] > $iOverride Then                 $iOverride = $aTemp[0]                 ReDim $aCSV[$iBound][$iOverride] ; Add columns to accomodate more items             EndIf         EndIf         For $j = 1 To $aTemp[0]             If StringLen($aTemp[$j]) Then                 If Not StringRegExp($aTemp[$j], '[^"]') Then ; Field only contains double quotes                     $aTemp[$j] = StringTrimLeft($aTemp[$j], 1) ; Delete enclosing double quote single char                 EndIf                 $aCSV[$i][$j -1] = $aTemp[$j] ; Populate each row             EndIf         Next     Next     If $iOverride > 1 Then         Return $aCSV ; Multiple Columns     Else         For $i = 0 To $iBound -1             If StringLen($aArray[$i]) And (Not StringRegExp($aArray[$i], '[^"]')) Then ; Only contains double quotes                 $aArray[$i] = StringTrimLeft($aArray[$i], 1) ; Delete enclosing double quote single char             EndIf         Next         Return $aArray ; Single column     EndIf EndFunc ;==> _CSVSplit ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name...........: __GetSubstitute ; Description ...: Searches for a character to be used for substitution, ie one not contained within the input string ; Syntax.........: __GetSubstitute($string, ByRef $iCountdown) ; Parameters ....: $string   - The string of characters to avoid ;                  $iCountdown - The first code point to begin checking ; Return values .: Success   - Returns a suitable substitution character not found within the first parameter ;                  Failure   - Sets @error to 1 => No substitution character available ; Author ........: czardas ; Comments ......; This function is connected to the function _CSVSplit and was not intended for general use ;                  $iCountdown is returned ByRef to avoid selecting the same character on subsequent calls to this function ;                  Initially $iCountown should be passed with a value = 63743 ; =============================================================================================================================== Func __GetSubstitute($string, ByRef $iCountdown)     If $iCountdown < 57344 Then Return SetError(1, 0, "") ; Out of options     Local $sTestChar     For $i = $iCountdown To 57344 Step -1         $sTestChar = ChrW($i)         $iCountdown -= 1         If Not StringInStr($string, $sTestChar) Then             Return $sTestChar         EndIf     Next     Return SetError(1, 0, "") ; Out of options EndFunc ;==> __GetSubstitute ;
    See examples in post #8 below.
    ;
  3. Like
    czardas got a reaction from Soa in Combine variable name   
    Yes: Look at Eval() in the help file.
  4. Thanks
    czardas got a reaction from SkysLastChance in check array empty   
    No it does not work well.
    To test the first element, you could use:
    If $aArray[0] == "" Then MsgBox(0, "Contents", "nothing") To test if the array actually contains elements, you should use:
    If UBound($aArray) = 0 Then MsgBox(0, "Contents", "zero elements") These examples will only work with 1D arrays.
  5. Like
    czardas got a reaction from Gulnar32 in Tic-Tac-Toe   
    You should not use the Global keyword inside functions - use Local instead. There are a few tic-tac-toe examples on this forum including one I made myself. Perhaps looking at these will give you some ideas on building your AI engine.
  6. Like
    czardas got a reaction from NassauSky in GUI design concepts.   
    Menu Bar Buttons
    Yesterday I stumbled upon an idea. I needed to add some buttons to my GUI, but didn't have enough room. There was plenty  of room left on the menu bar, so that's where I decided put them. The method works fine on my machine (surprisingly perhaps). Please report if you experience any issues running the script, or if you know of a better way to do this. Thanks.
    ;
    #include <GuiconstantsEx.au3> #include <StaticConstants.au3> _MenuButtons() Func _MenuButtons() Local $hGUI = GUICreate("Menu Button Example", 270, 100) GUICtrlCreateMenu(" ") ; A menu has to be created first (this seems logical). GUICtrlSetState(-1, $GUI_DISABLE) ; The above menu is treated as a spacer. Local $hToStart = GUICtrlCreateMenuItem(" << ", -1) ; Menu items are now placed on the menu bar. Local $hStepBack = GUICtrlCreateMenuItem(" < ", -1) Local $hPlay = GUICtrlCreateMenuItem(" P ", -1) Local $hStepForward = GUICtrlCreateMenuItem(" > ", -1) Local $hToEnd = GUICtrlCreateMenuItem(" >> ", -1) Local $hLabel = GUICtrlCreateLabel("", 10, 25, 250, 20, $SS_CENTER) GUISetState(@SW_SHOW) Local $msg While 1 $msg = GUIGetMsg() Switch $msg Case $GUI_EVENT_CLOSE ExitLoop Case $hToStart GUICtrlSetData($hLabel, "Back to Start") Case $hStepBack GUICtrlSetData($hLabel, "Step Back") Case $hPlay GUICtrlSetData($hLabel, "Play / Pause") Case $hStepForward GUICtrlSetData($hLabel, "Step Forward") Case $hToEnd GUICtrlSetData($hLabel, "Forward to End") EndSwitch WEnd EndFunc ;
    The help file states that when setting menuID to -1 with GUICtrlCreateMenuItem(), '-1 refers to the first level menu'. For me, it's difficult to interpret what this means. The controls just appear on the menu bar, which I never (quite) thought of as being a menu: since it has no specific handle (that I know of) and is not clickable by default.
    Also consider that if you remove the menu created in the example above, the code will no longer work as expected. I am at a loss to explain this. Testers needed!
  7. Like
    czardas reacted to pseakins in Why is _ArraySort so broken? Updated 1/9/22 630pm g2g   
    I wouldn't be so critical of a FREE and excellent product. @Jonhas no time to work on development and@Josdoes an excellent job of syncing the product with the latest updates to Scite apart from actively supporting this forum responding to many brain dead newbie queries.
  8. Like
    czardas reacted to ripdad in Trying to get a function to call in nanoseconds efficiently   
    This graphic shows what it looks like with the silence left in it at that speed.

     
  9. Like
    czardas reacted to Jos in Congrats to the New MVPs   
    Welcome to " the other side" 
    Jos
  10. Like
    czardas got a reaction from Professor_Bernd in Regular expression, strip quoted string literals   
    I wrote this a while ago. It seems to work. I haven't looked closely at your RegExp versions, but perhaps the code is worth posting.


    ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name...........: __StripOutStrings ; Description ...: Removes all strings wrapped in quotes from the code. ; Syntax.........: __StripOutStrings(ByRef $sCodeStr) ; Parameters ....: $sCodeStr - The string to alter. ; Return values .: [ByRef] the string after removing all strings wrapped in quotes ; Author ........: czardas ; Modified.......: ; Remarks .......: ; Related .......: None ; Link ..........: ; Example .......: No ; =============================================================================================================================== Func __StripOutStrings(ByRef $sCodeStr) Local $foo, $bar, $iStart, $iEnd, $aQuot While 1 $foo = StringInStr($sCodeStr, '"') ; Double quote $bar = StringInStr($sCodeStr, "'") ; Single quote If $foo = 0 And $bar = 0 Then ExitLoop If $foo > $bar And $bar > 0 Then $iStart = $bar $sQuot = "'" Else $iStart = $foo $sQuot = '"' EndIf $iEnd = StringInStr($sCodeStr, $sQuot, 0, 2) If $iEnd = 0 Then ; Error Code??? $sCodeStr = StringLeft($sCodeStr, $iStart -1) Else $sCodeStr = StringLeft($sCodeStr, $iStart -1) & " " & StringRight($sCodeStr, StringLen($sCodeStr) - $iEnd) EndIf WEnd EndFunc ;==> __StripOutStrings
  11. Like
    czardas got a reaction from TheDcoder in Why does a control need to have double of it's coordinates removed from the GUI height and width to have equal spacing?   
    I always drink a cup of coffee at times like this.
  12. Like
    czardas reacted to wisem2540 in Covid-19 response - Need Android code   
    Update on this.  In record time, Cisco released a custom version of the client for us that will auto-answer every call.
     
    Thanks - this can be closed.  Stay safe everyone
  13. Like
    czardas reacted to Melba23 in Covid-19 response - Need Android code   
    wisem2540,
    Excellent use of the forum. I will post a link to this thread in GH&S
    M23
  14. Like
    czardas reacted to wisem2540 in Covid-19 response - Need Android code   
    Hello all,
    I have some experience with Autoit, but almost none for Android. I work for a healthcare organization and as part of their Covid response, we are attempting to check on patients remotely.
    The idea here is that we would put android tablets in the rooms and use Cisco Jabber to call into the room. I would like to deliver an APK to the tablet that could detect an incoming call from Cisco Jabber, and then tap the answer button.
    I realize this is probably not the appropriate use for this forum, so I apologize in advance. However, if someone would be willing to help with this code, we could save lives.
    Thanks
  15. Thanks
    czardas reacted to pixelsearch in ListView export speed   
    @jpm : many thanks for your thoughts
    Today was a lucky day because I finally found the quickest way to import / export a CVS file, after it has been modified by a listview control. Here are the steps that led me here :
    1) Import phase : _CSVSplit() by Czardas, found here :
    I haven't found another function that is so reliable. We'll talk about its speed a bit later.
    Melba23 indicated here to Water that, from AutoIt v3.3.12.0, _FileReadToArray() could be used to parse a CSV file into an array.
    That's true... as long as there aren't any delimiters embedded in any field. But let's say your CSV file is comma delimited and an adress field contains a comma (for example 326, Highway Road) then _FileReadToArray() won't return any Array at all (error 3). Now good luck to find why, in this file full of commas delimiters. I'm not sure that the $FRTA_INTARRAYS flag (creating a 1D "array of arrays") will be enough to quickly debug what's happening in your file. Also better avoid embedded quotes in fields, or fields surrounded by quotes etc... They won't generate an error but will appear like this in the Array """Sacramento""" instead of "Sacramento"
    That's why _CSVSplit() seems imho a more reliable solution, as it handles all preceding situations.
    2) Export phase : _ArrayToCSV() by Czardas
    Unfortunately this export function, though reliable, takes a lot of time, which made me try something else :
    2) Export phase : _GUICtrlListView_SaveCSV() by Guinness, found here or there :
    This one is quick as it's based on surrounding each and every listview cell with a quote, also doubling any eventual quote found within the cell. Here is a part of guinness function :
    For $i = 0 To $iItemCount For $j = 0 To $iColumnCount $sReturn &= $sQuote & StringReplace(_GUICtrlListView_GetItemText($hListView, $i, $j), $sQuote, $sQuote & $sQuote, 0, 1) & $sQuote ... Next ... Next Using guinness export function, you end up with a CSV file where every field is surrounded with quotes but guess what ?
    When you want to import it back with Czardas _CSVSplit(), now it takes a very long time because of all the quotes found everywhere in guinness CSV file ! That's because Czardas _CSVSplit() is very fast... as long as most fields aren't surrounded by quotes.
    Facing that dilemma, I decided to rework a bit guinness function, not only to change _GUICtrlListView_GetItemText() with the "personal" function as we discussed yesterday, but to avoid having any field surrounded by quotes when it's not absolutely necessary (99% of all cases), here is the result :
    ;============================================ Func Export_csv() ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; trying this, instead of _GUICtrlListView_GetItemText() ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Local $nBegin = TimerInit() Local $iRows = _GUICtrlListView_GetItemCount($idListView) Local $iCols = _GUICtrlListView_GetColumnCount($idListView) Local $sWrite = "", $sText = "", $sQuote = chr(34), $sDoubleQuote = chr(34) & chr(34), $sPattern = "\R|" & $sDelimiter If $bHeaders1stRow = True Then ; 1st row contained headers in the imported CSV file $sWrite = $g_sHeadersReadLine & @CRLF EndIf Local $bUnicode = GUICtrlSendMsg($idListView, $LVM_GETUNICODEFORMAT, 0, 0) <> 0 Local $tBuffer, $pBuffer, $tItem, $pItem $tBuffer = DllStructCreate(($bUnicode ? "wchar Text[4096]" : "char Text[4096]")) $pBuffer = DllStructGetPtr($tBuffer) $tItem = DllStructCreate($tagLVITEM) $pItem = DllStructGetPtr($tItem) DllStructSetData($tItem, "TextMax", 4096) DllStructSetData($tItem, "Text", $pBuffer) For $i = 0 To $iRows -1 For $j = 0 To $iCols -1 DllStructSetData($tItem, "SubItem", $j) GUICtrlSendMsg($idListView, ($bUnicode ? $LVM_GETITEMTEXTW : $LVM_GETITEMTEXTA), $i, $pItem) $sText = DllStructGetData($tBuffer, "Text") If StringRegExp($sText, $sQuote) Then $sText = $sQuote & StringReplace($sText, $sQuote, $sDoubleQuote) & $sQuote ; 1st If +++ If StringRegExp($sText, $sPattern) And StringLeft($sText, 1) <> $sQuote Then $sText = $sQuote & $sText & $sQuote ; 2nd If +++ $sWrite &= $sText & (($j < $iCols - 1) ? $sDelimiter : @CRLF) Next Next Local $nEnd = TimerDiff($nBegin) ConsoleWrite($nEnd & @CRLF) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Local $sFileName_Export = FileSaveDialog(...) And now Czardas _CSVSplit() is very quick again when importing a CSV file created by guinness amended export function, while tests done this afternoon looked very promising (speed & reliability)
  16. Like
    czardas got a reaction from ARTESE in Roman (Again)   
    I wrote this Roman numeral conversion function a while ago, but never got round to writing the reverse function. This is a very simple example aimed more at beginners: sometimes you need a break from trying to figure out the more complicated stuff. I decided to post this mini UDF just in case someone might make use of it. The regexp in the _IsRoman() validation function might also be of interest to some of the more advanced members. Maybe it can be improved.
    #include-once ; #INDEX# ====================================================================================================================== ; Title .........: Roman ; AutoIt Version : 3.3.14.2 ; Language ......: English ; Description ...: Roman numeral conversion and validation. ; Notes .........: Roman numerals range between 1 and 3999. ; Author(s) .....: czardas ; ============================================================================================================================== ; #CURRENT# ==================================================================================================================== ; _IsRoman ; _Roman ; _RomanToDec ; ============================================================================================================================== ; #FUNCTION# =================================================================================================================== ; Name...........: _IsRoman ; Description ...: Tests if a string is a roman numeral. ; Syntax.........: _IsRoman($sRoman) ; Parameters.....; $sRoman - The string to test. ; Return values .: Returns True or False and sets @error to 1 if the parameter is an empty string. ; Author ........: czardas ; ============================================================================================================================== Func _IsRoman($sRoman) If $sRoman = '' Then Return SetError(1, 0, False) $sRoman = StringRegExpReplace($sRoman, '(?i)(\A)(M{0,3})?(CM|DC{0,3}|CD|C{0,3})?(XC|LX{0,3}|XL|X{0,3})?(IX|VI{0,3}|IV|I{0,3})?', '') Return $sRoman = '' EndFunc ;==>_IsRoman ; #FUNCTION# =================================================================================================================== ; Name...........: _Roman ; Description ...: Converts a decimal integer to a roman numeral. ; Syntax.........: _Roman($iInt) ; Parameters.....; $iInt - The integer to convert. ; Return values .: Returns the integer converted to a Roman numeral. ; Sets @error to 1 and returns an empty string if the integer is out of bounds. ; Author ........: czardas ; ============================================================================================================================== Func _Roman($iInt) If Not StringIsInt($iInt) Or $iInt > 3999 Or $iInt < 1 Then Return SetError(1, 0, "") $iInt = Int($iInt) ; in case the string contains leading zeros Local $aNumeral[10][4] = _ [["","","",""], _ ["M","C","X","I"], _ ["MM","CC","XX","II"], _ ["MMM","CCC","XXX","III"], _ ["","CD","XL","IV"], _ ["","D","L","V"], _ ["","DC","LX","VI"], _ ["","DCC","LXX","VII"], _ ["","DCCC","LXXX","VIII"], _ ["","CM","XC","IX"]] Local $iOffset = StringLen($iInt) -4, $sRoman = "", $aDecimal = StringSplit($iInt, "", 2) For $i = 0 To UBound($aDecimal) -1 $sRoman &= $aNumeral[$aDecimal[$i]][$i -$iOffset] Next Return $sRoman EndFunc ;==>_Roman ; #FUNCTION# =================================================================================================================== ; Name...........: _RomanToDec ; Description ...: Converts a roman numeral to a decimal integer. ; Syntax.........: _RomanToDec($sRoman) ; Parameters.....; $sRoman - The Roman numeral to convert. ; Return values .: Returns the Roman numeral converted to an integer. ; Sets @error to 1 and returns an empty string if the Roman numeral is invalid. ; Author ........: czardas ; ============================================================================================================================== Func _RomanToDec($sRoman) If Not _IsRoman($sRoman) Then Return SetError(1, 0, '') Local $aChar = StringSplit($sRoman, '') For $i = 1 To $aChar[0] Switch $aChar[$i] Case 'I' $aChar[$i] = 1 Case 'V' $aChar[$i] = 5 Case 'X' $aChar[$i] = 10 Case 'L' $aChar[$i] = 50 Case 'C' $aChar[$i] = 100 Case 'D' $aChar[$i] = 500 Case 'M' $aChar[$i] = 1000 EndSwitch Next Local $iCount = 0, $iCurr, $iNext For $i = 1 To $aChar[0] $iCurr = $aChar[$i] If $i < $aChar[0] Then $iNext = $aChar[$i +1] If $iNext > $iCurr Then $iCurr = $iNext - $iCurr $i += 1 ; skip the next element EndIf EndIf $iCount += $iCurr Next Return $iCount EndFunc ;==>_RomanToDec
    Simple Demo
    #include <Array.au3> #include 'Roman.au3' Local $aRoman[4000] = [3999] For $i = 1 To 3999 $aRoman[$i] = _Roman($i) Next _ArrayShuffle($aRoman, 1) ; shuffle the array starting from Row 1 _ArrayDisplay($aRoman, "Shuffled") RomanSort($aRoman, 1) ; sort the roman numerals by size _ArrayDisplay($aRoman, "Sorted") Func RomanSort(ByRef $aArray, $iStart = Default, $iStop = Default) If Not IsArray($aArray) Or UBound($aArray, 0) <> 1 Then Return SetError(1) ; simple 1D array demo $iStart = ($iStart = Default ? 0 : Int($iStart)) $iStop = ($iStop = Default ? UBound($aArray) -1 : Int($iStop)) If $iStart < 0 Or $iStart > $iStop Or $iStop > UBound($aArray) -1 Then Return SetError(2) ; out of bounds For $i = $iStart To $iStop If Not _IsRoman($aArray[$i]) Then Return SetError(3) ; Roman numerals only [other solutions are beyond the scope of this simple demo] $aArray[$i] = _RomanToDec($aArray[$i]) ; convert to decimal Next _ArraySort($aArray, 0, $iStart, $iStop) ; sort integers For $i = $iStart To $iStop $aArray[$i] = _Roman($aArray[$i]) ; convert back to Roman numerals Next EndFunc ;==> RomanSort  
  17. Like
    czardas reacted to pixelsearch in CSV file editor   
    Hi everybody
    The script below (901f) allows to wander easily through a listview, selecting any item or subitem by using the 4 direction keys. The Enter key is also managed and allows to fire an event (as double-click does)
    With the help of mikell  (many thanks !)  and after several tests based on 1000 rows & 6 columns, we succeeded to code a clear WM_NOTIFY function, which is simple (though solid) and should be reusable without any modification in other scripts dealing with basic listviews (we didn't use or check any particular style for the listview)
    Trapping the Enter key has been done by using a dummy control + Accelerators, though we spent the whole last week trapping it in another way, using Yashied's Wsp.dll (without any problem) . Finally we choosed the dummy control option... to have a smaller code.
    Version 901f (Nov 11, 2019) : the pic below shows how the selected subitem appears, with its specific background colour (light blue)

    Version 901f code :
    #include <GUIConstantsEx.au3> #include <GuiListView.au3> #include <WindowsConstants.au3> #include <WinAPIvkeysConstants.au3> Global $hGUI = GUICreate("Wandering through ListView (901f)", 460, 500) Global $idListView = GUICtrlCreateListView _ (" Col 0 | Col 1| Col 2| Col 3", 15, 60, 430, 400) Global $hListView = GuiCtrlGetHandle($idListView) For $iRow = 0 To 99 $sRow = StringFormat("%2s", $iRow) GUICtrlCreateListViewItem( _ "Row " & $sRow & " / Col 0 |" & _ "Row " & $sRow & " / Col 1 |" & _ "Row " & $sRow & " / Col 2 |" & _ "Row " & $sRow & " / Col 3", $idListView) Next Global $g_iColumnCount = _GUICtrlListView_GetColumnCount($idListView) -1 Global $g_iItem = -1, $g_iSubItem = -1 ; item/subitem selected in ListView control Global $idDummy_Dbl_Click = GUICtrlCreateDummy() Global $idDummy_Enter = GUICtrlCreateDummy() Global $aAccelKeys[1][2] = [["{ENTER}", $idDummy_Enter]] GUISetAccelerators($aAccelKeys) GUIRegisterMsg($WM_NOTIFY, "WM_NOTIFY") GUISetState(@SW_SHOW) While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE GUIDelete($hGUI) Exit Case $idDummy_Dbl_Click MsgBox($MB_TOPMOST, "Double-click activated cell", _ "Row " & $g_iItem & " / Col " & $g_iSubItem) Case $idDummy_Enter If _WinAPI_GetFocus() = $hListView And $g_iItem > -1 Then MsgBox($MB_TOPMOST, "Enter activated cell", _ "Row " & $g_iItem & " / Col " & $g_iSubItem) EndIf EndSwitch WEnd ;============================================ Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam) #forceref $hWnd, $iMsg, $wParam Local $tNMHDR, $hWndFrom, $iIDFrom, $iCode $tNMHDR = DllStructCreate($tagNMHDR, $lParam) $hWndFrom = DllStructGetData($tNMHDR, "hWndFrom") $iCode = DllStructGetData($tNMHDR, "Code") Static $bMouseDown = False, $bNotXP = Not (@OSVersion = "WIN_XP") Switch $hWndFrom Case $hListView Switch $iCode Case $NM_CUSTOMDRAW Local $tCustDraw = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam) Local $iDrawStage = DllStructGetData($tCustDraw, "dwDrawStage") If $iDrawStage = $CDDS_PREPAINT Then Return $CDRF_NOTIFYITEMDRAW If $iDrawStage = $CDDS_ITEMPREPAINT Then Return $CDRF_NOTIFYSUBITEMDRAW Local $iItem = DllStructGetData($tCustDraw, "dwItemSpec") Local $iSubItem = DllStructGetData($tCustDraw, "iSubItem") Local $iColor = 0xFF000000 ; this is $CLR_DEFAULT in ColorConstants.au3 If $iItem = $g_iItem And $iSubItem = $g_iSubItem Then $iColor = 0xFFFFC0 ; light blue for 1 subitem (BGR) EndIf DllStructSetData($tCustDraw, "clrTextBk", $iColor) Return $CDRF_NEWFONT Case $LVN_KEYDOWN If $bMouseDown Or $g_iItem = -1 Then Return 1 ; don't process Local $tInfo = DllStructCreate($tagNMLVKEYDOWN, $lParam) Local $iVK = DllStructGetData($tInfo, "VKey") Switch $iVK Case $VK_RIGHT If $g_iSubItem < $g_iColumnCount Then $g_iSubItem += 1 If $bNotXP Then _GUICtrlListView_RedrawItems($hListview, $g_iItem, $g_iItem) EndIf Case $VK_LEFT If $g_iSubItem > 0 Then $g_iSubItem -= 1 If $bNotXP Then _GUICtrlListView_RedrawItems($hListview, $g_iItem, $g_iItem) EndIf Case $VK_SPACE ; spacebar would select the whole row Return 1 EndSwitch Case $NM_RELEASEDCAPTURE $bMouseDown = True Local $iItemSave = $g_iItem Local $aHit = _GUICtrlListView_SubItemHitTest($hListView) $g_iItem = $aHit[0] $g_iSubItem = $aHit[1] If $g_iItem = -1 And $iItemSave > -1 Then _GUICtrlListView_RedrawItems($hListview, $iItemSave, $iItemSave) EndIf Case $LVN_ITEMCHANGED Local $tInfo = DllStructCreate($tagNMLISTVIEW, $lParam) Local $iNewState = DllStructGetData($tInfo, "NewState") Switch $iNewState Case BitOr($LVIS_FOCUSED, $LVIS_SELECTED) $g_iItem = DllStructGetData($tInfo, "Item") _GUICtrlListView_SetItemSelected($hListview, $g_iItem, False) EndSwitch Case $NM_CLICK, $NM_RCLICK $bMouseDown = False Case $NM_DBLCLK $bMouseDown = False If $g_iItem > -1 Then GUICtrlSendToDummy($idDummy_Dbl_Click) EndSwitch EndSwitch Return $GUI_RUNDEFMSG EndFunc ;==>WM_NOTIFY Version 901k (Dec 16, 2019)
    What started with a simple "wander through listview" has turned now to a functional CSV file editor, which can be useful to modify your CSV files with AutoIt :

    Here are the instructions to use the script, based on a CSV file starting like this :
    street,city,zip,state,beds,baths,sq__ft,type,sale_date,price,latitude,longitude 3526 HIGH ST,SACRAMENTO,95838,CA,2,1,836,Residential,Wed May 21 00:00:00 EDT 2008,59222,38.631913,-121.434879 51 OMAHA CT,SACRAMENTO,95823,CA,3,1,1167,Residential,Wed May 21 00:00:00 EDT 2008,68212,38.478902,-121.431028 ... 1) Import options : comma delimited (default)

    No need to change anything if your CSV is comma delimited (other options are Semicolon delimited, Tab delimited)
    2) Import options : First row = headers (default = checked)

    * Keep it checked if the 1st row of your imported file contains headers (that's the case in our example)
    * UNcheck it if the 1st row contains data, making listview headers appear like this :
    Col 0 | Col 1 | Col 2 ... 3) Import your CSV file :

    Only now the listview will be created dynamically. As soon as it is populated, GUI becomes resizable/maximizable, which can be helpful during modifications of a listview containing many columns.
    4) Selection color : light blue (default)

    You can change the selected cell background color by clicking the "Selection color" button : this will open Windows color picker.
    5) Editing a listview cell : done by Enter key (or double-click), this is how the edited cell will appear :

    * Please note that the edited background color (green in the pic) depends on each computer theme. It is not related to the selected background we discussed in 4)
    * Validate your modification with Enter key, or cancel the modification (revert) with Escape Key
    6) Edit Font size : 15 (default)

    15 was good in the precedent pic, the edited cell had its content "RIO LINDA" perfectly aligned with all other cells (on my computer).
    Here again, the font height required depends on each computer : if you want the edited font to be bigger (or smaller), just use the updown control.
    7) Chained Edit ? (default = No)

    * "No" => when you finish editing a cell (Enter key), the same cell stays selected.
    * "Horizontally" => If checked, edition will automatically continue with the cell on its right.
    * "Vertically" => If checked, edition will automatically continue with the cell below.
    This feature can be very useful when you modify cells of a whole colum (vertically) or cells by row (horizontally)
    8 )  Inserting a blank line (not in Gui) : press the "Ins" key :


    9) Deleting a line (not in Gui) : press the "Del" key :


    10) Export CSV file :

    Filename automatically suggested for export will be : Filename import & actual date & actual time, for example :
    Import name = "Sales Results.csv" => suggested Export name = "Sales Results_2019-12-16 16:00:59.csv"
    Version 901m (Dec 18, 2019)
    Yesterday, mikell suggested to import the csv file by dropping it directly into the GUI, good idea
    This new version 901m allows it. Now there are 2 ways to import the csv file :
    * Import button
    * Drag and drop into the large droppable zone, as shown in the pic below (this zone will be reused to create the listview at same coords)

    Version 901n (Dec 20, 2019)
    As t0nZ got tons of csv files, pipe "|" separated, here is a new version allowing this 4th separator

    Version 901p (Dec 25, 2019)
    New functionality : now you can drag headers to reorder columns. It may help some users who need it while editing their file.
    Exported CSV file will be saved according to the new columns order.

    Version 901r (Dec 29, 2019)
    New functionality : Numeric sort on any column (right click on column header)
    It is recommended to backup (export) your file before sorting, just in case you need a copy of it, unsorted.

    Version 901s (Dec 30, 2019)
    1 functionality added : String sort (right click on column header)
    Numeric sort is alright in most cases, but sometimes we also need a String sort like shown in the following picture. Both ways of sorting (numeric and string) are found in this new release.

    Version 901t (Jan 3, 2020)
    3 functionalities added  Rename Header , Insert Column , Delete Column  (right click on column header to display its context menu)

    Version 901u (Jan 6, 2020)
    1 functionality added : Natural sort. Thanks to jchd for the idea and Melba23 for his function ArrayMultiColSort() included in the script.
    Though this natural sort isn't fully implemented, it should work when numbers precede letters (see pic below or better, try it on the "street" column found in the downloadable csv test file below)
    Natural sort duration + listview update are fast, maybe because of the new function _BufferCreate() described here and now added to the script.

    Version 901w (Jan 10, 2020)
    Two functionalities added :
    1) Close File button, which allows to import other csv file(s) during the same session

    2) Import speed has been improved because the listview control is now populated directly by an Array and not anymore by GUICtrlCreateListViewItem()
    This explains why, in this version, there are no more Control id's for listview items, allowing to empty the listview content in a snap with this line of code :
    _SendMessage($g_hListView, $LVM_DELETEALLITEMS) That's what it took to add the Close File button and import several csv files during the same session, avoiding id leaks. Please report if any issue is encountered.
    Version 901x (Jan 14, 2020)
    One minor functionality added : number of rows is now displayed just under the listview, it may be handy sometimes.

    Other minor changes included (natural sort speed improved)
    Credits :
    Many thanks to Czardas for his function _CSVSplit() and guinness for his function _SaveCSV(), guys you did a great job.
    Thanks to Musashi : your suggestions and time passed on testing beta versions of the script, that was really helpful and challenging. Not sure I would have ended this script without your detailed reports.
    Mikell : the 1st step above (901f) that we wrote together, it all started from here. Your knowledge and kindness are legendary !
    Not forgetting all other persons who were inspiring : LarsJ, Melba23, jpm, that list could be endless...
    Download link : version 901x - Jan 14, 2020 (minor update on Jan 15)
    901x - CSV file editor.au3
    Test csv file (986 rows/12cols) :
    Sacramento real estate transactions.csv
  18. Like
    czardas got a reaction from ss26 in ArrayWorkshop   
    ArrayWorkshop is what I like to think of as the 'Leibnitzian' approach to multidimensional arrays: formulaic beyond 3 dimensions. Most of the functions allow you to target the dimension in which you want processing to be done. It shouldn't take too long to figure out how this works after you've run some examples. _ArrayAttach treats multidimensional arrays as if they were lego bricks - any array can be attached regardless of bounds or dimensions.
    #include-once ; #INDEX# ====================================================================================================================== ; Title .........: ArrayWorkshop [version 1.0.1] ; AutoIt Version : 3.3.14.2 ; Language ......: English ; Description ...: Multidimensional array functions. ; Notes .........: In this library, an array region is defined as follows: ; 1. items within a one dimensional array ; 2. lists within a two dimensional array (rows or columns) ; 3. tables within a three dimensional array ; 4. cuboidal areas within a four dimension array ; 5. four dimensional cuboids within a five dimensional array ; etc... ; To reduce bloat, several functions access part of the script referred to (in the comments) as 'Remote Loops'. ; Arrays containing zero elements are not supported and will cause functions to return an error. ; Limiting the number of dimensions to single digits was a practical decision to simplify syntax. ; Credit must go to fellow AutoIt forum members whose code or suggestions have been influential in the ; development of these functions: Jos van der Zande, jguinch, jchd, LazyCoder, Tylo, Ultima, Melba23, BrewManNH ; The above list is not exhaustive, nor are the names in any particular order. ; Author(s) .....: czardas ; ============================================================================================================================== ; #CURRENT# ==================================================================================================================== ; _ArrayAttach [limit = 9 dimensions] ; _ArraySortXD [limit = 9 dimensions] ; _ArrayTransform [limit = 9 dimensions] ; _ArrayUniqueXD [limit = 9 dimensions] ; _DeleteDimension [limit = 9 dimensions] ; _DeleteRegion [limit = 9 dimensions] ; _ExtractRegion [limit = 9 dimensions] ; _InsertRegion [limit = 9 dimensions] ; _PreDim [limit = 9 dimensions] ; _ReverseArray [limit = 9 dimensions] ; _SearchArray [limit = 9 dimensions] ; _ShuffleArray [limit = 9 dimensions] ; ============================================================================================================================== ; #INTERNAL_USE_ONLY#=========================================================================================================== ; __AcquireExponent, __CreateTrac, __ExtractVector, __FindExact, __FindExactCase, __FindString, __FindStringCase, __FindWord, ; __FindWordCase __FloodFunc, ___FloodXD, ___FormatNum, __GetBounds, __HiddenIndices, ___Search1D, ___NewArray, ___NumCompare, ; __QuickSort1D, __QuickSortXD, __ResetBounds, ___Reverse1D, __Separate1D, __Separate256, __SeparateXD, __Shuffle1D, ; __ShuffleXD , __TagSortSwap, __TagSortSwapXD ; ============================================================================================================================== #Au3Stripper_Off Global $g__ARRWSHOP_RESUME = True ; prevents certain processes from running when set to False [do not use in your script] Global Const $g__ARRWSHOP_SUB = ChrW(57344) ; [U+E000] ; #FUNCTION# =================================================================================================================== ; Name...........: _ArrayAttach ; Description ...: Joins two arrays together. ; Syntax.........: _ArrayAttach($aTarget, $aSource [, $iDimension = 1]) ; Parameters.....; $aTarget - [ByRef] Target array to which the source array (or data) will be concatenated. ; $aSource - The array to attach to the target. ; $iDimension - [Optional] Integer value - the dimension in which concatenation occurs. Default = 1st dimension ; Return values .: Success - Returns the modified array ByRef. ; Failure sets @error as follows: ; |@error = 1 $aTarget is not a valid array. ; |@error = 2 Dimension limit exceeded. ; |@error = 3 Arrays must contain at least one element. ; |@error = 4 Output array size exceeds AutoIt limits. ; Author ........: czardas ; Comments ......; This function is limited to arrays of up to nine dimensions. ; Extra dimensions are added to the target when: ; 1. the source array has more dimensions than the target [or] ; 2. the 3rd parameter ($iDimension) is greater than the number of dimensions available. ; ============================================================================================================================== Func _ArrayAttach(ByRef $aTarget, $aSource, $iDimension = 1) If Not IsArray($aTarget) Then Return SetError(1) ; the target must be an array Local $aBoundSrc[1] If Not IsArray($aSource) Then ; convert $aSource into an array $aBoundSrc[0] = $aSource ; use $aBoundSrc as a temporary array to preserve memory $aSource = $aBoundSrc ; conversion by assignment EndIf $aBoundSrc = __GetBounds($aSource, 9) ; get the bounds of the source array If @error Then Return SetError(3) ; $aSource must contain at least one element Local $aBoundTgt = __GetBounds($aTarget, 9) ; get the bounds of the target array If @error Then Return SetError(3) ; $aTarget must contain at least one element $iDimension = ($iDimension = Default) ? 1 : Int($iDimension) If $aBoundTgt[0] > 9 Or $aBoundSrc[0] > 9 Or $iDimension > 9 Or $iDimension < 1 Then Return SetError(2) ; dimension limit exceeded Local $iDim = ($aBoundSrc[0] > $aBoundTgt[0]) ? $aBoundSrc[0] : $aBoundTgt[0] ; minimum number of dimensions needed for the output If $iDimension > $iDim Then $iDim = $iDimension ; the specified dimension may not yet exist Local $aBoundNew[$iDim + 1] ; output bounds $aBoundNew[0] = $iDim ; element 0 contains the number of dimensions For $i = 1 To $iDim ; get the minimum bounds within all dimensions If $i <> $iDimension Then $aBoundNew[$i] = ($aBoundSrc[$i] > $aBoundTgt[$i]) ? $aBoundSrc[$i] : $aBoundTgt[$i] Else ; expansion within the explicit dimension is determined differently $aBoundNew[$i] = $aBoundSrc[$i] + $aBoundTgt[$i] ; add the number of sub-indices together EndIf $aBoundSrc[$i] -= 1 ; convert to the final index value in each dimension of the source array Next Local $iCount = 1 ; check output bounds remain within range For $i = 1 To $aBoundNew[0] $iCount *= $aBoundNew[$i] If $iCount > 16777216 Then Return SetError(4) ; output array size exceeds AutoIt limits Next _PreDim($aTarget, $iDim) ; add more dimensions if needed __ResetBounds($aTarget, $aBoundNew) ; ReDim the target to make space for more elements If $iDim = 1 Then ; [do not send to remote loop region] For $iRegion = $aBoundTgt[$iDimension] To $aBoundNew[$iDimension] - 1 ; to the new bounds of the [only] dimension $aTarget[$iRegion] = $aSource[$iRegion - $aBoundTgt[$iDimension]] Next Return EndIf Local $sTransfer = '$aSource' & __HiddenIndices($aBoundSrc[0], $iDimension), _ ; to access elements at their original indices $iFrom, $aFloodFill = __FloodFunc() $aBoundSrc[$iDimension] = 0 ; whichever loop this relates to must only run once on each encounter For $iRegion = $aBoundTgt[$iDimension] To $aBoundNew[$iDimension] - 1 ; to the new bounds of the specified dimension $iFrom = $iRegion - $aBoundTgt[$iDimension] ; adjusted to begin transfer from source element 0 $aFloodFill[$iDim]($aTarget, $aBoundSrc, $iDimension, $iRegion, $iFrom, $aSource, $sTransfer) ; flood the region Next EndFunc ;==>_ArrayAttach ; #FUNCTION# =================================================================================================================== ; Name...........: _ArraySortXD ; Description ...: Sorts multidimensional arrays according to miscellaneous criteria. ; Syntax.........: _ArraySortXD($aArray [, $iDimension = 1 [, $iAlgorithm = 0 [, $iEnd = -1 [, $1 = 0 [, $2 = 0 ], etc... ]]]] ; Parameters.....; $aArray - [ByRef] The array to sort. ; $iDimension - [Optional] Integer value - the dimension in which sorting occurs. Default = 1st dimension ; $iAlgorithm - [Optional] Integer value - defines the sorting criteria. Default = lexical [see comments] ; $iEnd - [Optional] Integer value - final index to stop sorting (within the explicit dimension). Default = -1 ; $1, $2, $3, $4, $5, $6, $7, $8, $9 - [Optional] - start sub-indices within each dimension. [see comments] ; Return values .: Success - Returns the modified array ByRef. ; Failure sets @error as follows: ; |@error = 1 The first parameter is not a valid array. ; |@error = 2 Array does not contain any elements. ; |@error = 3 Dimension does not exist. ; |@error = 4 Out of range start parameter detected. ; |@error = 5 Out of range $iEnd value detected. ; |@error = 6 Bad algorithm detected. ; Author ........: czardas ; Comments ......; The algorithm parameter is binary flag. You can combine any of the following values: ; $iAlgorithm = 0 - alphabetical (lexical - applies to all items including numbers) ; $iAlgorithm = 1 - descending (applies to sorted items only) ; $iAlgorithm = 2 - numeric (applies to numbers only) ; $iAlgorithm = 4 - alphanumeric (applies to all items - overrides flag 2) ; $iAlgorithm = 256 - sort decimal strings by magnitude (combine with flags 2 and 4) ; $iAlgorithm = 512 - maintain original sequence of non-numeric items (only applies to flag 2) ; You can combine the various flags using BitOR() or you can also add them together. ; When sorting with flag 2, numbers always appear before unsorted items. ; When sorting with flag 4, numbers appear before non-numeric items in ascending order. ; Flag 256 will be ignored if not combined with flags 2 or 4. ; Flag 512 will be ignored if not combined with flag 2. ; ----------------------------------------------------------- ; Optional start sub-indices; $1, $2, $3, $4, $5, $6, $7, $8 and $9; work as follows: ; In the explicit dimension the start index should be less than $iEnd. ; In all other dimensions start values also represent end values. [range = 1, not multi-regional within XD] ; This function works for arrays of up to nine dimensions. ; ============================================================================================================================== Func _ArraySortXD(ByRef $aArray, $iDimension = 1, $iAlgorithm = 0, $iEnd = -1, $1 = 0, $2 = 0, $3 = 0, $4 = 0, $5 = 0, $6 = 0, $7 = 0, $8 = 0, $9 = 0) If Not IsArray($aArray) Then Return SetError(1) ; $aArray must be an array Local $aBound = __GetBounds($aArray) ; get the bounds of the array If @error Then Return SetError(2) ; $aArray must contain more than zero elements $iDimension = ($iDimension = Default) ? 1 : Int($iDimension) If $iDimension > $aBound[0] Or $iDimension < 1 Then Return SetError(3) ; dimension limit exceeded Local $aParam = [$aBound[0], $1, $2, $3, $4, $5, $6, $7, $8, $9] ; [$aParam can be oversized] For $i = 1 To $aBound[0] $aParam[$i] = ($aParam[$i] = Default) ? 0 : Int($aParam[$i]) If $aParam[$i] < 0 Or $aParam[$i] >= $aBound[$i] Then Return SetError(4) ; out of range start parameter was detected Next $iEnd = ($iEnd = -1 Or $iEnd = Default) ? $aBound[$iDimension] - 1 : Int($iEnd) Local $iStart = $aParam[$iDimension] If $iEnd <= $iStart Or $iEnd >= $aBound[$iDimension] Then Return SetError(5) ; meaningless $iEnd value $iAlgorithm = ($iAlgorithm = Default) ? 0 : Int($iAlgorithm) If BitAND(0xFFFFFCF8, $iAlgorithm) Then Return SetError(6) ; meaningless $iAlgorithm ; determine the type of algorithm Local $bNumeric = BitAND($iAlgorithm, 2) Or BitAND($iAlgorithm, 4), _ $bAlpha = BitAND($iAlgorithm, 4) Or Not $bNumeric, _ $bDecimals = $bNumeric And BitAND($iAlgorithm, 256), _ $bSequential = BitAND($iAlgorithm, 512) And BitAND($iAlgorithm, 2) And Not $bAlpha, _ $bReverse = Mod($iAlgorithm, 2) <> 0 Local $aTrac, $aVector, $iItems = 0 ; to count numeric elements [if needed] If $aBound[0] = 1 Then ; First deal with 1D arrays [optimized for algorithms 0, 2 and 4 without any other flags] If $bNumeric Then ; numeric [1D] If Not ($bDecimals Or $bSequential) Then ; tracking is not needed [conditional may require modification at some point to accomodate new algorithms] $iItems = __Separate1D($aArray, $iStart, $iEnd) ; place numbers before strings __QuickSort1D($aArray, $iStart, $iStart + $iItems - 1, 2) ; sort numbers numerically Else ; employ a track and trace mechanism $aTrac = __CreateTrac($aBound[$iDimension], $iStart, $iEnd) ; instead of sorting the array we will track migrating indices If $bDecimals Then $aVector = $aArray $iItems = __Separate256($aVector, $aTrac, $iStart, $iEnd) ; [numbers and decimal strings] __QuickSortXD($aVector, $aTrac, $iStart, $iStart + $iItems - 1, 256) ; pretend to sort the array but instead relocate indices in $aTrac $aVector = '' ; reduce memory usage ASAP Else ; [$bSequential = True] $iItems = __SeparateXD($aArray, $aTrac, $iStart, $iEnd) ; [numbers only] __QuickSortXD($aArray, $aTrac, $iStart, $iStart + $iItems - 1, 2) ; pretend to sort the array but instead relocate indices in $aTrac EndIf EndIf If $bAlpha Then If $bDecimals Then __QuickSortXD($aArray, $aTrac, $iStart + $iItems, $iEnd, 0) ; sort strings lexically Else __QuickSort1D($aArray, $iStart + $iItems, $iEnd, 0) ; sort strings lexically and finish If $bReverse Then ___Reverse1D($aArray, $iStart, $iEnd) ; descending Return ; finished EndIf ElseIf $bSequential Then __QuickSort1D($aTrac, $iStart + $iItems, $iEnd, 2) ; sort non-numeric element indices corrupted during separation ElseIf Not $bDecimals Then ; tracking was not used, so we are almost done here If $bReverse Then ___Reverse1D($aArray, $iStart, $iStart + $iItems - 1) ; descending Return ; finished EndIf If $bReverse Then ___Reverse1D($aTrac, $iStart, ($bAlpha ? $iEnd : $iStart + $iItems - 1)) ; reverse indices in $aTrac __TagSortSwap($aArray, $aTrac, $iStart, $iEnd) ; similar to the knight's tour problem Else ; lexical [1D] __QuickSort1D($aArray, $iStart, $iEnd, 0) If $bReverse Then ___Reverse1D($aArray, $iStart, $iEnd) ; descending EndIf Else ; multidimensional [XD] $aTrac = __CreateTrac($aBound[$iDimension], $iStart, $iEnd) ; used to track migrating indices $aVector = __ExtractVector($aArray, $iDimension, $aParam) ; first extract the vector to use for sorting (row or column etc...) ; now come similar arguments as for 1D If $bNumeric Then ; numeric [XD] $iItems = $bDecimals ? __Separate256($aVector, $aTrac, $iStart, $iEnd) : __SeparateXD($aVector, $aTrac, $iStart, $iEnd) __QuickSortXD($aVector, $aTrac, $iStart, $iStart + $iItems - 1, ($bDecimals ? 256 : 2)) ; relocate indices in $aTrac If $bAlpha Then __QuickSortXD($aVector, $aTrac, $iStart + $iItems, $iEnd, 0) ; sort strings lexically ElseIf $bSequential Then $aVector = '' ; free up memory __QuickSort1D($aTrac, $iStart + $iItems, $iEnd, 2) ; sort non-numeric element indices corrupted during separation EndIf $aVector = '' ; as above If $bReverse Then ___Reverse1D($aTrac, $iStart, ($bAlpha ? $iEnd : $iStart + $iItems - 1)) Else __QuickSortXD($aVector, $aTrac, $iStart, $iEnd, 0) ; relocate indices in $aTrac $aVector = '' ; as above If $bReverse Then ___Reverse1D($aTrac, $iStart, $iEnd) ; descending EndIf If $aBound[0] = 2 And $iDimension = 1 Then ; slightly more optimal method __TagSortSwapXD($aArray, $aTrac, $iStart, $iEnd) ; similar to the knight's tour problem Else $aParam = $aBound ; original parameters are no longer needed $aParam[$iDimension] = 1 Local $aRegion = ___NewArray($aParam) ; to store extracted regions For $i = 1 To $aParam[0] $aParam[$i] -= 1 Next Local $sIndices = __HiddenIndices($aBound[0], $iDimension), $fnFloodFill = __FloodFunc()[$aBound[0]], _ $iCurr, $iNext, $sTransfer = '$aSource' & $sIndices ; array syntax ; [now comes the knight's tour in 9 dimensions] ;) For $iInit = $iStart To $iEnd ; initialize each swap sequence If $aTrac[$iInit] <> $iInit Then ; regions will now be overwritten in accordance with tracking information $iCurr = $iInit ; set the current region as the start of the sequence $fnFloodFill($aRegion, $aParam, $iDimension, 0, $iInit, $aArray, $sTransfer) ; extract region $sTransfer = '$aTarget' & $sIndices ; array syntax Do $fnFloodFill($aArray, $aParam, $iDimension, $iCurr, $aTrac[$iCurr], '', $sTransfer) ; overwrite each region in the sequence $iNext = $aTrac[$iCurr] ; get the next index in the sequence $aTrac[$iCurr] = $iCurr ; set to ignore overwritten regions on subsequent encounters $iCurr = $iNext ; follow the trail as far as it goes [index could be higher or lower] Until $aTrac[$iCurr] = $iInit ; all sequences end at this juncture $sTransfer = '$aSource' & $sIndices ; now we know where to put the initial region we copied earlier $fnFloodFill($aArray, $aParam, $iDimension, $iCurr, 0, $aRegion, $sTransfer) $aTrac[$iCurr] = $iCurr ; set to ignore on subsequent encounters [as above] EndIf Next EndIf EndIf EndFunc ;==>_ArraySortXD ; #FUNCTION# =================================================================================================================== ; Name...........: _ArrayTransform ; Description ...: Alters the shape of a multidimensional array without losing data. ; Syntax.........: _ArrayTransform($aArray [, $sShape = Default]) ; Parameters.....; $aArray - The array to modify. ; $sShape - [Optional] String or integer value - output dimension bounds sequence [See Comments] ; Return values .: Success - Returns the transformed array [ByRef]. ; Failure sets @error as follows: ; |@error = 1 The 1st parameter is not a valid array. ; |@error = 2 Bad $sShape parameter. ; Author ........: czardas ; Comments ......: The shape parameter must be a sequence of unique digits: each refering to a different dimension. ; For a 2D array the default shape parameter is '21', and for 3D it is '321' etc... ; The default output sequence transposes the array by reversing the dimension bounds ==> [7][8] becomes [8][7]. ; If you want a different output shape, change the dimension bounds sequence. ; eg. when $sShape = '2431', [1][2][3][4] will become [2][4][3][1] ; This function is limited to arrays of between two and nine dimensions. ; ============================================================================================================================== Func _ArrayTransform(ByRef $aArray, $sShape = Default) ; [default shape = reverse sequence '987654321'] If Not IsArray($aArray) Then Return SetError(1) ; not a valid array. Local $aBound = __GetBounds($aArray) If @error Or $aBound[0] = 1 Or $aBound[0] > 9 Then Return SetError(1) If $sShape = Default Then $sShape = StringRight('987654321', $aBound[0]) If StringLen($sShape) <> $aBound[0] Or Not StringIsDigit($sShape) Then Return SetError(2) ; bad $sShape parameter Local $aTrac = StringSplit($sShape, ''), $sTransfer = '$aSource' For $i = 1 To $aBound[0] If Not StringInStr($sShape, $i) Then Return SetError(2) ; bad $sShape parameter [dimensions must already exist] $sTransfer &= '[$a[' & $aTrac[$i] & ']]' ; default ==> '$aSource[$a[9]][$a[8]][$a[7]][$a[6]][$a[5]] etc...' Next Local $aBoundNew = $aBound __TagSortSwap($aBoundNew, $aTrac, 1, $aBoundNew[0]) Local $aNewArray = ___NewArray($aBoundNew), $fnFloodFill = __FloodFunc()[$aBound[0]] For $i = 1 To $aBoundNew[0] $aBoundNew[$i] -= 1 Next $fnFloodFill($aNewArray, $aBoundNew, 0, 0, '', $aArray, $sTransfer) $aArray = $aNewArray EndFunc ;==>_ArrayTransform ; #FUNCTION# =================================================================================================================== ; Name...........: _ArrayUniqueXD ; Description ...: Removes duplicate items from an array (or duplicate regions from a multidimensional array). ; Syntax.........: _ArrayUniqueXD($aArray [, $bCaseSense = False [, $iDimension = 1]]) ; Parameters.....; $aArray - The array containing duplicate regions to be removed. ; $bCaseSense - [Optional] Set to true for case sensitive matches. Default value = False ; $iDimension - [Optional] Integer value - the dimension to which uniqueness applies. Default = 1st dimension ; Return values .: Success - Returns the unique array [ByRef]. ; Failure sets @error as follows: ; |@error = 1 The 1st parameter is not an array. ; |@error = 2 The 1st parameter does not contain any elements. ; |@error = 3 The 3rd parameter is not a valid dimension. ; Author ........: czardas ; Comments ......; This function is limited to arrays of up to nine dimensions. ; All elements within a region must be duplicated (in juxtaposed positions) before removal takes place. ; Integers of the same magnitude are treated as duplicates regardless of data type. ; This function does not remove regions containing objects, DLLStructs or other arrays. ; Example .......; _ArrayUniqueXD($aArray, Default, 2) ; [$iDimension = 2] ; ==> deletes duplicate COLUMNS from a 2D array ; ============================================================================================================================== Func _ArrayUniqueXD(ByRef $aArray, $bCaseSense = Default, $iDimension = 1) If Not IsArray($aArray) Or UBound($aArray, 0) > 9 Then Return SetError(1) ; not a valid array Local $aBound = __GetBounds($aArray, 9) If @error Then Return SetError(2) ; array contains zero elements $iDimension = ($iDimension = Default) ? 1 : Int($iDimension) If $iDimension < 1 Or $iDimension > $aBound[0] Then Return SetError(3) ; dimension does not exist If $aBound[$iDimension] < 2 Then Return ; the array is already unique For $i = 1 To 9 $aBound[$i] -= 1 ; set the max index in each dimension Next Local $sExpression = '$aArray' ; to access elements at their original indices For $i = 1 To $aBound[0] If $i <> $iDimension Then $sExpression &= '[$' & $i & ']' ; default expression = '$aArray[$iFrom][$2][$3][$4][$5] etc...' Else $sExpression &= '[$iFrom]' EndIf Next Local $sTransfer = '$aTarget' & __HiddenIndices($aBound[0], $iDimension), _ $fnFloodFill = __FloodFunc()[$aBound[0]], $iDim = $aBound[0], _ ; preserve this value $iItems = 0, $aFunction[1] = [0], $vElement, $iInt, $sName $aBound[0] = $aBound[$iDimension] ; the first loop will run to the bounds of the specified dimension $aBound[$iDimension] = 0 ; whichever loop this relates to must only run once on each encounter If $bCaseSense = Default Then $bCaseSense = False Local $oDictionary = ObjCreate("Scripting.Dictionary") For $iFrom = 0 To $aBound[0] $sName = '' ; clear previous name For $9 = 0 To $aBound[9] For $8 = 0 To $aBound[8] For $7 = 0 To $aBound[7] For $6 = 0 To $aBound[6] For $5 = 0 To $aBound[5] For $4 = 0 To $aBound[4] For $3 = 0 To $aBound[3] For $2 = 0 To $aBound[2] For $1 = 0 To $aBound[1] $vElement = Execute($sExpression) ; get the data contained in each element ; use non-hexadecimal characters (other than x) as datatype identifiers and convert the data to hexadecimal where necessary Switch VarGetType($vElement) Case 'String' If Not $bCaseSense Then $vElement = StringUpper($vElement) ; generates a case insensitive name segment $sName &= 's' & StringToBinary($vElement, 4) ; UTF8 [$SB_UTF8] Case 'Int32', 'Int64' ; use decimal without conversion ; the minus sign of a negative integer is replaced with 'g': to distinguish it from the positive value $sName &= ($vElement < 0) ? 'g' & StringTrimLeft($vElement, 1) : 'i' & $vElement Case 'Double' ; may be an integer $iInt = Int($vElement) $sName &= ($vElement = $iInt) ? (($iInt < 0) ? 'g' & StringTrimLeft($iInt, 1) : 'i' & $iInt) : 'h' & Hex($vElement) Case 'Bool' ; True or False $sName &= ($vElement = True) ? 't' : 'v' Case 'Binary' $sName &= 'y' & $vElement Case 'Ptr' $sName &= 'p' & $vElement Case 'Keyword' ; Default or Null (other variable declarations are illegal) $sName &= ($vElement = Default) ? 'w' : 'n' Case 'Function', 'UserFunction' ; string conversion will fail For $k = 1 To $aFunction[0] ; unique functions are stored in a separate array If $vElement = $aFunction[$k] Then ; this function has been encountered previously $sName &= 'u' & $k ContinueLoop 2 EndIf Next $aFunction[0] += 1 If $aFunction[0] > UBound($aFunction) - 1 Then ReDim $aFunction[$aFunction[0] + 10] $aFunction[$aFunction[0]] = $vElement $sName &= 'u' & $aFunction[0] Case Else ; Array, Object, DLLStruct [or Map] $sName = False ; set to ignore ExitLoop 9 EndSwitch Next Next Next Next Next Next Next Next Next If $sName Then If $oDictionary.Exists($sName) Then ContinueLoop ; this region has been seen previously $oDictionary.Item($sName) ; use $sName as key EndIf ; overwrite the next region (assumes that the first duplicate region will be found quite quickly) If $iDim = 1 Then $aArray[$iItems] = $aArray[$iFrom] Else $fnFloodFill($aArray, $aBound, $iDimension, $iItems, $iFrom, '', $sTransfer) ; access the remote loop region EndIf $iItems += 1 Next $aBound[0] = $iDim ; reset the number of dimensions For $i = 1 To $aBound[0] ; reset the bounds $aBound[$i] += 1 Next $aBound[$iDimension] = $iItems ; new bounds of the explicit dimension __ResetBounds($aArray, $aBound) ; remove the remaining duplicate array regions EndFunc ;==>_ArrayUniqueXD ; #FUNCTION# =================================================================================================================== ; Name...........: _DeleteDimension ; Description ...: Delete dimensions, or a range of dimensions, from a multidimensional array. ; Syntax.........: _DeleteDimension(ByRef $aArray, $iDimension, $iRange = 1) ; Parameters.....; $aArray - [ByRef] The array from which dimensions are deleted. ; $iDimension - Integer value - the dimension (or the first dimension) to delete. ; $iRange - [Optional] Integer value - the number of dimensions to delete. Default = 1 ; Return values .: Success - Returns the modified array ByRef. ; Failure sets @error as follows: ; |@error = 1 $aArray is not a valid array. ; |@error = 2 Dimension limit exceeded. ; |@error = 3 Arrays must contain at least one element. ; |@error = 4 Meaningless range value [range must be greater than zero]. ; |@error = 5 Illegal operation - deleting all dimensions is not supported. ; Author ........: czardas ; Comments ......; Beware - deleting dimensions can have very destructive effect on the array's contents. ; Deletes all regions with indices greater than zero while removing each dimension. ; This function will not delete all the dimensions from an array. ; This function is limited to arrays of up to nine dimensions. ; ============================================================================================================================== Func _DeleteDimension(ByRef $aArray, $iDimension, $iRange = 1) If Not IsArray($aArray) Then Return SetError(1) ; this parameter must be an array Local $aBound = __GetBounds($aArray) If @error Then Return SetError(3) ; arrays must contain at least one element If $aBound[0] > 9 Then Return SetError(2) $iDimension = Int($iDimension) If $iDimension < 1 Or $iDimension > $aBound[0] Then Return SetError(2) ; check for dimension range overflow and set to ignore [subject to review] $iRange = ($iRange = Default) ? 1 : Int($iRange) If $iRange < 1 Then Return SetError(4) ; range must be greater than zero If $iDimension + $iRange - 1 >= $aBound[0] Then $iRange = 1 + $aBound[0] - $iDimension If $iRange = $aBound[0] Then Return SetError(5) ; illegal operation [deleting all dimensions is not an intended feature] Local $sTransfer = '$aSource', $iCount = 1, $aBoundNew[$aBound[0] - $iRange + 1] $aBoundNew[0] = $aBound[0] - $iRange For $i = 1 To $aBound[0] If $i < $iDimension Or $i > $iDimension + $iRange - 1 Then ; dimensions to keep $aBoundNew[$iCount] = $aBound[$i] ; assign the bounds of the new array $sTransfer &= '[$a[' & $iCount & ']]' $iCount += 1 Else ; dimensions to delete For $i = $iDimension To $iDimension + $iRange - 1 ; run through the range $sTransfer &= '[0]' ; delete / hide dimensions from the fill instructions Next $i -= 1 ; prevents incrementing the loop iteration count twice EndIf Next Local $aNewArray = ___NewArray($aBoundNew) For $i = 1 To $aBoundNew[0] $aBoundNew[$i] -= 1 ; maximum sub-index within each dimension Next Local $aFloodFill = __FloodFunc() $aFloodFill[$aBoundNew[0]]($aNewArray, $aBoundNew, 0, 0, '', $aArray, $sTransfer) ; flood the new array $aArray = $aNewArray EndFunc ;==>_DeleteDimension ; #FUNCTION# =================================================================================================================== ; Name...........: _DeleteRegion ; Description ...: Deletes a region from a multidimensional array. ; Syntax.........: _DeleteRegion($aArray, $iDimension [, $iSubIndex = 0 [, $iRange = 1]]) ; Parameters.....; $aArray - [ByRef] The array to delete the region from. ; $iDimension - [Optional] Integer value - the dimension used to define the region. Default = 1 ; $iSubIndex - [Optional] Integer value - the index, or start of, of the region to delete. Default = 0 ; $iRange - [Optional] Integer value - the size of the region to delete. Default = 1 ; Return values .: Success - Returns the modified array ByRef. ; Failure sets @error as follows: ; |@error = 1 $aArray is not a valid array. ; |@error = 2 Dimension limit exceeded. ; |@error = 3 Dimension does not exist in the array. ; |@error = 4 Arrays must contain at least one element. ; |@error = 5 Sub-index does not exist in the dimension. ; |@error = 6 Invalid Range. ; Author ........: czardas ; Comments ......; This function is limited to arrays of up to nine dimensions. ; ============================================================================================================================== Func _DeleteRegion(ByRef $aArray, $iDimension = 1, $iSubIndex = 0, $iRange = 1) If Not IsArray($aArray) Then Return SetError(1) Local $aBound = __GetBounds($aArray) ; get the bounds of each dimension If @error Then Return SetError(4) ; $aArray must contain at least one element If $aBound[0] > 9 Then Return SetError(2) ; nine dimension limit $iDimension = ($iDimension = Default) ? 1 : Int($iDimension) If $iDimension > $aBound[0] Or $iDimension < 1 Then Return SetError(3) ; out of bounds dimension $iSubIndex = ($iSubIndex = Default) ? 0 : Int($iSubIndex) If $iSubIndex < 0 Or $iSubIndex > $aBound[$iDimension] - 1 Then Return SetError(5) ; sub-index does not exist in the dimension $iRange = ($iRange = Default) ? 1 : Int($iRange) If $iRange < 1 Then Return SetError(6) ; range must be greater than zero $iRange = ($iSubIndex + $iRange < $aBound[$iDimension]) ? $iRange : $aBound[$iDimension] - $iSubIndex ; corrects for overflow If $iRange = $aBound[$iDimension] Then Return SetError(6) ; deleting the whole region is not currently supported [give reason] $aBound[$iDimension] -= $iRange ; the size of the dimension in the new array If $aBound[0] = 1 Then For $iNext = $iSubIndex To $aBound[$iDimension] - 1 $aArray[$iNext] = $aArray[$iNext + $iRange] Next ReDim $aArray[$aBound[$iDimension]] Return EndIf Local $iMaxIndex = $aBound[$iDimension] - 1 For $i = 1 To $aBound[0] $aBound[$i] -= 1 Next $aBound[$iDimension] = 0 ; set to loop once [one region at a time] Local $iFrom, $sTransfer = '$aTarget' & __HiddenIndices($aBound[0], $iDimension), $fnFloodFill = __FloodFunc()[$aBound[0]] For $iNext = $iSubIndex To $iMaxIndex $iFrom = $iNext + $iRange $fnFloodFill($aArray, $aBound, $iDimension, $iNext, $iFrom, '', $sTransfer) ; overwrite the final [untouched] region Next $aBound[$iDimension] = $iMaxIndex For $i = 1 To $aBound[0] $aBound[$i] += 1 Next __ResetBounds($aArray, $aBound) ; delete remaining indices EndFunc ;==>_DeleteRegion ; #FUNCTION# =================================================================================================================== ; Name...........: _ExtractRegion ; Description ...: Extracts a region from a multidimensional array. ; Syntax.........: _ExtractRegion($aArray, $iDimension [, $iSubIndex = 0 [, $iRange = 1]]) ; Parameters.....; $aArray - The array from which to extract the region. ; $iDimension - Integer value - the dimension used to define the region. ; $iSubIndex - [Optional] Integer value - the index, or start of, of the region to extract. Default = 0 ; $iRange - [Optional] Integer value - the number of regions to extract. Default = 1 ; Return values .: Success - Returns a new array containing all the extracted regions within the defined range. ; Failure sets @error as follows: ; |@error = 1 $aArray is not a valid array. ; |@error = 2 Dimension limit exceeded. ; |@error = 3 Dimension does not exist in the array. ; |@error = 4 Arrays must contain at least one element. ; |@error = 5 Sub-index does not exist in the dimension. ; |range must be greater than zero ; Author ........: czardas ; Comments ......; This function is limited to arrays of up to nine dimensions. ; The extracted region will contain the same number of dimensions as the original array. ; ============================================================================================================================== Func _ExtractRegion($aArray, $iDimension, $iSubIndex = 0, $iRange = 1) If Not IsArray($aArray) Then Return SetError(1) Local $aBound = __GetBounds($aArray) ; get the bounds of each dimension If @error Then Return SetError(4) ; $aArray must contain at least one element If $aBound[0] > 9 Then Return SetError(2) ; nine dimension limit $iDimension = Int($iDimension) If $iDimension > $aBound[0] Or $iDimension < 1 Then Return SetError(3) ; out of bounds dimension $iSubIndex = ($iSubIndex = Default) ? 0 : Int($iSubIndex) If $iSubIndex < 0 Or $iSubIndex > $aBound[$iDimension] - 1 Then Return SetError(5) ; sub-index does not exist in the dimension $iRange = ($iRange = Default) ? 1 : Int($iRange) If $iRange < 1 Then Return SetError(6) ; range must be greater than zero $iRange = ($iSubIndex + $iRange < $aBound[$iDimension]) ? $iRange : $aBound[$iDimension] - $iSubIndex $aBound[$iDimension] = $iRange ; the size of the dimension in the new array Local $aRegion = ___NewArray($aBound) ; create new array For $i = 1 To $aBound[0] $aBound[$i] -= 1 Next If $aBound[0] = 1 Then For $iNext = 0 To $iRange - 1 $aRegion[$iNext] = $aArray[$iNext + $iSubIndex] Next Return $aRegion EndIf $aBound[$iDimension] = 0 ; set to loop once [one region at a time] Local $iFrom, $sTransfer = '$aSource' & __HiddenIndices($aBound[0], $iDimension), $fnFloodFill = __FloodFunc()[$aBound[0]] For $iNext = 0 To $iRange - 1 $iFrom = $iNext + $iSubIndex $fnFloodFill($aRegion, $aBound, $iDimension, $iNext, $iFrom, $aArray, $sTransfer) ; extract region Next Return $aRegion EndFunc ;==>_ExtractRegion ; #FUNCTION# =================================================================================================================== ; Name...........: _InsertRegion ; Description ...: Inserts a multidimensional array into another multidimensional array. ; Syntax.........: _InsertRegion($aTarget, $aSource [, $iDimension = 1 [, $iSubIndex = 0]]) ; Parameters.....; $aTarget - The array to modify. ; $aSource - The array to insert. ; $iDimension - Integer value - the dimension in which insertion takes place. ; $iSubIndex - [Optional] Integer value - sub-index within the dimension where insertion occurs. Default = 0 ; Return values .: Success - Returns the target array ByRef. ; Failure sets @error as follows: ; |@error = 1 $aTarget is not a valid array. ; |@error = 2 $aSource is not a valid array. ; |@error = 3 Arrays must contain at least one element. ; |@error = 4 Array bounds do not match. ; |@error = 5 Output array size exceeds AutoIt limits. ; |@error = 6 $iSubIndex is out of range. ; Author ........: czardas ; Comments ......; This function is limited to arrays of up to nine dimensions. ; The target array must contain the same number of dimensions as the source array. ; With the exception of $iDimension; the bounds of all other dimensions, in both arrays, must match. ; ============================================================================================================================== Func _InsertRegion(ByRef $aTarget, $aSource, $iDimension = 1, $iSubIndex = 0) If Not IsArray($aTarget) Or UBound($aTarget, 0) > 9 Then Return SetError(1) If Not IsArray($aSource) Or UBound($aSource, 0) > 9 Then Return SetError(2) Local $aBoundTgt = __GetBounds($aTarget) If @error Then Return SetError(3) ; arrays must contain at least one element Local $aBoundSrc = __GetBounds($aSource) If @error Then Return SetError(3) ; arrays must contain at least one element $iDimension = ($iDimension = Default) ? 1 : Int($iDimension) For $i = 1 To $aBoundTgt[0] If $aBoundTgt[$i] <> $aBoundSrc[$i] And $iDimension <> $i Then Return SetError(4) ; array bounds are inconsistent Next $iSubIndex = ($iSubIndex = Default) ? 0 : Int($iSubIndex) If $iSubIndex < 0 Or $iSubIndex > $aBoundTgt[$iDimension] Then Return SetError(5) ; $iSubIndex is out of range Switch $iSubIndex Case 0 _ArrayAttach($aSource, $aTarget, $iDimension) If @error Then Return SetError(6) ; output array size exceeds AutoIt limits $aTarget = $aSource Case $aBoundTgt[$iDimension] _ArrayAttach($aTarget, $aSource, $iDimension) If @error Then Return SetError(6) ; output array size exceeds AutoIt limits Case Else ; check output bounds remain within range before modifications begin $aBoundSrc[$iDimension] += $aBoundTgt[$iDimension] Local $iCount = 1 For $i = 1 To $aBoundSrc[0] $iCount *= $aBoundSrc[$i] If $iCount > 16777216 Then Return SetError(6) ; output array size exceeds AutoIt limits Next Local $aEnd = _ExtractRegion($aTarget, $iDimension, $iSubIndex, 16777216) ; out of bounds range values extract all remaining sub-indices within the dimension If @error Then Return SetError(@error) $aBoundTgt[$iDimension] = $iSubIndex __ResetBounds($aTarget, $aBoundTgt) _ArrayAttach($aTarget, $aSource, $iDimension) _ArrayAttach($aTarget, $aEnd, $iDimension) EndSwitch EndFunc ;==>_InsertRegion ; #FUNCTION# =================================================================================================================== ; Name...........: _PreDim ; Description ...: Changes the size of an array by adding, or removing, dimensions. ; Syntax.........: _PreDim($aArray, $iDimensions [, $iPush = False]) ; Parameters.....; $aArray - The original array. ; $iDimensions - The number of dimensions in the returned array. ; $bPush - [Optional] If set to True, new dimensions are created on, or removed from, the left [see comments]. ; Return values .: Returns the modified array ByRef. ; Failure sets @error as follows: ; |@error = 1 The first parameter is not an array. ; |@error = 2 The requested array has more than 9 dimensions. ; |@error = 3 The original array has more than 9 dimensions. ; |@error = 4 Arrays must contain at least one element. ; Author.........: czardas ; Comments ......; This function works for up to 9 dimensions. ; By default, new dimensions are added to the right in a standard sequence: $aArray[7][6] ==> $aArray[7][6][1] ; Dimensions are removed in reverse sequence: $aArray[7][6] ==> $aArray[7] ; When the $bPush parameter is set to True, the original array will be pushed to higher dimensions: ; $aArray[7][6] ==> $aArray[1][7][6], or the process reversed: $aArray[7][6] ==> $aArray[6] ; ============================================================================================================================== Func _PreDim(ByRef $aArray, $iDimensions, $bPush = False) If Not IsArray($aArray) Then Return SetError(1) $iDimensions = Int($iDimensions) If $iDimensions < 1 Or $iDimensions > 9 Then Return SetError(2) Local $iPreDims = UBound($aArray, 0) ; current number of dimensions If $iPreDims = $iDimensions Then Return ; no change If $iPreDims > 9 Then Return SetError(3) ; too many dimensions Local $aBound = __GetBounds($aArray) ; get the size of each original dimension If @error Then Return SetError(4) ; $aArray must contain at least one element $aBound[0] = $iDimensions ; overwrite this value with the new number of dimensions Local $sTransfer = '[$a[1]][$a[2]][$a[3]][$a[4]][$a[5]][$a[6]][$a[7]][$a[8]][$a[9]]' ; array syntax to be sent to the remote loop region If $bPush Then ; prefix dimensions, or delete from the left Local $iOffset = Abs($iDimensions - $iPreDims) If $iPreDims > $iDimensions Then ; lower dimensions get deleted For $i = 1 To $iDimensions ; shift elements to lower indices $aBound[$i] = $aBound[$i + $iOffset] Next $sTransfer = '$aSource' & StringLeft('[0][0][0][0][0][0][0][0]', $iOffset * 3) & StringLeft($sTransfer, $iDimensions * 7) Else ; lower dimensions are created ReDim $aBound[$iDimensions + 1] ; make space for more dimensions For $i = $iDimensions To $iOffset + 1 Step -1 ; shift elements to higher indices $aBound[$i] = $aBound[$i - $iOffset] Next For $i = 1 To $iOffset ; assign the size of each additional dimension [1][1][1]... etc... $aBound[$i] = 1 Next $sTransfer = '$aSource' & StringMid($sTransfer, 1 + $iOffset * 7, $iPreDims * 7) EndIf Else ; Default behaviour = append dimensions, or delete from the right ReDim $aBound[$iDimensions + 1] ; modify the number of dimensions [according to the new array] For $i = $iPreDims + 1 To $iDimensions ; assign the size of each new dimension ...[1][1][1] etc... $aBound[$i] = 1 Next $sTransfer = '$aSource' & StringLeft($sTransfer, $iPreDims * 7) EndIf ; add or remove dimensions Local $aNewArray = ___NewArray($aBound) For $i = 1 To $iDimensions $aBound[$i] -= 1 ; convert elements to the maximum index value within each dimension Next ; access the remote loop region Local $iSubIndex = 0, $aFloodFill = __FloodFunc() $aFloodFill[$iDimensions]($aNewArray, $aBound, 0, $iSubIndex, '', $aArray, $sTransfer) $aArray = $aNewArray EndFunc ;==>_PreDim ; #FUNCTION# =================================================================================================================== ; Name...........: _ReverseArray ; Description ...: Reverses regions within a multidimensional array. ; Syntax.........: _ReverseArray($aArray [, $iDimension = 1 [, $iStart = 0 [, $iEnd = -1]]]) ; Parameters.....; $aArray - The array to modify. ; $iDimension - [Optional] Integer value - the dimension in which the array sub-indices (regions) are reversed. ; $iStart - [Optional] The start sub-index within the defined dimension. Default value = 0 ; $iEnd - [Optional] Integer value - The end sub-index within the defined dimension. Default value = -1 ; Return values .: Success - Returns the reversed array [ByRef]. ; Failure sets @error as follows: ; |@error = 1 The 1st parameter is not a valid array. ; |@error = 2 The 1st parameter does not contain any elements. ; |@error = 3 The dimension does not exist. ; |@error = 4 Meaningless $iStart value. ; |@error = 5 Meaningless $iEnd value. ; Author ........: czardas ; Comments ......; This function is limited to arrays of up to nine dimensions. ; ============================================================================================================================== Func _ReverseArray(ByRef $aArray, $iDimension = 1, $iStart = 0, $iEnd = -1) If Not IsArray($aArray) Or UBound($aArray, 0) > 9 Then Return SetError(1) ; not a valid array Local $aBound = __GetBounds($aArray) If @error Then Return SetError(2) ; array contains zero elements $iDimension = ($iDimension = Default) ? 1 : Int($iDimension) If $iDimension < 1 Or $iDimension > $aBound[0] Then Return SetError(3) ; dimension does not exist $iStart = ($iStart = Default) ? 0 : Int($iStart) If $iStart < 0 Or $iStart > $aBound[$iDimension] - 2 Then Return SetError(4) ; meaningless $iStart value $iEnd = ($iEnd = -1 Or $iEnd = Default) ? $aBound[$iDimension] - 1 : Int($iEnd) If $iEnd <= $iStart Or $iEnd >= $aBound[$iDimension] Then Return SetError(5) ; meaningless $iEnd value If $aBound[0] = 1 Then ___Reverse1D($aArray, $iStart, $iEnd) Else $aBound[$iDimension] = 1 Local $aRegion = ___NewArray($aBound) ; to store extracted regions For $i = 1 To $aBound[0] $aBound[$i] -= 1 Next Local $sIndices = __HiddenIndices($aBound[0], $iDimension), $fnFloodFill = __FloodFunc()[$aBound[0]], _ $sTransfer = '$aSource' & $sIndices ; array syntax While $iEnd > $iStart $fnFloodFill($aRegion, $aBound, $iDimension, 0, $iStart, $aArray, $sTransfer) ; extract the current start region $sTransfer = '$aTarget' & $sIndices $fnFloodFill($aArray, $aBound, $iDimension, $iStart, $iEnd, '', $sTransfer) ; overwrite the current start region $sTransfer = '$aSource' & $sIndices $fnFloodFill($aArray, $aBound, $iDimension, $iEnd, 0, $aRegion, $sTransfer) ; overwrite the current end region $iStart += 1 $iEnd -= 1 WEnd EndIf EndFunc ;==>_ReverseArray ; #FUNCTION# =================================================================================================================== ; Name...........: _SearchArray ; Description ...: Searches through a multidimensional array. ; Syntax.........: _SearchArray($aArray, $vSearchTerm [, $bCaseSense = False [, $iDimension = 1]]) ; Parameters.....; $aArray - [ByRef] Target array to which the source array (or data) will be concatenated. ; $vSearchTerm - The string or data to search for. ; $bCaseSense - [Optional] Boolean value - case sensitive search. Default = False ; $iDimension - [Optional] Integer value - the dimension used to conduct the search. Default = 1st dimension ; $iAlgo - [Optional] Algorithm: 1 = exact match, 2 = find a string, 3 = find a word within text. [Default = 1] ; Return values .: Success - Returns an array of all regions containing the term searched for within the defined dimension. ; Failure sets @error as follows: ; |@error = 1 $aArray is not a valid array. ; |@error = 2 Arrays must contain a minimum of one element. ; |@error = 3 Dimension limit exceeded. ; |@error = 4 Bad algorithm. ; |@error = 5 No matches found. ; Author ........: czardas ; Comments ......; This function is limited to arrays of up to nine dimensions. ; Searching through a 2D array in the first dimension returns an array of all rows containing the search term. ; Searching a 2D array in the second dimension will return an array of all columns containing the search term. ; Searching any array will return an array of regions containing the search term within the defined dimension. ; [$iAlgo = 3] A word may contain any characters. Word boundaries are defined by non-alphanumeric characters. ; May return FP matches if $iAlgo = 3, $vSearchTerm contains '\E' and $aArray contains the code point U+E000. ; ============================================================================================================================== Func _SearchArray($aArray, $vSearchTerm, $bCaseSense = False, $iDimension = 1, $iAlgo = 1) ; [Exact Match] If Not IsArray($aArray) Then Return SetError(1) ; this parameter must be an array Local $aBound = __GetBounds($aArray) If @error Then Return SetError(2) ; arrays must contain at least one element If $aBound[0] > 9 Then Return SetError(3) ; dimension range exceeded $iDimension = ($iDimension = Default) ? 1 : Int($iDimension) If $iDimension < 1 Or $iDimension > $aBound[0] Then Return SetError(3) $iAlgo = ($iAlgo = Default) ? 1 : Int($iAlgo) If $iAlgo < 1 Or $iAlgo > 3 Then Return SetError(4) If $aBound[0] = 1 Then ; 1D special case $aArray = ___Search1D($aArray, $vSearchTerm, $bCaseSense, $iAlgo) Return @error ? SetError(5) : $aArray EndIf For $i = 1 To $aBound[0] $aBound[$i] -= 1 ; maximum index value within each dimension Next Local $iItems = 0, $iMaxIndex = $aBound[$iDimension], $fnFloodFill = __FloodFunc()[$aBound[0]], $aFunc = ['__FindExact', '__FindString', '__FindWord'], _ $sTransfer = $aFunc[$iAlgo - 1] & ($bCaseSense ? 'Case' : '') & '($aTarget, $a, $aBound, $iFrom, "$aTarget' & _ __HiddenIndices($aBound[0], $iDimension) & '")' ; string to execute $aBound[0] = $vSearchTerm $aBound[$iDimension] = 0 ; this loop must only run once on each encounter For $iFrom = 0 To $iMaxIndex ; loop through sub-indices within the dimension $fnFloodFill($aArray, $aBound, $iDimension, $iItems, $iFrom, '', $sTransfer) ; overwrite the current region while searching If Not $g__ARRWSHOP_RESUME Then ; a match halted the search during the overwrite $iItems += 1 ; set to overwrite the next region $g__ARRWSHOP_RESUME = True ; resume searching on the next pass EndIf Next If $iItems = 0 Then Return SetError(5) ; no matches found $aBound[0] = UBound($aBound) - 1 ; reset all bounds For $i = 1 To $aBound[0] $aBound[$i] += 1 Next $aBound[$iDimension] = $iItems __ResetBounds($aArray, $aBound) ; remove mismatches Return $aArray EndFunc ;==>_SearchArray ; #FUNCTION# =================================================================================================================== ; Name...........: _ShuffleArray ; Description ...: Shuffles multidimensional regions within an array, or items within multidimensional regions. ; Syntax.........: _ShuffleArray($aArray [, $iDimension = 1 [, $bFruitMachineStyle = False]]) ; Parameters.....; $aArray - The original array. ; $iDimension - [Optional] - The dimension used to define the regions to be shuffled. Default = 1 ; $bFruitMachineStyle - [Optional] Shuffle all items within regions defined by the dimension. Default = False ; Return values .: Returns the modified array ByRef. ; Failure sets @error as follows: ; |@error = 1 The first parameter is not a valid array. ; |@error = 2 The first parameter contains the wrong number of dimensions. ; |@error = 3 The second parameter does not relate to any of the dimensions available. ; |@error = 4 Arrays must contain at least one element. ; Author.........: czardas ; Comments ......; This function works for arrays of up to 9 dimensions. ; Setting $iDimension = 0 overrides the 3rd parameter and shuffles everything - anywhere within the array. ; Example .......; _ShuffleArray($aArray, 2, True) ; ==> This is a fruit machine! ; ============================================================================================================================== Func _ShuffleArray(ByRef $aArray, $iDimension = 1, $bFruitMachineStyle = False) If Not IsArray($aArray) Then Return SetError(1) Local $aBound = __GetBounds($aArray) ; get the bounds of each dimension If @error Then Return SetError(4) ; $aArray must contain at least one element If $aBound[0] > 9 Then Return SetError(2) ; nine dimension limit $iDimension = ($iDimension = Default) ? 1 : Int($iDimension) If $iDimension > $aBound[0] Or $iDimension < 0 Then Return SetError(3) ; out of bounds dimension Local $aTemp = $aBound ; regional bounds For $i = 1 To $aBound[0] $aBound[$i] -= 1 Next Local $iSubIndex, $sTransfer, $aFloodFill = __FloodFunc() If $iDimension > 0 Then ; shuffle regions or elements within regions If $aBound[0] > 1 Then $aTemp[$iDimension] = 1 Local $aRegion = ___NewArray($aTemp) ; to store extracted regions Local $sIndices = __HiddenIndices($aBound[0], $iDimension) $aTemp = $aBound $aTemp[$iDimension] = 0 ; set to loop once [one region at a time] If $bFruitMachineStyle Then ; contents will be shuffled within each region $sTransfer = '$aSource' & $sIndices ; array syntax For $iSubIndex = 0 To $aBound[$iDimension] ; loop through all indices within the dimension $aFloodFill[$aBound[0]]($aRegion, $aTemp, $iDimension, 0, $iSubIndex, $aArray, $sTransfer) ; extract region __ShuffleXD($aRegion, $aTemp) ; shuffle the extracted region $aFloodFill[$aBound[0]]($aArray, $aTemp, $iDimension, $iSubIndex, 0, $aRegion, $sTransfer) ; reinsert the shuffled region Next Else ; regions will be shuffled within the dimension Local $iRandom $sTransfer = '$aSource' & $sIndices For $iSubIndex = 0 To $aBound[$iDimension] $aFloodFill[$aBound[0]]($aRegion, $aTemp, $iDimension, 0, $iSubIndex, $aArray, $sTransfer) ; extract each region $sTransfer = '$aTarget' & $sIndices $iRandom = Random(0, $aBound[$iDimension], 1) ; acquire a random index $aFloodFill[$aBound[0]]($aArray, $aTemp, $iDimension, $iSubIndex, $iRandom, $aArray, $sTransfer) ; replace the original region $sTransfer = '$aSource' & $sIndices $aFloodFill[$aBound[0]]($aArray, $aTemp, $iDimension, $iRandom, 0, $aRegion, $sTransfer) ; replace the extracted region at the acquired index Next EndIf Else ; not a multidimensional array __Shuffle1D($aArray) ; shuffle the contents EndIf Else ; totally random - ignoring dimension bounds __ShuffleXD($aArray, $aBound) EndIf EndFunc ;==>_ShuffleArray #Region - Miscellaneous ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with numeric sort. Decimal strings should first be formatted with ___FormatNum() ; Author ........: czardas ; ============================================================================================================================== Func ___AcquireExponent($vNum) Local $bString = IsString($vNum) If $bString Then $vNum = StringReplace($vNum, '-', '') ; the minus symbol must first be stripped Return $bString ? ((StringLeft($vNum, 1) = '.') ? StringLen(StringRegExpReplace($vNum, '\.0*', '')) - StringLen($vNum) : StringInStr($vNum, '.') - 2) : Number(StringRight(StringFormat('%.1e', $vNum / 1), 4)) EndFunc ;==>___AcquireExponent ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Description ...: Return an array with a range of assigned index values (used to track migration patterns in __QuickSortXD). ; Author ........: czardas ; =============================================================================================================================== Func __CreateTrac($iBound, $iStart, $iEnd) Local $aTracker[$iBound] For $i = $iStart To $iEnd $aTracker[$i] = $i ; fill the (tracking) range with indices Next Return $aTracker EndFunc ;==>__CreateTrac ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: Extract a vector (list) from any dimension within a multidimensional array. ; ============================================================================================================================== Func __ExtractVector($aArray, $iDimension, $aIndices) $aIndices[$iDimension] = '$a[1]' ; $aIndices[$iDimension] is the remote loop count Local $sTransfer = '$aSource' ; the main array is the source For $i = 1 To $aIndices[0] $sTransfer &= '[' & $aIndices[$i] & ']' Next Local $iBound = UBound($aArray, $iDimension), $aVector[$iBound], $iSubIndex = 0, $aBound = ['', $iBound - 1] ___Flood1D($aVector, $aBound, $iDimension, $iSubIndex, '', $aArray, $sTransfer) Return $aVector EndFunc ;==>__ExtractVector ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with _ArraySearch - non-case-sensitive comparison. ; ============================================================================================================================== Func __FindExact($aTarget, $a, $aBound, $iFrom, $sSyntax) ; [default algorithm] $sSyntax = Execute($sSyntax) If $g__ARRWSHOP_RESUME And $aBound[0] = $sSyntax Then $g__ARRWSHOP_RESUME = False Return $sSyntax #forceref $aTarget, $a, $iFrom EndFunc ;==>__FindExact ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with _ArraySearch - case-sensitive comparison. ; ============================================================================================================================== Func __FindExactCase($aTarget, $a, $aBound, $iFrom, $sSyntax) $sSyntax = Execute($sSyntax) If $g__ARRWSHOP_RESUME And $aBound[0] == $sSyntax Then $g__ARRWSHOP_RESUME = False Return $sSyntax #forceref $aTarget, $a, $iFrom EndFunc ;==>__FindExactCase ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with _ArraySearch - search within strings - non-case-sensitive comparison. ; ============================================================================================================================== Func __FindString($aTarget, $a, $aBound, $iFrom, $sSyntax) $sSyntax = Execute($sSyntax) If $g__ARRWSHOP_RESUME And StringInStr($sSyntax, $aBound[0]) Then $g__ARRWSHOP_RESUME = False Return $sSyntax #forceref $aTarget, $a, $iFrom EndFunc ;==>__FindString ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with _ArraySearch - search within strings - case-sensitive comparison. ; ============================================================================================================================== Func __FindStringCase($aTarget, $a, $aBound, $iFrom, $sSyntax) $sSyntax = Execute($sSyntax) If $g__ARRWSHOP_RESUME And StringInStr($sSyntax, $aBound[0], 1) Then $g__ARRWSHOP_RESUME = False Return $sSyntax #forceref $aTarget, $a, $iFrom EndFunc ;==>__FindStringCase ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with _ArraySearch - search between word boundaries - non-case-sensitive comparison. ; ============================================================================================================================== Func __FindWord($aTarget, $a, $aBound, $iFrom, $sSyntax) $sSyntax = Execute($sSyntax) If $g__ARRWSHOP_RESUME And StringRegExp(StringReplace($sSyntax, '\E', $g__ARRWSHOP_SUB, 0, 1), '(*UCP)(?i)(\A|[^[:alnum:]])(\Q' & StringReplace($aBound[0], '\E', $g__ARRWSHOP_SUB, 0, 1) & '\E)(\z|[^[:alnum:]])') Then $g__ARRWSHOP_RESUME = False Return $sSyntax #forceref $aTarget, $a, $iFrom EndFunc ;==>__FindWord ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with _ArraySearch - search between word boundaries - case-sensitive comparison. ; ============================================================================================================================== Func __FindWordCase($aTarget, $a, $aBound, $iFrom, $sSyntax) $sSyntax = Execute($sSyntax) If $g__ARRWSHOP_RESUME And StringRegExp(StringReplace($sSyntax, '\E', $g__ARRWSHOP_SUB, 0, 1), '(*UCP)(\A|[^[:alnum:]])(\Q' & StringReplace($aBound[0], '\E', $g__ARRWSHOP_SUB, 0, 1) & '\E)(\z|[^[:alnum:]])') Then $g__ARRWSHOP_RESUME = False Return $sSyntax #forceref $aTarget, $a, $iFrom EndFunc ;==>__FindWordCase ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: Return an array of functions used for populating multidimensional array elements. ; ============================================================================================================================== Func __FloodFunc() Local $aFloodFunc = ['', ___Flood1D, ___Flood2D, ___Flood3D, ___Flood4D, ___Flood5D, ___Flood6D, ___Flood7D, ___Flood8D, ___Flood9D] Return $aFloodFunc EndFunc ;==>__FloodFunc ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with numeric sort. ['\A[\+\-]?(\d*\.?\d+|d+\.)\z' only ==> 1.0 .01 0.] ; Author ........: czardas ; ============================================================================================================================== Func ___FormatNum($sNum) If Not StringRegExp($sNum, '[1-9]') Then Return 0 $sNum = StringReplace($sNum, '+', '') ; get rid of plus symbol $sNum = StringRegExpReplace($sNum, "^-?\K(?=\.)", "0") ; add zeros [courtesy of jguinch] $sNum = StringRegExpReplace($sNum, "^-?\K0+(?=[1-9]|0\.?)|\.0*$|\.\d*[1-9]\K0+", "") ; strip zeros [courtesy of jguinch] If Execute($sNum) == $sNum Then Return Execute($sNum) ; return a number Return StringInStr($sNum, '.') ? StringRegExpReplace($sNum, '(\A\-?)(0)', '\1') : $sNum & '.' ; for fast comparison with floats [^^ StringCompare('1.', 1) > 0] EndFunc ;==>___FormatNum ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: Get the bounds of each available dimension in a multidimensional array. ; ============================================================================================================================== Func __GetBounds($aArray, $iHypothetical = 0) Local $iMaxDim = UBound($aArray, 0) Local $aBound[($iHypothetical ? $iHypothetical : $iMaxDim) + 1] ; [or ==> Local $aBound[9]] $aBound[0] = $iMaxDim For $i = 1 To $iMaxDim $aBound[$i] = UBound($aArray, $i) If $aBound[$i] = 0 Then Return SetError(1) Next If $iHypothetical Then For $i = $iMaxDim + 1 To $iHypothetical $aBound[$i] = 1 ; imaginary dimensions Next EndIf Return $aBound EndFunc ;==>__GetBounds ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: Return a fragment of code which is the format for the $sTransfer parameter in ___FloodXD. ; ============================================================================================================================== Func __HiddenIndices($iBound, $iDimension) Local $sSyntax = '' ; to access elements at their original indices For $i = 1 To $iBound If $i <> $iDimension Then $sSyntax &= '[$a[' & $i & ']]' ; default ==> '$aSource[$iFrom][$a[2]][$a[3]][$a[4]][$a[5]] etc...' Else $sSyntax &= '[$iFrom]' EndIf Next Return $sSyntax EndFunc ;==>__HiddenIndices ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: Create an array of between one and nine dimensions using predefined bounds. ; ============================================================================================================================== Func ___NewArray($aBound) Switch $aBound[0] Case 1 Local $aArray[$aBound[1]] Case 2 Local $aArray[$aBound[1]][$aBound[2]] Case 3 Local $aArray[$aBound[1]][$aBound[2]][$aBound[3]] Case 4 Local $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]] Case 5 Local $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]] Case 6 Local $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]][$aBound[6]] Case 7 Local $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]][$aBound[6]][$aBound[7]] Case 8 Local $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]][$aBound[6]][$aBound[7]][$aBound[8]] Case 9 Local $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]][$aBound[6]][$aBound[7]][$aBound[8]][$aBound[9]] EndSwitch Return $aArray EndFunc ;==>___NewArray ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with _SortArray() numeric comparison [str > num(?), num > str(?), str > str(?)] ; Author ........: czardas ; ============================================================================================================================== Func ___NumCompare($vNum1, $vNum2) ; ($vNum1 > $vNum2) If IsNumber($vNum1) And IsNumber($vNum2) Then If $vNum1 = $vNum2 Then Return 0 Return ($vNum1 > $vNum2) ? 1 : -1 EndIf If $vNum1 == '1.#INF' Or $vNum2 == '-1.#INF' Then Return 1 ; these values interfere with the comparison below If $vNum1 == '-1.#INF' Or $vNum2 == '1.#INF' Then Return -1 ; ditto Local $bNeg1 = (StringLeft($vNum1, 1) = '-') If $bNeg1 <> (StringLeft($vNum2, 1) = '-') Then Return $bNeg1 ? -1 : 1 Local $iExp1 = ___AcquireExponent($vNum1), $iExp2 = ___AcquireExponent($vNum2) If $iExp1 <> $iExp2 Then Return (($iExp1 > $iExp2) ? 1 : -1) * ($bNeg1 ? -1 : 1) ; negative magnitude changes the result Local $bType1 = (VarGetType($vNum1) = 'Double'), $bType2 = (VarGetType($vNum2) = 'Double') If $bType1 Or $bType2 Then ; grab all 17 digits from the double $vNum1 = $bType1 ? StringLeft(StringReplace(StringFormat('%.17e', $vNum1), '.', ''), 17) : StringReplace($vNum1, '.', '') $vNum2 = $bType2 ? StringLeft(StringReplace(StringFormat('%.17e', $vNum2), '.', ''), 17) : StringReplace($vNum2, '.', '') EndIf Return StringCompare($vNum1, $vNum2) * ($bNeg1 ? -1 : 1) ; negative magnitude changes the result EndFunc ;==>___NumCompare ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name...........: __QuickSort1D [adaptation of __ArrayQuickSort1D] ; Description ...: Helper function for sorting 1D arrays ; Author ........: Jos van der Zande, LazyCoder, Tylo, Ultima ; Modified.......: czardas - replaced alphanumeric sort with separate lexical and numeric sorting algorithms. ; =============================================================================================================================== Func __QuickSort1D(ByRef $aArray, $iStart, $iEnd, $iAlgorithm = 0) If $iEnd <= $iStart Then Return Local $vTmp ; InsertionSort (faster for smaller segments) If ($iEnd - $iStart) < 15 Then Switch $iAlgorithm Case 0 ; lexical For $i = $iStart + 1 To $iEnd $vTmp = $aArray[$i] For $j = $i - 1 To $iStart Step -1 If StringCompare($vTmp, $aArray[$j]) >= 0 Then ExitLoop $aArray[$j + 1] = $aArray[$j] Next $aArray[$j + 1] = $vTmp Next Return Case 2 ; numeric strict For $i = $iStart + 1 To $iEnd $vTmp = $aArray[$i] If IsNumber($vTmp) Then For $j = $i - 1 To $iStart Step -1 If $vTmp >= $aArray[$j] And IsNumber($aArray[$j]) Then ExitLoop $aArray[$j + 1] = $aArray[$j] Next $aArray[$j + 1] = $vTmp EndIf Next Return EndSwitch Return EndIf ; QuickSort Local $L = $iStart, $R = $iEnd, $vPivot = $aArray[Int(($iStart + $iEnd) / 2)] Do If $iAlgorithm = 0 Then ; lexical While StringCompare($aArray[$L], $vPivot) < 0 $L += 1 WEnd While StringCompare($aArray[$R], $vPivot) > 0 $R -= 1 WEnd ElseIf $iAlgorithm = 2 Then ; numeric strict While $aArray[$L] < $vPivot $L += 1 WEnd While $aArray[$R] > $vPivot $R -= 1 WEnd EndIf If $L <= $R Then ; Swap $vTmp = $aArray[$L] $aArray[$L] = $aArray[$R] $aArray[$R] = $vTmp $L += 1 $R -= 1 EndIf Until $L > $R __QuickSort1D($aArray, $iStart, $R, $iAlgorithm) __QuickSort1D($aArray, $L, $iEnd, $iAlgorithm) EndFunc ;==>__QuickSort1D ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name...........: __QuickSortXD [adaptation of __ArrayQuickSort1D] ; Description ...: Helper function for sorting multidimensional arrays ; Author ........: Jos van der Zande, LazyCoder, Tylo, Ultima ; Modified.......: czardas - to sort indices of an X-dimensional array vector, instead of overwriting complete regions or rows. ; =============================================================================================================================== Func __QuickSortXD($aArray, ByRef $aTrac, $iStart, $iEnd, $iAlgorithm = 0) If $iEnd <= $iStart Then Return Local $iTmp ; InsertionSort (faster for smaller segments) If ($iEnd - $iStart) < 15 Then Switch $iAlgorithm Case 0 ; lexical For $i = $iStart + 1 To $iEnd $iTmp = $aTrac[$i] For $j = $i - 1 To $iStart Step -1 If (StringCompare($aArray[$iTmp], $aArray[$aTrac[$j]]) >= 0) Then ExitLoop $aTrac[$j + 1] = $aTrac[$j] Next $aTrac[$j + 1] = $iTmp Next Case 2 ; (or 4) numeric strict [also applies to algorithm 4] For $i = $iStart + 1 To $iEnd $iTmp = $aTrac[$i] For $j = $i - 1 To $iStart Step -1 If $aArray[$iTmp] >= $aArray[$aTrac[$j]] Then ExitLoop $aTrac[$j + 1] = $aTrac[$j] Next $aTrac[$j + 1] = $iTmp Next Case 256 ; numeric [preprocessed; decimal string formatting required, see ___FormatNum] For $i = $iStart + 1 To $iEnd $iTmp = $aTrac[$i] For $j = $i - 1 To $iStart Step -1 If ___NumCompare($aArray[$iTmp], $aArray[$aTrac[$j]]) >= 0 Then ExitLoop $aTrac[$j + 1] = $aTrac[$j] Next $aTrac[$j + 1] = $iTmp Next EndSwitch Return EndIf ; QuickSort Local $L = $iStart, $R = $iEnd, $vPivot = $aArray[$aTrac[Int(($iStart + $iEnd) / 2)]] Do If $iAlgorithm = 0 Then ; lexical While StringCompare($aArray[$aTrac[$L]], $vPivot) < 0 $L += 1 WEnd While StringCompare($aArray[$aTrac[$R]], $vPivot) > 0 $R -= 1 WEnd ElseIf $iAlgorithm = 2 Then ; numeric strict [strings not allowed] While $aArray[$aTrac[$L]] < $vPivot $L += 1 WEnd While $aArray[$aTrac[$R]] > $vPivot $R -= 1 WEnd ElseIf $iAlgorithm = 256 Then ; numeric greedy [includes decimal strings] While ___NumCompare($aArray[$aTrac[$L]], $vPivot) < 0 $L += 1 WEnd While ___NumCompare($aArray[$aTrac[$R]], $vPivot) > 0 $R -= 1 WEnd EndIf If $L <= $R Then ; Swap $iTmp = $aTrac[$L] $aTrac[$L] = $aTrac[$R] $aTrac[$R] = $iTmp $L += 1 $R -= 1 EndIf Until $L > $R __QuickSortXD($aArray, $aTrac, $iStart, $R, $iAlgorithm) __QuickSortXD($aArray, $aTrac, $L, $iEnd, $iAlgorithm) EndFunc ;==>__QuickSortXD ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: ReDim arrays of different dimensions. ; ============================================================================================================================== Func __ResetBounds(ByRef $aArray, $aBound) Switch $aBound[0] Case 1 ReDim $aArray[$aBound[1]] Case 2 ReDim $aArray[$aBound[1]][$aBound[2]] Case 3 ReDim $aArray[$aBound[1]][$aBound[2]][$aBound[3]] Case 4 ReDim $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]] Case 5 ReDim $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]] Case 6 ReDim $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]][$aBound[6]] Case 7 ReDim $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]][$aBound[6]][$aBound[7]] Case 8 ReDim $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]][$aBound[6]][$aBound[7]][$aBound[8]] Case 9 ReDim $aArray[$aBound[1]][$aBound[2]][$aBound[3]][$aBound[4]][$aBound[5]][$aBound[6]][$aBound[7]][$aBound[8]][$aBound[9]] EndSwitch EndFunc ;==>__ResetBounds ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Description ...: Helper function: reverses a 1D array. ; Author ........: czardas ; =============================================================================================================================== Func ___Reverse1D(ByRef $aArray, $iStart, $iStop) Local $vTemp While $iStop > $iStart $vTemp = $aArray[$iStart] $aArray[$iStart] = $aArray[$iStop] $aArray[$iStop] = $vTemp $iStart += 1 $iStop -= 1 WEnd EndFunc ;==>___Reverse1D ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with _ArraySearch - searches through a 1D array. ; ============================================================================================================================== Func ___Search1D($aArray, $vSearchTerm, $bCaseSense, $iAlgo) Local $iItems = 0 Switch $iAlgo Case 1 ; find exact match If $bCaseSense Then ; case-sensitive For $i = 0 To UBound($aArray) - 1 If $aArray[$i] == $vSearchTerm Then $aArray[$iItems] = $aArray[$i] $iItems += 1 EndIf Next Else ; optimal [using a second loop avoids using a conditional within the loop] For $i = 0 To UBound($aArray) - 1 If $aArray[$i] = $vSearchTerm Then $aArray[$iItems] = $aArray[$i] $iItems += 1 EndIf Next EndIf Case 2 ; find a string within a string For $i = 0 To UBound($aArray) - 1 If StringInStr($aArray[$i], $vSearchTerm, $bCaseSense) Then $aArray[$iItems] = $aArray[$i] $iItems += 1 EndIf Next Case 3 ; find a word within text Local $sPattern = $bCaseSense ? '(*UCP)(\A|[^[:alnum:]])(\Q' : '(*UCP)(?i)(\A|[^[:alnum:]])(\Q' For $i = 0 To UBound($aArray) - 1 If StringRegExp(StringReplace($aArray[$i], '\E', $g__ARRWSHOP_SUB, 0, 1), $sPattern & StringReplace($vSearchTerm, '\E', $g__ARRWSHOP_SUB, 0, 1) & '\E)(\z|[^[:alnum:]])') Then $aArray[$iItems] = $aArray[$i] $iItems += 1 EndIf Next EndSwitch If Not $iItems Then Return SetError(1) ReDim $aArray[$iItems] Return $aArray EndFunc ;==>___Search1D ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with numeric sort. [numbers other than -1.#IND = OK] ; ============================================================================================================================== Func __Separate1D(ByRef $aArray, $iStart, $iEnd) Local $vTemp, $iItems = 0 For $i = $iStart To $iEnd If IsNumber($aArray[$i]) And Not ($aArray[$i] == '-1.#IND') Then $vTemp = $aArray[$iStart + $iItems] $aArray[$iStart + $iItems] = $aArray[$i] $aArray[$i] = $vTemp $iItems += 1 EndIf Next Return $iItems ; the number of numeric items EndFunc ;==>__Separate1D ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with numeric sort - separates numbers from strings. [numbers (!-1.#IND) = OK, decimal strings = OK] ; Author ........: czardas ; ============================================================================================================================== Func __Separate256(ByRef $aArray, ByRef $aTrac, $iStart, $iEnd) Local $vTemp, $iItems = 0 For $i = $iStart To $iEnd If (IsNumber($aArray[$aTrac[$i]]) And Not ($aArray[$aTrac[$i]] == '-1.#IND')) Or IsString($aArray[$aTrac[$i]]) * StringRegExp($aArray[$aTrac[$i]], '\A\h*[\+\-]?\h*(\d*\.?\d+|\d+\.)\h*\z') Then $vTemp = $aTrac[$iStart + $iItems] $aTrac[$iStart + $iItems] = $aTrac[$i] $aTrac[$i] = $vTemp If IsString($aArray[$aTrac[$iStart + $iItems]]) Then $aArray[$aTrac[$iStart + $iItems]] = ___FormatNum(StringStripWS($aArray[$aTrac[$iStart + $iItems]], 8)) ; format numeric strings ready for comparison $iItems += 1 EndIf Next Return $iItems ; the number of numeric items EndFunc ;==>__Separate256 ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: For use with numeric sort - separates numbers from strings. [numbers (! -1.#IND) = OK] ; Author ........: czardas ; ============================================================================================================================== Func __SeparateXD(ByRef $aArray, ByRef $aTrac, $iStart, $iEnd) Local $vTemp, $iItems = 0 For $i = $iStart To $iEnd If IsNumber($aArray[$aTrac[$i]]) And Not ($aArray[$aTrac[$i]] == '-1.#IND') Then $vTemp = $aTrac[$iStart + $iItems] $aTrac[$iStart + $iItems] = $aTrac[$i] $aTrac[$i] = $vTemp $iItems += 1 EndIf Next Return $iItems ; the number of numeric items EndFunc ;==>__SeparateXD ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: Shuffle a one dimensional array. ; ============================================================================================================================== Func __Shuffle1D(ByRef $aArray) Local $vTemp, $iRandom, $iBound = UBound($aArray) - 1 For $i = 0 To $iBound $iRandom = Random(0, $iBound, 1) $vTemp = $aArray[$i] $aArray[$i] = $aArray[$iRandom] $aArray[$iRandom] = $vTemp Next EndFunc ;==>__Shuffle1D ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Description ...: Shuffle a multidimensional array. ; ============================================================================================================================== Func __ShuffleXD(ByRef $aArray, $aBound) ReDim $aBound[10] ; [could possibly be dealt with earlier by the call to _GetBounds] Local $vTemp, $R[10] ; [random indices] For $9 = 0 To $aBound[9] For $8 = 0 To $aBound[8] For $7 = 0 To $aBound[7] For $6 = 0 To $aBound[6] For $5 = 0 To $aBound[5] For $4 = 0 To $aBound[4] For $3 = 0 To $aBound[3] For $2 = 0 To $aBound[2] For $1 = 0 To $aBound[1] For $i = 1 To $aBound[0] $R[$i] = Random(0, $aBound[$i], 1) Next Switch $aBound[0] Case 1 $vTemp = $aArray[$1] $aArray[$1] = $aArray[$R[1]] $aArray[$R[1]] = $vTemp Case 2 $vTemp = $aArray[$1][$2] $aArray[$1][$2] = $aArray[$R[1]][$R[2]] $aArray[$R[1]][$R[2]] = $vTemp Case 3 $vTemp = $aArray[$1][$2][$3] $aArray[$1][$2][$3] = $aArray[$R[1]][$R[2]][$R[3]] $aArray[$R[1]][$R[2]][$R[3]] = $vTemp Case 4 $vTemp = $aArray[$1][$2][$3][$4] $aArray[$1][$2][$3][$4] = $aArray[$R[1]][$R[2]][$R[3]][$R[4]] $aArray[$R[1]][$R[2]][$R[3]][$R[4]] = $vTemp Case 5 $vTemp = $aArray[$1][$2][$3][$4][$5] $aArray[$1][$2][$3][$4][$5] = $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]] $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]] = $vTemp Case 6 $vTemp = $aArray[$1][$2][$3][$4][$5][$6] $aArray[$1][$2][$3][$4][$5][$6] = $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]][$R[6]] $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]][$R[6]] = $vTemp Case 7 $vTemp = $aArray[$1][$2][$3][$4][$5][$6][$7] $aArray[$1][$2][$3][$4][$5][$6][$7] = $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]][$R[6]][$R[7]] $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]][$R[6]][$R[7]] = $vTemp Case 8 $vTemp = $aArray[$1][$2][$3][$4][$5][$6][$7][$8] $aArray[$1][$2][$3][$4][$5][$6][$7][$8] = $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]][$R[6]][$R[7]][$R[8]] $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]][$R[6]][$R[7]][$R[8]] = $vTemp Case 9 $vTemp = $aArray[$1][$2][$3][$4][$5][$6][$7][$8][$9] $aArray[$1][$2][$3][$4][$5][$6][$7][$8][$9] = $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]][$R[6]][$R[7]][$R[8]][$R[9]] $aArray[$R[1]][$R[2]][$R[3]][$R[4]][$R[5]][$R[6]][$R[7]][$R[8]][$R[9]] = $vTemp EndSwitch Next Next Next Next Next Next Next Next Next EndFunc ;==>__ShuffleXD ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Description ...: Helper function for populating 1D arrays. [knight's tour type algorithm] ; Author ........: czardas ; =============================================================================================================================== Func __TagSortSwap(ByRef $aArray, ByRef $aTrac, $iStart, $iEnd) Local $vFirst, $i, $iNext For $iInit = $iStart To $iEnd ; initialize each swap sequence If $aTrac[$iInit] <> $iInit Then ; elements will now be swapped in a sequence $i = $iInit ; set the current index to the start of the sequence $vFirst = $aArray[$i] ; copy data [although we don't know where to put it yet] Do $aArray[$i] = $aArray[$aTrac[$i]] ; overwrite each element in the sequence $iNext = $aTrac[$i] ; get the next index in the sequence $aTrac[$i] = $i ; set to ignore overwritten elements on subsequent encounters $i = $iNext ; follow the trail as far as it goes [index could be higher or lower] Until $aTrac[$i] = $iInit ; all sequences end at this juncture $aArray[$i] = $vFirst ; now we know where to put the initial element we copied earlier $aTrac[$i] = $i ; set to ignore on subsequent encounters [as above] EndIf Next EndFunc ;==>__TagSortSwap ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Description ...: Helper function for populating 2D arrays. [knight's tour type algorithm] ; Author ........: czardas ; =============================================================================================================================== Func __TagSortSwapXD(ByRef $aArray, ByRef $aTrac, $iStart, $iEnd) Local $iCols = UBound($aArray, 2), $aFirst[$iCols], $i, $iNext For $iInit = $iStart To $iEnd ; initialize each potential overwrite sequence [separate closed system] If $aTrac[$iInit] <> $iInit Then ; rows will now be overwritten in accordance with tracking information $i = $iInit ; set the current row as the start of the sequence For $j = 0 To $iCols - 1 $aFirst[$j] = $aArray[$i][$j] ; copy the first row [although we don't know where to put it yet] Next Do For $j = 0 To $iCols - 1 $aArray[$i][$j] = $aArray[$aTrac[$i]][$j] ; overwrite each row [following the trail] Next $iNext = $aTrac[$i] ; get the index of the next row in the sequence $aTrac[$i] = $i ; set to ignore rows already processed [may be needed once, or not at all] $i = $iNext ; follow the trail as far as it goes [indices could be higher or lower] Until $aTrac[$i] = $iInit ; all tracking sequences end at this juncture For $j = 0 To $iCols - 1 $aArray[$i][$j] = $aFirst[$j] ; now we know where to put the initial row we copied earlier Next $aTrac[$i] = $i ; set to ignore rows already processed [as above] EndIf Next EndFunc ;==>__TagSortSwapXD #EndRegion - Miscellaneous #Region - Remote Loops ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: ___Flood1D, ___Flood2D, ___Flood3D, ___Flood4D, ___Flood5D, ___Flood6D, ___Flood7D, ___Flood8D, ___Flood9D ; Description ...: Flood the region of a multidimensional array defined by the sub-index within the specified dimension. ; Syntax.........: ___FloodXD($aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) ; Parameters.....; $aTarget - Array to populate. ; $aBound - Array containing the bounds of each dimension. ; $iDimension - Integer value defining the dimension. ; $iSubIndex - Integer value of the sub-index within the dimension. ; $iFrom - [Hidden] Integer value defining a sub-index within $aSource. ; $aSource - [Hidden] Array containing data which may be used to populate the target array. ; $sTransfer - String of instructions used to acquire data. ; Return values .: [ByRef] The target array after the region has been flooded. ; Author ........: czardas ; Comments ......; Using remote loops cuts out several hundred lines of duplicated code. ; Recursively targeting a custom function, whichever dimension, is straight forward and reasonably optimal. ; The approach may introduce a small circumstantial speed deficit, which seems a fair trade-off. ; The sequence of loops runs backwards: optimized for higher dimensions with less bounds. ; Only the elements associated with the specified sub-index within the defined dimension are overwritten. ; Setting $iDimension to 0 causes the functions to overwrite all the elements within the target array. ; The $sTransfer parameter must be runnable code. ; Unused hidden parameters should be passed as empty strings. ; $aBound[0] can also be used as a wild card. ; ============================================================================================================================== Func ___Flood1D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) ; [still experimental] #forceref $iDimension, $iFrom, $aSource ; $iDimension would normally not apply here (special case) Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] ; loop iteration count [or indices of higher dimensions within the source array] For $a[1] = $iSubIndex To $aBound[1] ; from the start to the bounds of the 1st dimension (special case) ; only one operation is needed in this special case $aTarget[$a[1]] = Execute($sTransfer) ; hidden parameters may appear in the code being executed Next EndFunc ;==>___Flood1D ; ============================================================================================================================== ; the following functions are slightly different ; ============================================================================================================================== Func ___Flood2D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) #forceref $iFrom, $aSource ; hidden parameters Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] ; loop iteration count [or indices of higher dimensions within the source array] For $a[2] = 0 To $aBound[2] For $a[1] = 0 To $aBound[1] $a[$iDimension] = $iSubIndex ; override the iteration count (fast method) - $a[0] has no influence $aTarget[$a[1]][$a[2]] = Execute($sTransfer) ; hidden parameters may appear in the code being executed Next Next EndFunc ;==>___Flood2D ; ============================================================================================================================== ; see previous description and comments ; ============================================================================================================================== Func ___Flood3D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) #forceref $iFrom, $aSource ; as above Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] ; as above For $a[3] = 0 To $aBound[3] For $a[2] = 0 To $aBound[2] For $a[1] = 0 To $aBound[1] $a[$iDimension] = $iSubIndex ; as above $aTarget[$a[1]][$a[2]][$a[3]] = Execute($sTransfer) ; as above Next Next Next EndFunc ;==>___Flood3D ; ============================================================================================================================== ; see previous description and comments ; ============================================================================================================================== Func ___Flood4D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) #forceref $iFrom, $aSource Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] For $a[4] = 0 To $aBound[4] For $a[3] = 0 To $aBound[3] For $a[2] = 0 To $aBound[2] For $a[1] = 0 To $aBound[1] $a[$iDimension] = $iSubIndex $aTarget[$a[1]][$a[2]][$a[3]][$a[4]] = Execute($sTransfer) Next Next Next Next EndFunc ;==>___Flood4D ; ============================================================================================================================== ; see previous description and comments ; ============================================================================================================================== Func ___Flood5D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) #forceref $iFrom, $aSource Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] For $a[5] = 0 To $aBound[5] For $a[4] = 0 To $aBound[4] For $a[3] = 0 To $aBound[3] For $a[2] = 0 To $aBound[2] For $a[1] = 0 To $aBound[1] $a[$iDimension] = $iSubIndex $aTarget[$a[1]][$a[2]][$a[3]][$a[4]][$a[5]] = Execute($sTransfer) Next Next Next Next Next EndFunc ;==>___Flood5D ; ============================================================================================================================== ; see previous description and comments ; ============================================================================================================================== Func ___Flood6D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) #forceref $iFrom, $aSource Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] For $a[6] = 0 To $aBound[6] For $a[5] = 0 To $aBound[5] For $a[4] = 0 To $aBound[4] For $a[3] = 0 To $aBound[3] For $a[2] = 0 To $aBound[2] For $a[1] = 0 To $aBound[1] $a[$iDimension] = $iSubIndex $aTarget[$a[1]][$a[2]][$a[3]][$a[4]][$a[5]][$a[6]] = Execute($sTransfer) Next Next Next Next Next Next EndFunc ;==>___Flood6D ; ============================================================================================================================== ; see previous description and comments ; ============================================================================================================================== Func ___Flood7D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) #forceref $iFrom, $aSource Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] For $a[7] = 0 To $aBound[7] For $a[6] = 0 To $aBound[6] For $a[5] = 0 To $aBound[5] For $a[4] = 0 To $aBound[4] For $a[3] = 0 To $aBound[3] For $a[2] = 0 To $aBound[2] For $a[1] = 0 To $aBound[1] $a[$iDimension] = $iSubIndex $aTarget[$a[1]][$a[2]][$a[3]][$a[4]][$a[5]][$a[6]][$a[7]] = Execute($sTransfer) Next Next Next Next Next Next Next EndFunc ;==>___Flood7D ; ============================================================================================================================== ; see previous description and comments ; ============================================================================================================================== Func ___Flood8D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) #forceref $iFrom, $aSource Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] For $a[8] = 0 To $aBound[8] For $a[7] = 0 To $aBound[7] For $a[6] = 0 To $aBound[6] For $a[5] = 0 To $aBound[5] For $a[4] = 0 To $aBound[4] For $a[3] = 0 To $aBound[3] For $a[2] = 0 To $aBound[2] For $a[1] = 0 To $aBound[1] $a[$iDimension] = $iSubIndex $aTarget[$a[1]][$a[2]][$a[3]][$a[4]][$a[5]][$a[6]][$a[7]][$a[8]] = Execute($sTransfer) Next Next Next Next Next Next Next Next EndFunc ;==>___Flood8D ; ============================================================================================================================== ; see previous description and comments ; ============================================================================================================================== Func ___Flood9D(ByRef $aTarget, $aBound, $iDimension, $iSubIndex, $iFrom, $aSource, $sTransfer) #forceref $iFrom, $aSource Local $a[10] = ['', 0, 0, 0, 0, 0, 0, 0, 0, 0] For $a[9] = 0 To $aBound[9] For $a[8] = 0 To $aBound[8] For $a[7] = 0 To $aBound[7] For $a[6] = 0 To $aBound[6] For $a[5] = 0 To $aBound[5] For $a[4] = 0 To $aBound[4] For $a[3] = 0 To $aBound[3] For $a[2] = 0 To $aBound[2] For $a[1] = 0 To $aBound[1] $a[$iDimension] = $iSubIndex $aTarget[$a[1]][$a[2]][$a[3]][$a[4]][$a[5]][$a[6]][$a[7]][$a[8]][$a[9]] = Execute($sTransfer) Next Next Next Next Next Next Next Next Next EndFunc ;==>___Flood9D #EndRegion - Remote Loops #Au3Stripper_On This UDF and all examples can be downloaded in the zip archive below.
    ArrayWorkshop.7z
    Related Topics : https://www.autoitscript.com/forum/topic/179779-_arraydeclarefromstring-_arraytodeclarationstring-_arrayshufflemultidim-_arraycompare-_arrayenumvalues-_arrayassign/ - some rather nice functions.
    Interesting link from @jchd : https://reference.wolfram.com/language/guide/ListManipulation.html - more stuff than I am ever likely to use.
  19. Like
    czardas got a reaction from ss26 in Multi dimensional arrays & stringsplit() function   
    Here's a method:


    #include <Array.au3> ; Needed for _ArrayDisplay $sTest = "0,1,2|3,4,5,6|7,8,9" $a2D = StringTo2dArray($sTest, "|", ",") _ArrayDisplay($a2D) Func StringTo2dArray($sString, $sDelim4Rows, $sDelim4Cols) Local $aArray = StringSplit($sString, $sDelim4Rows, 2) ; Split to get rows Local $iBound = UBound($aArray) Local $aRet[$iBound][2], $aTemp, $iOverride = 0 For $i = 0 To $iBound -1 $aTemp = StringSplit($aArray[$i], $sDelim4Cols) ; Split to get row items If Not @error Then If $aTemp[0] > $iOverride Then $iOverride = $aTemp[0] ReDim $aRet[$iBound][$iOverride] ; Add columns to accomodate more items EndIf EndIf For $j = 1 To $aTemp[0] $aRet[$i][$j -1] = $aTemp[$j] ; Populate each row Next Next If $iOverride <= 1 Then $aRet = $aArray ; Array contains single row or column Return $aRet EndFunc ;==> StringTo2dArray
  20. Thanks
    czardas got a reaction from BigDaddyO in CSVSplit   
    This UDF introduces one or two small tweaks to the functions I posted in Snippet Dump (nothing major). Terminating the CSV with a break is now the default behaviour (although I think it's just a lazy convention). The inclusion of a new function _ArrayToSubItemCSV() got me excited. Thanks for an awesome idea Chimaera. I have included the option to sort the returned CSV formatted strings ascending using any column.
    ;
    #include-once #include <Array.au3> ; #INDEX# ======================================================================================================================= ; Title .........: CSVSplit ; AutoIt Version : 3.3.8.1 ; Language ......: English ; Description ...: CSV related functions ; Notes .........: CSV format does not have a general standard format, however these functions allow some flexibility. ;                  The default behaviour of the functions applies to the most common formats used in practice. ; Author(s) .....: czardas ; =============================================================================================================================== ; #CURRENT# ===================================================================================================================== ;_ArrayToCSV ;_ArrayToSubItemCSV ;_CSVSplit ; =============================================================================================================================== ; #INTERNAL_USE_ONLY#============================================================================================================ ; __GetSubstitute ; =============================================================================================================================== ; #FUNCTION# ==================================================================================================================== ; Name...........: _ArrayToCSV ; Description ...: Converts a two dimensional array to CSV format ; Syntax.........: _ArrayToCSV ( $aArray [, $sDelim [, $sNewLine [, $bFinalBreak ]]] ) ; Parameters ....: $aArray      - The array to convert ;                  $sDelim      - Optional - Delimiter set to comma by default (see comments) ;                  $sNewLine    - Optional - New Line set to @LF by default (see comments) ;                  $bFinalBreak - Set to true in accordance with common practice => CSV Line termination ; Return values .: Success  - Returns a string in CSV format ;                  Failure  - Sets @error to: ;                 |@error = 1 - First parameter is not a valid array ;                 |@error = 2 - Second parameter is not a valid string ;                 |@error = 3 - Third parameter is not a valid string ;                 |@error = 4 - 2nd and 3rd parameters must be different characters ; Author ........: czardas ; Comments ......; One dimensional arrays are returned as multiline text (without delimiters) ;                ; Some users may need to set the second parameter to semicolon to return the prefered CSV format ;                ; To convert to TSV use @TAB for the second parameter ;                ; Some users may wish to set the third parameter to @CRLF ; =============================================================================================================================== Func _ArrayToCSV($aArray, $sDelim = Default, $sNewLine = Default, $bFinalBreak = True)     If Not IsArray($aArray) Or Ubound($aArray, 0) > 2 Or Ubound($aArray) = 0 Then Return SetError(1, 0 ,"")     If $sDelim = Default Then $sDelim = ","     If $sDelim = "" Then Return SetError(2, 0 ,"")     If $sNewLine = Default Then $sNewLine = @LF     If $sNewLine = "" Then Return SetError(3, 0 ,"")     If $sDelim = $sNewLine Then Return SetError(4, 0, "")     Local $iRows = UBound($aArray), $sString = ""     If Ubound($aArray, 0) = 2 Then ; Check if the array has two dimensions         Local $iCols = UBound($aArray, 2)         For $i = 0 To $iRows -1             For $j = 0 To $iCols -1                 If StringRegExp($aArray[$i][$j], '["\r\n' & $sDelim & ']') Then                     $aArray[$i][$j] = '"' & StringReplace($aArray[$i][$j], '"', '""') & '"'                 EndIf                 $sString &= $aArray[$i][$j] & $sDelim             Next             $sString = StringTrimRight($sString, StringLen($sDelim)) & $sNewLine         Next     Else ; The delimiter is not needed         For $i = 0 To $iRows -1             If StringRegExp($aArray[$i], '["\r\n' & $sDelim & ']') Then                 $aArray[$i] = '"' & StringReplace($aArray[$i], '"', '""') & '"'             EndIf             $sString &= $aArray[$i] & $sNewLine         Next     EndIf     If Not $bFinalBreak Then $sString = StringTrimRight($sString, StringLen($sNewLine)) ; Delete any newline characters added to the end of the string     Return $sString EndFunc ;==> _ArrayToCSV ; #FUNCTION# ==================================================================================================================== ; Name...........: _ArrayToSubItemCSV ; Description ...: Converts an array to multiple CSV formated strings based on the content of the selected column ; Syntax.........: _ArrayToSubItemCSV($aCSV, $iCol [, $sDelim [, $bHeaders [, $iSortCol [, $bAlphaSort ]]]]) ; Parameters ....: $aCSV       - The array to parse ;                  $iCol       - Array column used to search for unique content ;                  $sDelim     - Optional - Delimiter set to comma by default ;                  $bHeaders   - Include csv column headers - Default = False ;                  $iSortCol   - The column to sort on for each new CSV (sorts ascending) - Default = False ;                  $bAlphaSort - If set to true, sorting will be faster but numbers won't always appear in order of magnitude. ; Return values .: Success   - Returns a two dimensional array - col 0 = subitem name, col 1 = CSV data ;                  Failure   - Returns an empty string and sets @error to: ;                 |@error = 1 - First parameter is not a 2D array ;                 |@error = 2 - Nothing to parse ;                 |@error = 3 - Invalid second parameter Column number ;                 |@error = 4 - Invalid third parameter - Delimiter is an empty string ;                 |@error = 5 - Invalid fourth parameter - Sort Column number is out of range ; Author ........: czardas ; Comments ......; @CRLF is used for line breaks in the returned array of CSV strings. ;                ; Data in the sorting column is automatically assumed to contain numeric values. ;                ; Setting $iSortCol equal to $iCol will return csv rows in their original ordered sequence. ; =============================================================================================================================== Func _ArrayToSubItemCSV($aCSV, $iCol, $sDelim = Default, $bHeaders = Default, $iSortCol = Default, $bAlphaSort = Default)     If Not IsArray($aCSV) Or UBound($aCSV, 0) <> 2 Then Return SetError(1, 0, "") ; Not a 2D array     Local $iBound = UBound($aCSV), $iNumCols = UBound($aCSV, 2)     If $iBound < 2 Then Return SetError(2, 0, "") ; Nothing to parse     If IsInt($iCol) = 0 Or $iCol < 0 Or $iCol > $iNumCols -1 Then Return SetError(3, 0, "") ; $iCol is out of range     If $sDelim = Default Then $sDelim = ","     If $sDelim = "" Then Return SetError(4, 0, "") ; Delimiter can not be an empty string     If $bHeaders = Default Then $bHeaders = False     If $iSortCol = Default Or $iSortCol == False Then $iSortCol = -1     If IsInt($iSortCol) = 0 Or $iSortCol < -1 Or $iSortCol > $iNumCols -1 Then Return SetError(5, 0, "") ; $iSortCol is out of range     If $bAlphaSort = Default Then $bAlphaSort = False     Local $iStart = 0     If $bHeaders Then         If $iBound = 2 Then Return SetError(2, 0, "") ; Nothing to parse         $iStart = 1     EndIf     Local $sTestItem, $iNewCol = 0     If $iSortCol <> -1 And ($bAlphaSort = False Or $iSortCol = $iCol) Then ; In this case we need an extra Column for sorting         ReDim $aCSV [$iBound][$iNumCols +1]         ; Populate column         If $iSortCol = $iCol Then             For $i = $iStart To $iBound -1                 $aCSV[$i][$iNumCols] = $i             Next         Else             For $i = $iStart To $iBound -1                 $sTestItem = StringRegExpReplace($aCSV[$i][$iSortCol], "\A\h+", "") ; Remove leading horizontal WS                 If StringIsInt($sTestItem) Or StringIsFloat($sTestItem) Then                     $aCSV[$i][$iNumCols] = Number($sTestItem)                 Else                     $aCSV[$i][$iNumCols] = $aCSV[$i][$iSortCol]                 EndIf             Next         EndIf         $iNewCol = 1         $iSortCol = $iNumCols     EndIf     _ArraySort($aCSV, 0, $iStart, 0, $iCol) ; Sort on the selected column     Local $aSubItemCSV[$iBound][2], $iItems = 0, $aTempCSV[1][$iNumCols + $iNewCol], $iTempIndex     $sTestItem = Not $aCSV[$iBound -1][$iCol]     For $i = $iBound -1 To $iStart Step -1         If $sTestItem <> $aCSV[$i][$iCol] Then ; Start a new csv instance             If $iItems > 0 Then ; Write to main array                 ReDim $aTempCSV[$iTempIndex][$iNumCols + $iNewCol]                 If $iSortCol <> -1 Then _ArraySort($aTempCSV, 0, $iStart, 0, $iSortCol)                 If $iNewCol Then ReDim $aTempCSV[$iTempIndex][$iNumCols]                 $aSubItemCSV[$iItems -1][0] = $sTestItem                 $aSubItemCSV[$iItems -1][1] = _ArrayToCSV($aTempCSV, $sDelim, @CRLF)             EndIf             ReDim $aTempCSV[$iBound][$iNumCols + $iNewCol] ; Create new csv template             $iTempIndex = 0             $sTestItem = $aCSV[$i][$iCol]             If $bHeaders Then                 For $j = 0 To $iNumCols -1                     $aTempCSV[0][$j] = $aCSV[0][$j]                 Next                 $iTempIndex = 1             EndIf             $iItems += 1         EndIf         For $j = 0 To $iNumCols + $iNewCol -1 ; Continue writing to csv             $aTempCSV[$iTempIndex][$j] = $aCSV[$i][$j]         Next         $iTempIndex += 1     Next     ReDim $aTempCSV[$iTempIndex][$iNumCols + $iNewCol]     If $iSortCol <> -1 Then _ArraySort($aTempCSV, 0, $iStart, 0, $iSortCol)     If $iNewCol Then ReDim $aTempCSV[$iTempIndex][$iNumCols]     $aSubItemCSV[$iItems -1][0] = $sTestItem     $aSubItemCSV[$iItems -1][1] = _ArrayToCSV($aTempCSV, $sDelim, @CRLF)     ReDim $aSubItemCSV[$iItems][2]     Return $aSubItemCSV EndFunc ;==> _ArrayToSubItemCSV ; #FUNCTION# ==================================================================================================================== ; Name...........: _CSVSplit ; Description ...: Converts a string in CSV format to a two dimensional array (see comments) ; Syntax.........: CSVSplit ( $aArray [, $sDelim ] ) ; Parameters ....: $aArray  - The array to convert ;                  $sDelim  - Optional - Delimiter set to comma by default (see 2nd comment) ; Return values .: Success  - Returns a two dimensional array or a one dimensional array (see 1st comment) ;                  Failure  - Sets @error to: ;                 |@error = 1 - First parameter is not a valid string ;                 |@error = 2 - Second parameter is not a valid string ;                 |@error = 3 - Could not find suitable delimiter replacements ; Author ........: czardas ; Comments ......; Returns a one dimensional array if the input string does not contain the delimiter string ;                ; Some CSV formats use semicolon as a delimiter instead of a comma ;                ; Set the second parameter to @TAB To convert to TSV ; =============================================================================================================================== Func _CSVSplit($string, $sDelim = ",") ; Parses csv string input and returns a one or two dimensional array     If Not IsString($string) Or $string = "" Then Return SetError(1, 0, 0) ; Invalid string     If Not IsString($sDelim) Or $sDelim = "" Then Return SetError(2, 0, 0) ; Invalid string     $string = StringRegExpReplace($string, "[\r\n]+\z", "") ; [Line Added] Remove training breaks     Local $iOverride = 63743, $asDelim[3] ; $asDelim => replacements for comma, new line and double quote     For $i = 0 To 2         $asDelim[$i] = __GetSubstitute($string, $iOverride) ; Choose a suitable substitution character         If @error Then Return SetError(3, 0, 0) ; String contains too many unsuitable characters     Next     $iOverride = 0     Local $aArray = StringRegExp($string, '\A[^"]+|("+[^"]+)|"+\z', 3) ; Split string using double quotes delim - largest match     $string = ""     Local $iBound = UBound($aArray)     For $i = 0 To $iBound -1         $iOverride += StringInStr($aArray[$i], '"', 0, -1) ; Increment by the number of adjacent double quotes per element         If Mod ($iOverride +2, 2) = 0 Then ; Acts as an on/off switch             $aArray[$i] = StringReplace($aArray[$i], $sDelim, $asDelim[0]) ; Replace comma delimeters             $aArray[$i] = StringRegExpReplace($aArray[$i], "(\r\n)|[\r\n]", $asDelim[1]) ; Replace new line delimeters         EndIf         $aArray[$i] = StringReplace($aArray[$i], '""', $asDelim[2]) ; Replace double quote pairs         $aArray[$i] = StringReplace($aArray[$i], '"', '') ; Delete enclosing double quotes - not paired         $aArray[$i] = StringReplace($aArray[$i], $asDelim[2], '"') ; Reintroduce double quote pairs as single characters         $string &= $aArray[$i] ; Rebuild the string, which includes two different delimiters     Next     $iOverride = 0     $aArray = StringSplit($string, $asDelim[1], 2) ; Split to get rows     $iBound = UBound($aArray)     Local $aCSV[$iBound][2], $aTemp     For $i = 0 To $iBound -1         $aTemp = StringSplit($aArray[$i], $asDelim[0]) ; Split to get row items         If Not @error Then             If $aTemp[0] > $iOverride Then                 $iOverride = $aTemp[0]                 ReDim $aCSV[$iBound][$iOverride] ; Add columns to accomodate more items             EndIf         EndIf         For $j = 1 To $aTemp[0]             If StringLen($aTemp[$j]) Then                 If Not StringRegExp($aTemp[$j], '[^"]') Then ; Field only contains double quotes                     $aTemp[$j] = StringTrimLeft($aTemp[$j], 1) ; Delete enclosing double quote single char                 EndIf                 $aCSV[$i][$j -1] = $aTemp[$j] ; Populate each row             EndIf         Next     Next     If $iOverride > 1 Then         Return $aCSV ; Multiple Columns     Else         For $i = 0 To $iBound -1             If StringLen($aArray[$i]) And (Not StringRegExp($aArray[$i], '[^"]')) Then ; Only contains double quotes                 $aArray[$i] = StringTrimLeft($aArray[$i], 1) ; Delete enclosing double quote single char             EndIf         Next         Return $aArray ; Single column     EndIf EndFunc ;==> _CSVSplit ; #INTERNAL_USE_ONLY# =========================================================================================================== ; Name...........: __GetSubstitute ; Description ...: Searches for a character to be used for substitution, ie one not contained within the input string ; Syntax.........: __GetSubstitute($string, ByRef $iCountdown) ; Parameters ....: $string   - The string of characters to avoid ;                  $iCountdown - The first code point to begin checking ; Return values .: Success   - Returns a suitable substitution character not found within the first parameter ;                  Failure   - Sets @error to 1 => No substitution character available ; Author ........: czardas ; Comments ......; This function is connected to the function _CSVSplit and was not intended for general use ;                  $iCountdown is returned ByRef to avoid selecting the same character on subsequent calls to this function ;                  Initially $iCountown should be passed with a value = 63743 ; =============================================================================================================================== Func __GetSubstitute($string, ByRef $iCountdown)     If $iCountdown < 57344 Then Return SetError(1, 0, "") ; Out of options     Local $sTestChar     For $i = $iCountdown To 57344 Step -1         $sTestChar = ChrW($i)         $iCountdown -= 1         If Not StringInStr($string, $sTestChar) Then             Return $sTestChar         EndIf     Next     Return SetError(1, 0, "") ; Out of options EndFunc ;==> __GetSubstitute ;
    See examples in post #8 below.
    ;
  21. Thanks
    czardas got a reaction from ScrapeYourself in ArrayWorkshop   
    @UEZ I have put all the examples in a single file and added an attachment to the first post. Thanks for looking.
  22. Like
    czardas got a reaction from robertocm in Convert string to array   
    Use StringSplit.
    ;
    #include <Array.au3> Local $sString = "", $iMax = 10, $sDelim = "," For $i = 1 To $iMax $sString &= "test" & $i & $sDelim Next $sString = StringTrimRight($sString, 1) Local $aArray = StringSplit($sString, $sDelim, 2) _ArrayDisplay($aArray)
  23. Like
    czardas got a reaction from Earthshine in Fraction   
    Thanks Draygoes, the idea is to keep the original information intact and basically do maths with whole numbers. This way absolute accuracy is pretty much guaranteed, even when the whole numbers form part of a fraction. I think it's useful for small closed systems which use a lot of calculation. Only at the end of a number of precise calculations should the result be converted back to a float. This is the best way to maintain accuracy throughout a long winded calculation - where small inacuracies will have a knock on effect.
  24. Like
    czardas got a reaction from Earthshine in Fraction   
    Perform accurate division using real fractions and convert floating point numbers to fractions.

    Floating point arithmetic often introduces small inaccuracies. Most of the time this is not a problem, but sometimes it can be. As a workaround, many programmers decide to only use whole numbers for specific calculations - ones which need to return exact results. The fraction 1/3 cannot be represented using a float. You would need a decimal (or floating point variant) of infinite length to give an accurate representation of 1/3. Working with fractions is not entirely straight forward, and at times this can be a little frustrating.
    With the functions in this library, there is potential for internal calculations to produce numbers which are out of range. Not withstanding oversights: both input numbers should be less than 16 digits and the difference between the denominator and the divisor should be an order of magnitude less than 10^16, otherwise the function will return an error.
    With the exception of Fraction(), all included functions take array parameters. All return values are either arrays or boolean values. Documentation and examples pending.
    NEW VERSION requires operator64.au3 found here: https://www.autoitscript.com/forum/topic/176620-operator64/
    This is an alpha release -see post 33.
    #include-once #include 'operator64.au3' ; #INDEX# ====================================================================================================================== ; Title .........: Fraction ; AutoIt Version : 3.3.14.0 ; Language ......: English ; Description ...: Maths functions intended to be used with vulgar fractions. ; Notes .........: In this library, a fraction is defined as two integers stored in a two element array. ; Several functions use approximation when internal calculations go out of range. ; When an approximation occurs, @extended is set to 1. This return value should be checked in most instances. ; ----------------------------------------------------------------------------------------------------------- ; Input Size Limits for _Fraction() ; Unlimited within the confines of AutoIt (approximately) between -1e+308 and +1e+308 ; Smallest value (approximately) 1e-323 ; -------------------------------------------------------------------- ; Negative Output Size Limits for All Functions That Return a Fraction ; 9223372036854775807 /-1 To -1/ 9223372036854775807 negative range ; - 2^63 /1 Out of range ; tiny negative values are either rounded down to -1/ 9223372036854775807 or rounded up to 0/-1 ; ---------------------------------------------------------------- ; Positive Output Size Limits for Functions That Return a Fraction ; 1/ 9223372036854775807 To 9223372036854775807 /1 positive range ; 2^63 /1 Out of range ; tiny positive values are either rounded up to 1/ 9223372036854775807 or rounded down to 0/1 ; Author(s) .....: czardas, Pheonix XL ; ============================================================================================================================== ; #CURRENT# ==================================================================================================================== ; _Fraction ; _FractionAbs ; _FractionAdd ; _FractionApproximate ; _FractionCeiling ; _FractionDivide ; _FractionFloor ; _FractionMod ; _FractionMultiply ; _FractionPower ; _FractionRoot ; _FractionSubtract ; _IsFraction ; _IsFractionNegative ; _Reciprocal ; ============================================================================================================================== ; #INTERNAL_USE_ONLY#=========================================================================================================== ; __GCD ; __FloatRatio ; __FloatToDigits ; __Quotient ; __Simplify ; ============================================================================================================================== ; #FUNCTION# =================================================================================================================== ; Name...........: _Fraction ; Description ...: Conversion from float to fraction and whole number division. ; Syntax.........: _Fraction($nDividend [, $nDivisor = 1]) ; Parameters.....; $iDividend - Top fractional part ; $iDivisor - [Optional] Lower fractional part. The default value is 1. ; Return Values ; Success - Returns an array of two elements. The first element is the dividend, and the second is the divisor. ; Sets @Extended to 1 if rounding or approximation occurs, or if one of the input parameters is a float. ; Failure sets @error as follows ; |@error = 1 Input contains non-numeric or undefined data. ; |@error = 2 The divisor cannot be zero. ; |@error = 3 Out of range. ; Author ........: czardas ; Comments ......; Accepts Int32, Int64 and floats. Output ranges are shown in the main header. ; ============================================================================================================================== Func _Fraction($nDividend, $nDivisor = 1) If Not IsNumber($nDividend) Or StringInStr($nDividend, '#') Or _ Not IsNumber($nDivisor) Or StringInStr($nDivisor, '#') Then Return SetError(1) ; non-numeric input or undefined value If $nDivisor = 0 Then Return SetError(2) ; division by zero produces meaningless results Local $iExtended = 0 ; will be set to 1 if rounding or approximation occurs, or if one of the input parameters is a float $nDividend = __Integer64($nDividend) If @error Or $nDividend = 0x8000000000000000 Then $iExtended = 1 $nDivisor = __Integer64($nDivisor) If @error Or $nDivisor = 0x8000000000000000 Then $iExtended = 1 Local $aFraction[2] ; if both the dividend and divisor are negative, then both output values will be positive $aFraction[0] = ($nDividend < 0 And $nDivisor > 0) ? -1 : 1 $aFraction[1] = ($nDivisor < 0 And $nDividend > 0) ? -1 : 1 If $iExtended Then ; float parameters require preprocessing $nDividend = Number($nDividend, 3) $nDivisor = Number($nDivisor, 3) __FloatRatio($nDividend, $nDivisor) If @error Then Return SetError(3) ; out of range EndIf If $nDividend = 0 Then $aFraction[0] = 0 $aFraction[1] = $nDivisor > 0 ? 1 : -1 ; a fraction may have a value of negative zero (purpose unknown) Return $aFraction EndIf $nDividend = _Abs64($nDividend) $nDivisor = _Abs64($nDivisor) If $nDividend <> 0 Then __Simplify($nDividend, $nDivisor) ; division by the greatest common divisor $aFraction[0] *= $nDividend ; return values may be negative $aFraction[1] *= $nDivisor ; as above. Return SetExtended($iExtended, $aFraction) EndFunc ;==> _Fraction ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionAbs ; Description ...: Calculates the absolute value of a fraction. ; Syntax.........: _FractionAbs($aFraction) ; Parameters.....; $aFraction - A two element array containing a dividend and a divisor. ; Return values .: Success - Returns a two element array containing the absolute values of both dividend and divisor. ; Failure sets @error to 1 if the input is not a valid array containing both a dividend and a divisor. ; Author ........: czardas ; ============================================================================================================================== Func _FractionAbs($aFraction) If Not _IsFraction($aFraction) Then Return SetError(1) $aFraction[0] = _Abs64($aFraction[0]) $aFraction[1] = _Abs64($aFraction[1]) Return $aFraction EndFunc ;==> _FractionAbs ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionAdd ; Description ...: Calculates the sum of two fractions. ; Syntax.........: _FractionAdd($aFraction1, $aFraction2) ; Parameters.....; $aFraction1 - The first two element array containing a dividend and a divisor. ; $aFraction2 - The second two element array containing a dividend and a divisor. ; Return values .: Success - Returns a two element array containing the sum of the two fractions. ; Sets @Extended to 1 if rounding or approximation occurs. ; Failure sets @error as follows ; |@error = 1 Invalid input. ; |@error = 2 The divisor cannot become zero. ; |@error = 3 Out of range. ; Author ........: czardas ; ============================================================================================================================== Func _FractionAdd($aFraction1, $aFraction2) If Not (_IsFraction($aFraction1) And _IsFraction($aFraction2)) Then Return SetError(1) Local $iValue_1, $iValue_2, $iDividend, $iDivisor, $iExtended = 0 If __OverflowDetect('*', $aFraction1[0], $aFraction2[1], $iValue_1) Then $iExtended = 1 If __OverflowDetect('*', $aFraction1[1], $aFraction2[0], $iValue_2) Then $iExtended = 1 If __OverflowDetect('+', $iValue_1, $iValue_2, $iDividend) Then $iExtended = 1 If __OverflowDetect('*', $aFraction1[1], $aFraction2[1], $iDivisor) Then $iExtended = 1 Local $aAddition = _Fraction($iDividend, $iDivisor) If @extended Then $iExtended = 1 Return SetError(@error, $iExtended, $aAddition) EndFunc ;==> _FractionAdd ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionApproximate ; Description ...: Approximates the value of a fraction according to limits imposed on the size of the divisor. ; Syntax.........: _FractionApproximate($aFraction, $iMaxDivisor) ; Parameters.....; $aFraction - A two element array containing a valid dividend and divisor. ; $iMaxDivisor - The maximum numeric limit for the divisor. ; Return values .: Success - Returns a two element array containing the approximated fraction. ; Failure sets @error as follows ; |@error = 1 Invalid input for $aFraction. ; |@error = 2 Invalid input for $iMaxDivisor. ; Author ........: czardas ; Comments ......; Approximates fractions using the method of continued fractions. ; ============================================================================================================================== Func _FractionApproximate($aFraction, $iMaxDivisor) If Not _IsFraction($aFraction) Then Return SetError(1) Local $bNegative = @extended $iMaxDivisor = _Abs64($iMaxDivisor) If @extended Then Return SetError(2, 0, $aFraction) Local $aCurrentFraction = _FractionAbs($aFraction) If $iMaxDivisor < 1 Or $iMaxDivisor >= $aCurrentFraction[1] Or $aCurrentFraction[1] <= 1 Then Return SetError(2, 0, $aFraction) ; determine the terms of the continued fraction Local $sFractionOfFraction = '' Do $sFractionOfFraction &= __Quotient($aCurrentFraction[0], $aCurrentFraction[1]) & ',' $aCurrentFraction[0] = Mod($aCurrentFraction[0], $aCurrentFraction[1]) $aCurrentFraction = _Reciprocal($aCurrentFraction) Until @error Local $aContinued = StringSplit(StringTrimRight($sFractionOfFraction, 1), ',', 2) Local $iTry, $iRange, $iMin = 0, $iMax = Ubound($aContinued) -1, $aConvergence[2] ; binary search algorithm Do $aConvergence[0] = 0 $aConvergence[1] = 1 $iRange = $iMax - $iMin +1 $iTry = $iMin + Floor($iRange/2) If $iTry > $iMax Then $iTry = $iMax ; added patch ; evaluate the significant first few terms of the continued fraction If $iTry > 0 Then For $i = $iTry To 1 Step -1 $aConvergence = _FractionAdd($aConvergence, _Fraction(Number($aContinued[$i]))) $aConvergence = _Reciprocal($aConvergence) Next EndIf $aConvergence = _FractionAdd($aConvergence, _Fraction(Number($aContinued[0]))) If $aConvergence[1] > $iMaxDivisor Then ; aim was too high - target is lower $iMax = $iTry -1 Else ; aim was too low - target may be higher ; log low entry $aCurrentFraction = $aConvergence $iMin = $iTry +1 EndIf Until $iRange <= 1 If $bNegative Then $aCurrentFraction[(($aFraction[0] < 0) ? 0 : 1)] *= -1 Return $aCurrentFraction EndFunc ;==> _FractionApproximate ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionCeiling ; Description ...: Calculates the ceiling value of a fraction. ; Syntax.........: _FractionCeiling($aFraction) ; Parameters.....; $aFraction - A two element array containing a dividend and a divisor. ; Return values .: Success - Returns a two element array ==> The first element is the ceiling value, and the divisor is always 1. ; Failure sets @error as follows ; |@error = 1 The input is not a valid array containing a dividend and a divisor. ; Author ........: czardas ; Comments ......; If the fraction is negative, then its ceiling value represents the integer part of the fraction. ; ============================================================================================================================== Func _FractionCeiling($aFraction) If Not _IsFraction($aFraction) Then Return SetError(1) Local $bNegative = @extended If $aFraction[0] = 0 Then Return $aFraction ; for this to work we need to simplify the fraction $aFraction = _FractionAbs($aFraction) Local $iDividend = $aFraction[0], $iDivisor = $aFraction[1] __Simplify($iDividend, $iDivisor) If $iDivisor = 1 Then Return _Fraction($iDividend * ($bNegative = 0 ? 1 : -1)) Local $iCeiling = __Quotient($iDividend, $iDivisor) If $bNegative Then Return _Fraction($iCeiling * -1) Return _Fraction($iCeiling + 1) EndFunc ;==> _FractionCeiling ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionDivide ; Description ...: Divides the first fraction by the second. ; Syntax.........: _FractionDivide($aDividend, $aDivisor) ; Parameters.....; $aDividend - The first two element array containing a dividend and a divisor. ; $aDivisor - The second two element array containing a dividend and a divisor. ; Return values .: Success - Returns a two element array containing the result after division. ; Sets @Extended to 1 if rounding or approximation occurs. ; Failure sets @error as follows ; |@error = 1 Invalid input. ; |@error = 2 The divisor cannot become zero. ; |@error = 3 Out of range. ; Author ........: czardas ; ============================================================================================================================== Func _FractionDivide($aDividend, $aDivisor) If Not (_IsFraction($aDividend) And _IsFraction($aDivisor)) Then Return SetError(1) Local $iValue_1, $iValue_2, $iExtended = 0 If __OverflowDetect('*', $aDividend[0], $aDivisor[1], $iValue_1) Then $iExtended = 1 If __OverflowDetect('*', $aDividend[1], $aDivisor[0], $iValue_2) Then $iExtended = 1 If $aDivisor[0] = 0 Then Return SetError(2) Local $aDivision = _Fraction($iValue_1, $iValue_2) If @extended Then $iExtended = 1 Return SetError(@error, $iExtended, $aDivision) EndFunc ;==> _FractionDivide ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionFloor ; Description ...: Calculates the floor value of a fraction. ; Syntax.........: _FractionFloor($aFraction) ; Parameters.....; $aFraction - A two element array containing a dividend and a divisor. ; Return values .: Success - Returns a two element array ==> The first element is the floor value, and the divisor is always 1. ; Failure sets @error as follows ; |@error = 1 The input is not a valid array containing a dividend and a divisor. ; Author ........: czardas ; Comments ......; If the fraction is positive, then its floor value represents the integer part of the fraction. ; ============================================================================================================================== Func _FractionFloor($aFraction) If Not _IsFraction($aFraction) Then Return SetError(1) Local $bNegative = @extended If $aFraction[0] = 0 Then Return $aFraction ; for this to work we need to simplify the fraction $aFraction = _FractionAbs($aFraction) Local $iDividend = $aFraction[0], $iDivisor = $aFraction[1] __Simplify($iDividend, $iDivisor) If $iDivisor = 1 Then Return _Fraction($iDividend * ($bNegative = 0 ? 1 : -1)) Local $iFloor = __Quotient($iDividend, $iDivisor) If Not $bNegative Then Return _Fraction($iFloor) Return _Fraction($iFloor * -1 - 1) EndFunc ;==> _FractionFloor ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionMod ; Description ...: Performs the modulus operation with two fractions. ; Syntax.........: _FractionMod($aDividend, $aDivisor) ; Parameters.....; $aDividend - The first fraction is the dividend array. ; $aDivisor - The second fraction is the divisor array. ; Return values .: Success - Returns a two element array containing the modulus of $aDividend and $aDivisor. ; Sets @Extended to 1 if rounding or approximation occurs. ; Failure sets @error to: ; |@error = 1 Out of bounds - Fraction division failure. ; |@error = 2 Out of bounds - Fraction multiplication failure. ; |@error = 3 Out of bounds - Fraction subtraction failure. ; Author ........: czardas ; ============================================================================================================================== Func _FractionMod($aDividend, $aDivisor) Local $iExtended = 0, $aDivision = _FractionDivide($aDividend, $aDivisor) If @error Then Return SetError(1) If @extended Then $iExtended = @extended Local $aModulus[2] If $aDividend[0] = 0 Then $aModulus[0] = 0 $aModulus[1] = ($aDividend[0] < 0) ? -1 : 1 Return $aModulus EndIf Local $aMultiple = _FractionMultiply(_FractionAbs($aDivisor), _FractionFloor(_FractionAbs($aDivision))) If @error Then Return SetError(2) If @extended Then $iExtended = @extended $aModulus = _FractionSubtract(_FractionAbs($aDividend), $aMultiple) If @error Then Return SetError(3) If @extended Then $iExtended = @extended If _IsFractionNegative($aDividend) Then If $aDividend[0] < 0 Then $aModulus[0] *= -1 Else $aModulus[1] *= -1 EndIf EndIf Return SetExtended($iExtended, $aModulus) EndFunc ;==> _FractionMod ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionMultiply ; Description ...: Calculates the product of two fractions. ; Syntax.........: _FractionMultiply($aFraction1, $aFraction2) ; Parameters.....; $aFraction1 - The first two element array containing a dividend and a divisor. ; $aFraction2 - The second two element array containing a dividend and a divisor. ; Return values .: Success - Returns a two element array containing the product of the two fractions. ; Sets @Extended to 1 if rounding or approximation occurs. ; Failure sets @error as follows ; |@error = 1 Invalid input. ; |@error > 1 Internal calculations went out of range. ; Author ........: czardas ; ============================================================================================================================== Func _FractionMultiply($aFraction1, $aFraction2) If Not (_IsFraction($aFraction1) And _IsFraction($aFraction2)) Then Return SetError(1) Local $iValue_1, $iValue_2, $iExtended = 0 If __OverflowDetect('*', $aFraction1[0], $aFraction2[0], $iValue_1) Then $iExtended = 1 If __OverflowDetect('*', $aFraction1[1], $aFraction2[1], $iValue_2) Then $iExtended = 1 Local $aProduct = _Fraction($iValue_1, $iValue_2) If @extended Then $iExtended = 1 Return SetError(@error, $iExtended, $aProduct) EndFunc ;==> _FractionMultiply ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionPower ; Description ...: Raises a fraction to the power of a fraction. ; Syntax.........: _FractionPower($aFraction, $aPower) ; Parameters.....; $aFraction - The first two element array containing a dividend and a divisor. ; $aPower - The second two element array containing a dividend and a divisor. ; Return values .: Success - Returns a two element array containing $aFraction to the power of $aPower. ; Sets @Extended to 1 if rounding or approximation occurs. ; Failure sets @error as follows ; |@error = 1 Invalid input. ; |@error = 3 Out of range. ; |@error = 5 Imaginary fraction detected. ; Author ........: czardas ; Comments ......; Calculating fractional powers of approximated negative fractions leads to ambiguity. ; ============================================================================================================================== Func _FractionPower($aFraction, $aPower) If Not _IsFraction($aPower) Then Return SetError(1) Local $bNegative = _IsFractionNegative($aFraction) If @error Then Return SetError(1) If $bNegative And Mod($aPower[1], 2) = 0 Then Return SetError(5) Local $iSign = ($bNegative And Mod($aPower[0], 2) <> 0) ? -1 : 1 $aFraction = _FractionAbs($aFraction) Local $iExtended, $iPower = __WholeNumberDivision($aPower[0], $aPower[1]) If @extended Then $iExtended = 1 Local $iDividend If $iExtended Then $iDividend = $aFraction[0] ^ $iPower Else $iDividend = _Power64($aFraction[0], $iPower) If @extended Then $iExtended = 1 EndIf Local $iDivisor If $iExtended Then $iDivisor = $aFraction[1] ^ $iPower Else $iDivisor = _Power64($aFraction[1], $iPower) If @extended Then $iExtended = 1 EndIf $aPower = _Fraction($iSign * $iDividend, $iDivisor) If @extended Then $iExtended = 1 Return SetError(@error, $iExtended, $aPower) EndFunc ;==> FractionPower ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionRoot ; Description ...: Calculates the fractional root of a fraction. ; Syntax.........: _FractionRoot($aFraction, $aRoot) ; Parameters.....; $aFraction - The first two element array containing a dividend and a divisor. ; $aRoot - The second two element array containing a dividend and a divisor. ; Return values .: Success - Returns a two element array containing $aFraction to the power of the reciprocal of $aRoot. ; Sets @Extended to 1 if rounding or approximation occurs. ; Failure sets @error as follows ; |@error = 1 Invalid input. ; |@error = 3 Out of range. ; |@error = 5 Imaginary fraction detected. ; Author ........: czardas ; Comments ......; Calculating fractional roots of approximated negative fractions leads to ambiguity. ; ============================================================================================================================== Func _FractionRoot($aFraction, $aRoot) If Not _IsFraction($aRoot) Then Return SetError(1) Local $bNegative = _IsFractionNegative($aFraction) If @error Then Return SetError(1) If $bNegative And Mod($aRoot[0], 2) = 0 Then Return SetError(5) Local $iSign = ($bNegative And Mod($aRoot[1], 2) <> 0) ? -1 : 1 $aFraction = _FractionAbs($aFraction) Local $iExtended, $iRoot = __WholeNumberDivision($aRoot[0], $aRoot[1]) If @extended Then $iExtended = 1 Local $iDividend If $iExtended Then $iDividend = $aFraction[0] ^ (1 / $iRoot) Else $iDividend = _Root64($aFraction[0], $iRoot) If @extended Then $iExtended = 1 EndIf Local $iDivisor If $iExtended Then $iDivisor = $aFraction[1] ^ (1 / $iRoot) Else $iDivisor = _Root64($aFraction[1], $iRoot) If @extended Then $iExtended = 1 EndIf $aRoot = _Fraction($iSign * $iDividend, $iDivisor) If @extended Then $iExtended = 1 Return SetError(@error, $iExtended, $aRoot) EndFunc ;==> FractionRoot ; #FUNCTION# =================================================================================================================== ; Name...........: _FractionSubtract ; Description ...: Subtracts the second fraction from the first. ; Syntax.........: _FractionSubtract($aFraction1, $aFraction2) ; Parameters.....; $aFraction1 - The first two element array containing a dividend and a divisor. ; $aFraction2 - The second two element array containing a dividend and a divisor (subtracted from $aFraction1). ; Return values .: Success - Returns a two element array containing the resulting fraction. ; Sets @Extended to 1 if rounding or approximation occurs. ; Failure sets @error as follows ; |@error = 1 Invalid input. ; |@error = 2 The divisor cannot become zero. ; |@error = 3 Out of range. ; Author ........: czardas ; ============================================================================================================================== Func _FractionSubtract($aFraction1, $aFraction2) If Not (_IsFraction($aFraction1) And _IsFraction($aFraction2)) Then Return SetError(1) Local $iValue_1, $iValue_2, $iDividend, $iDivisor, $iExtended = 0 If __OverflowDetect('*', $aFraction1[0], $aFraction2[1], $iValue_1) Then $iExtended = 1 If __OverflowDetect('*', $aFraction1[1], $aFraction2[0], $iValue_2) Then $iExtended = 1 If __OverflowDetect('-', $iValue_1, $iValue_2, $iDividend) Then $iExtended = 1 If __OverflowDetect('*', $aFraction1[1], $aFraction2[1], $iDivisor) Then $iExtended = 1 Local $aSubtraction = _Fraction($iDividend, $iDivisor) If @extended Then $iExtended = 1 Return SetError(@error, $iExtended, $aSubtraction) EndFunc ;==> _FractionSubtract ; #FUNCTION# =================================================================================================================== ; Name...........: _IsFraction ; Description ...: Checks if the input is an array containing two valid integers ==> representing dividend and divisor. ; Syntax.........: _IsFraction($aFraction) ; Parameters.....; $aFraction - A two element array containing a dividend and a divisor. ; Return values .: Returns True if the input matches the criteria, otherwise returns False. ; Author.........: czardas ; Comments ......; Sets @extended to 1 if the fraction is negative. ; ============================================================================================================================== Func _IsFraction($aFraction) If Not IsArray($aFraction) Then Return False If Ubound($aFraction,0) <> 1 Or Ubound($aFraction) <> 2 Then Return False If Not StringInStr(VarGetType($aFraction[0]), 'Int') Or $aFraction[0] = 0x8000000000000000 Then Return False If Not StringInStr(VarGetType($aFraction[1]), 'Int') Or $aFraction[1] = 0x8000000000000000 Then Return False Return SetExtended((($aFraction[0] < 0 And $aFraction[1] > 0) Or ($aFraction[0] >= 0 And $aFraction[1] < 0)) ? 1 : 0, True) EndFunc ;==> _IsFraction ; #FUNCTION# =================================================================================================================== ; Name...........: _IsFractionNegative ; Description ...: Checks if the input array is a negative fraction. ; Syntax.........: _IsFractionNegative($aFraction) ; Parameters.....; $aFraction - A two element array containing a dividend and a divisor. ; Return values .: Returns True if the dividend and the divisor are of the opposite sign, othewise returns False. ; Failure sets @error to 1 if the input is not a valid array containing a dividend and a divisor. ; Author.........: czardas ; ============================================================================================================================== Func _IsFractionNegative($aFraction) Local $bNegative, $bIsFraction = _IsFraction($aFraction) $bNegative = @extended Return SetError(1 - $bIsFraction, 0, ($bNegative = 1)) EndFunc ;==> _IsFractionNegative ; #FUNCTION# =================================================================================================================== ; Name...........: _Reciprocal ; Description ...: Inverts a fraction by swapping the dividend and the divisor. ; Syntax.........: _Reciprocal($aFraction) ; Parameters.....; $aFraction - A two element array containing a dividend and a divisor. ; Return values .: Returns the reciprocal of the fraction. ; Failure sets @error as follows ; |@error = 1 The input is not a valid array containing a dividend and a divisor. ; |@error = 2 The divisor cannot become zero. ; Author.........: czardas ; ============================================================================================================================== Func _Reciprocal($aFraction) If Not _IsFraction($aFraction) Then Return SetError(1) Local $iDivisor = $aFraction[0] If $iDivisor = 0 Then Return SetError(2) $aFraction[0] = $aFraction[1] $aFraction[1] = $iDivisor Return $aFraction EndFunc ;==> _Reciprocal ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __FloatRatio ; Description ...: Helper function for Fraction() - preprocessing of float parameters to generate two proportional integers. ; Syntax.........: __FloatRatio([ByRef] $nDividend, [ByRef] $nDivisor) ; Parameters.....; $iDividend - Top fractional part ; $iDivisor - Lower fractional part ; Return values .: Success - Integer values are returned ByRef. ; Failure sets @error as follows ; |@error = 1 Out of bounds. ; Author ........: czardas ; Comments ......; No attempt has been made to accomodate for any double precision limitations. ; Small innacuracies can affect the 17th and (sometimes) the 16th digit in double precision. ; Using floats can easily be avoided by only passing integers to the function _Fraction(). ; Input positive floats only ; ============================================================================================================================== Func __FloatRatio(ByRef $nDividend, ByRef $nDivisor) If $nDivisor < 0 Then $nDividend *= -1 $nDivisor *= -1 EndIf If $nDivisor <> 1 Then $nDividend /= $nDivisor Local $nSignificand, $iExponent, $iDigits = 16 ; might as well grab as many digits as are available $nSignificand = __FloatToDigits($nDividend, $iDigits) $iExponent = @extended $nSignificand = Number($nSignificand, 2) ; Int-64 While $iExponent < - 18 ; divide the significand by powers of 10 $iDigits -= 1 If $iDigits < 0 Then ; too small If $nDividend < 1 / (2 * 10 ^ 18) Then ; round down to 0 / 1 $nSignificand = 0 $iExponent = 0 Else ; round up to 1 / 1000000000000000000 $nSignificand = 1 $iExponent = 18 EndIf ExitLoop EndIf $nSignificand = __FloatToDigits($nDividend, $iDigits) $iExponent = @extended ; adjust the exponent to accomodate division of the significand $nSignificand = Number($nSignificand, 2) ; Int-64 WEnd While $iExponent > 0 ; multiply the significand by powers of 10 If __OverflowDetect('*', $nSignificand, 10, $nSignificand) Then Return SetError(1) ; too large ~ out of bounds $iExponent -= 1 ; adjust the exponent to accomodate multiplication of the significand If $iExponent = 0 Then ExitLoop WEnd $nDividend = $nSignificand ; range 0 to 1000000000000000000 $nDivisor = _Power64(10, Abs($iExponent)) ; range 10 ^ (0 to 18) ==> powers of 10 only EndFunc ;==> __FloatRatio ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __FloatToDigits ; Description ...: Extracts a specified number of digits from a float. ; Syntax.........: __FloatToDigits($fFloat, $iDigits) ; Parameters.....; $fFloat - The float to extract the digits from. ; $iDigits - The number of digits to extract after the floating point (exponential representation). ; Return values .: Success - Returns a 32-bit or 64-bit signed integer. ; Sets @extended to the decimal exponent. ==> $fFloat = return value * 10 ^ exponent ; Failure sets @error to 1 if the input is not a float or undefined. ; Author ........: czardas ; ============================================================================================================================== Func __FloatToDigits($fFloat, $iDigits = 14) If VarGetType($fFloat) <> 'Double' Or StringInStr($fFloat, '#') Then Return SetError(1) Local $iSign = ($fFloat < 0) ? -1 : 1 ; machine epsilon = 5 × 10^-15, so the final two digits (16 and 17) could be highly innacurate $fFloat = StringFormat('%.' & $iDigits & 'e', $fFloat) ; rounds to the specified number of decimal places Local $aFloat = StringSplit($fFloat, "e", 2) ; zero-based array If $iSign < 0 Then $aFloat[0] = StringTrimLeft($aFloat[0], 1) ; remove the minus sign ; remove the decimal point and trailing zeros $aFloat[0] = StringLeft($aFloat[0], 1) & StringRegExpReplace(StringRight($aFloat[0], $iDigits), '(0+\z)', '') $aFloat[1] += 1 - StringLen($aFloat[0]) ; adjust the exponent to accommodate changes Return SetExtended($aFloat[1], Int($aFloat[0]) * $iSign) ; add back the minus sign EndFunc ;==> __FloatToDigits ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __GCD ; Description ...: Calculates the greatest common divisor of two integers. Original function name ==> _Greatest_Common_Factor() ; Syntax.........: __GCD($iValue1, $iValue2) ; Parameters.....; $iValue1 - First Integer. ; $iValue2 - Second Integer. ; Return values .: Success - Returns the greatest common divisor of $iValue1 and $iValue2. ; Author.........: Pheonix XL ; Modified.......; czardas ; Comments ......; IMPORTANT - Error checks have been removed. You must run the checks if you use this function yourself. ; ============================================================================================================================== Func __GCD($iValue1, $iValue2) ; Only accepts positive integers greater than zero. ; If Not (IsInt($iValue1) And IsInt($iValue2)) Or $iValue1 < 1 Or $iValue2 < 1 Then Return SetError(1) Local $iToggle If $iValue1 < $iValue2 Then ; Switch values. $iToggle = $iValue1 $iValue1 = $iValue2 $iValue2 = $iToggle EndIf Local $iModulus While 1 ; Method of Euclid. $iModulus = Mod($iValue1, $iValue2) If $iModulus = 0 Then ExitLoop $iValue1 = $iValue2 $iValue2 = $iModulus WEnd Return $iValue2 EndFunc ;==> __GCD ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __Quotient ; Description ...: Returns the quotient after division ~(the whole number part of a fraction). ; Syntax.........: __Quotient($nDividend, $nDivisor) ; Parameters.....; $iDividend - The integer to divide ; $iDivisor - The integer to divide by ; Return values .: Returns the quotient. ; Author ........: czardas ; Comments ......; Uses the same correction method as __WholeNumberDivision() in operator64.au3. ; ============================================================================================================================== Func __Quotient($nDividend, $nDivisor) Local $iQuotient = Floor($nDividend / $nDivisor), $iDifference, $iIntegral While $iQuotient * $nDivisor > $nDividend ; division is overstated $iDifference = ($nDivisor * $iQuotient) - $nDividend $iIntegral = Floor($iDifference / $nDivisor) ; avoid shooting beyond the target If $iIntegral = 0 Then $iIntegral = 1 ; prevents hanging in an infinite loop $iQuotient -= $iIntegral WEnd While $iQuotient * $nDivisor < $nDividend ; division is understated $iDifference = $nDividend - ($nDivisor * $iQuotient) $iIntegral = Floor($iDifference / $nDivisor) ; as above If $iIntegral = 0 Then ExitLoop ; we have found the floor already $iQuotient += $iIntegral WEnd Return $iQuotient EndFunc ;==> __Quotient ; #INTERNAL_USE_ONLY# ========================================================================================================== ; Name...........: __Simplify ; Description ...: Simplification by division. ; Syntax.........: __Simplify($iDividend, $iDivisor) ; Parameters.....; $iDividend - Top fractional part. ; $iDivisor - Lower fractional part. ; Author ........: czardas ; ============================================================================================================================== Func __Simplify(ByRef $iDividend, ByRef $iDivisor) Local $iGCD = __GCD($iDividend, $iDivisor) If $iGCD > 1 Then $iDividend = __WholeNumberDivision($iDividend, $iGCD) $iDivisor = __WholeNumberDivision($iDivisor, $iGCD) EndIf EndFunc ;==> __SimplifyExamples - currently testing for accuracy and possible bugs.
    #include 'Fraction.au3' Local $aFraction = _Fraction(3.1416) If @error Then Exit ; ==> Error handling. ConsoleWrite("3.1416 = " & $aFraction[0] & " / " & $aFraction[1] & @LF) $aFraction = _Fraction(0.125 , 2) ConsoleWrite("0.125 / 2 = " & $aFraction[0] & " / " & $aFraction[1] & @LF) $aFraction = _Fraction(0.00125, -0.32) ConsoleWrite("0.00125 / -0.32 = " & $aFraction[0] & " / " & $aFraction[1] & @LF) $aFraction = _Fraction(86418753, 2977963408767) ConsoleWrite("86418753 / 2977963408767 = " & $aFraction[0] & " / " & $aFraction[1] & @LF) ; Multiply two fractions (27 / 28 x 374 / 555) using whole number arithmetic: Local $aProduct = _FractionMultiply(_Fraction(27, 28), _Fraction(374, 555)) ConsoleWrite("27 / 28 x 374 / 555 = " & $aProduct[0] & " / " & $aProduct[1] & @LF) ; The modulus of two fractions: Local $aMod = _FractionMod(_Fraction(-1, 2), _Fraction(1, 3)) ConsoleWrite("Mod(-1/2, 1/3) = " & $aMod[0] & "/" & $aMod[1] & @LF) ; Represent pi (as accurately as possible) using a fraction with denominator of no more than thirteen digits. Local $aPi = _FractionApproximate(_Fraction(3.14159265358979), 1e+013 -1) ConsoleWrite($aPi[0] & " / " & $aPi[1] & " = " & $aPi[0] / $aPi[1] & " ~ Pi" & @LF) Local $aR2 = _FractionApproximate(_Fraction(2^.5), 1.0e+13 -1) ConsoleWrite($aR2[0] & " / " & $aR2[1] & " = " & $aR2[0] / $aR2[1] & " ~ 2^(1/2)" & @LF) Local $aLarge = _Fraction(1.23456789e+017,100000000000000100) If @error Then MsgBox(0, "", @error) ConsoleWrite(@extended & @LF) ConsoleWrite($aLarge[0] & " / " & $aLarge[1] & " = " & $aLarge[0] / $aLarge[1] & " = " & 1.23456789e+017 / 100000000000001000 & @LF) Local $aSmall = _Fraction(1.23456789e-200,8.64197523e-192) If @error Then MsgBox(0, "", @error) ConsoleWrite(@extended & @LF) ConsoleWrite($aSmall[0] & " / " & $aSmall[1] & " = " & $aSmall[0] / $aSmall[1] & " = " & 1.23456789e-200 / 8.64197523e-192 & @LF) Local $aTooSmall = _Fraction(1.23456789e-200,100000000000000100) If @error Then MsgBox(0, "", @error) ConsoleWrite("@extended = " & @extended & @LF) ConsoleWrite($aTooSmall[0] & " / " & $aTooSmall[1] & " = " & $aTooSmall[0] / $aTooSmall[1] & " = " & 1.23456789e-200 / 100000000000001000 & @LF) Local $aTooLarge = _Fraction(100000000000000100, 1.23456789e-200) ConsoleWrite("@error = " & @error & @LF) Local $aAddApprox = _FractionAdd(_Fraction(134567890000, 999999999999), _Fraction(987654321000, 777777777777777)) ConsoleWrite("@extended = " & @extended & @LF) ConsoleWrite($aAddApprox[0] & " / " & $aAddApprox[1] & " = " & $aAddApprox[0] / $aAddApprox[1] & " = " & (134567890000/999999999999 + 987654321000/777777777777777) ;
    See post 33 for information on the latest update.
  25. Like
    czardas got a reaction from FrancescoDiMuro in user input syntax check   
    There's probably a simpler pattern, but this seems to do what I think you want. The user can add spaces between each file format and extensions can be any length or include numbers e.g. *.pspimage , *.mp4
    I should add 'you are welcome'
×
×
  • Create New...