[WIP] utility library

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

[WIP] utility library

27 Aug 2019, 09:48

Hello,

I'm trying to bring my favorite methods from Lodash to ahk.

These need to be implemented in a larger class found here: https://github.com/Chunjee/biga.ahk in the file export.ahk or you may post them as standalone functions and I will convert them.

Need help with the following:
https://lodash.com/docs/4.17.15#matches
https://lodash.com/docs/4.17.15#orderBy



Partially completed:
https://biga-ahk.github.io/biga.ahk/#/?id=filter
https://biga-ahk.github.io/biga.ahk/#/?id=find


Done:
https://biga-ahk.github.io/biga.ahk/#/?id=concat
https://biga-ahk.github.io/biga.ahk/#/?id=difference
https://biga-ahk.github.io/biga.ahk/#/?id=join
https://biga-ahk.github.io/biga.ahk/#/?id=reverse
https://biga-ahk.github.io/biga.ahk/#/?id=uniq
https://biga-ahk.github.io/biga.ahk/#/?id=includes
https://biga-ahk.github.io/biga.ahk/#/?id=map
https://biga-ahk.github.io/biga.ahk/#/?id=samplesize
https://biga-ahk.github.io/biga.ahk/#/?id=clone
https://biga-ahk.github.io/biga.ahk/#/?id=clonedeep
https://biga-ahk.github.io/biga.ahk/#/?id=ismatch
https://biga-ahk.github.io/biga.ahk/#/?id=merge
https://biga-ahk.github.io/biga.ahk/#/?id=replace
https://biga-ahk.github.io/biga.ahk/#/?id=truncate
https://biga-ahk.github.io/biga.ahk/#/?id=trim
https://biga-ahk.github.io/biga.ahk/#/?id=trimStart
https://biga-ahk.github.io/biga.ahk/#/?id=trimEnd
https://biga-ahk.github.io/biga.ahk/#/?id=startCase
https://biga-ahk.github.io/biga.ahk/#/?id=indexOf
https://biga-ahk.github.io/biga.ahk/#/?id=lastIndexOf
etc...


performance improvements, clearer code, ahkv2 compatibility are also very welcome.

Live has zero dependencies.
Testing and development has the following dependencies:

Code: Select all

"dependencies": {

},
"devDependencies": {
    "unit-testing.ahk": "github:chunjee/unit-testing.ahk",
    "json.ahk": "github:chunjee/json.ahk"
}

Why?
Lodash is a cool library that makes quick work of data filtering, juggling, manipulation, etc. The library saves me a lot of time and helps me see the big picture instead of micromanaging data in an array, etc. Whenever I comeback to ahk I miss it and it's time to solve that for myself and the larger community.
I believe a decent port could radically change the way ahk projects are written for the better.

You can find the library copy/paste in post #2 as is customary
However, I recommend installing via npm npm install biga.ahk
Last edited by Chunjee on 16 Sep 2019, 09:12, edited 14 times in total.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: Help with utility library

27 Aug 2019, 10:39

Misc Notes:

Another large utility library that may offer some insights: https://github.com/Shambles-Dev/AutoHotkey-Facade



lib copy/paste:
v0.1.1

Code: Select all

Class biga {

	__New() {
        this.info_Array
        this.caseSensitive := false
        this.limit := -1
	}

    ; /--\--/--\--/--\--/--\--/--\
    ; Array functions
    ; \--/--\--/--\--/--\--/--\--/


    ; /--\--/--\--/--\--/--\--/--\
    ; Collection functions
    ; \--/--\--/--\--/--\--/--\--/

    filter(para_collection,para_func) {
        this.info_Array := []
        Loop, % para_collection.MaxIndex() {
            if (para_func is string) {
                if (para_collection[A_Index][para_func]) {
                    this.info_Array.push(para_collection[A_Index])
                }
            }
            if (IsFunc(para_func)) {
                if (%para_func%(para_collection[A_Index])) {
                    this.info_Array.push(para_collection[A_Index])
                }
            }
            ; if (para_func.Count() > 0) {
            ;     for Key, Value in para_func {
            ;         msgbox, % Key
            ;         msgbox, % Value
            ;         if (para_collection[A_Index][para_func]) {
            ;             this.info_Array.push(para_collection[A_Index])
            ;         }
            ;     }
            ; }
        }
        return this.info_Array
    }

    find(para_collection,para_iteratee,para_fromindex := 1) {
        Loop, % para_collection.MaxIndex() {
            if (para_fromindex > A_Index) {
                continue
            }
            if (para_iteratee is string) {
                if (para_collection[A_Index][para_iteratee]) {
                    return % para_collection[A_Index]
                }
            }
            if (IsFunc(para_iteratee)) {
                if (%para_iteratee%(para_collection[A_Index])) {
                    return % para_collection[A_Index]
                }
            }
            if (para_iteratee.Count() > 0) {
                ; for Key, Value in para_func {
                ;     msgbox, % Key
                ;     msgbox, % Value
                ;     if (para_collection[A_Index][para_func]) {
                ;         return % this.info_Array.push(para_collection[A_Index])
                ;     }
                ; }
            }
        }
    }

    includes(para_collection,para_value,para_fromIndex := 1) {
        if (!IsObject(para_collection)) {
            stringFoundVar := InStr(para_collection, para_value, this.caseSensitive, para_fromIndex)
            if (stringFoundVar == 0) {
                return false
            } else {
                return true
            }
        } else {
            loop, % para_collection.MaxIndex() {
                if (para_fromIndex > A_Index) {
                    continue
                }
                if (para_collection[A_Index] = para_value) {
                    return true
                }
            }
            return false
        }
    }

    join(para_collection,para_sepatator := ",") {
        if (!IsObject(para_collection)) {
            return false
        }
        l_output := ""
        loop, % para_collection.MaxIndex() {
            if (A_Index == para_collection.MaxIndex()) {
                return % l_output para_collection[A_Index]
            }
            l_output := para_collection[A_Index] para_sepatator l_output
        }
    }

    map(para_collection,para_iteratee) {
        this.info_Array := []
        Loop, % para_collection.MaxIndex() {
            if (IsFunc(para_iteratee)) {
                    this.info_Array.push(%para_iteratee%(para_collection[A_Index]))
            }
            if (para_iteratee is string) {
                this.info_Array.push(para_collection[A_Index][para_iteratee])
            }
        }
        return this.info_Array
    }

    matches(para_source) {
        ; fn_array := []
        ; fn := Func("dothis")
        ; For Key, Value in para_source {

        ; }
    }

    merge(para_collections*) {
        result := para_collections[1]
        for i, obj in para_collections {
            if(A_Index = 1) {
                Continue 
            }
            result := this.internal_Merge(result, obj)
        }
        return result
    }

    mergeWith(para_collection1,para_collection2,para_func) {

    }
    

    orderBy() {

    }


    reverse(para_collection) {
        if (!IsObject(para_collection)) {
            throw { error: "Type Error", file: A_LineFile, line: A_LineNumber }
        }
        this.info_Array := []
        while (para_collection.MaxIndex() != "") {
            this.info_Array.push(para_collection.pop())
        }
        return % this.info_Array
    }


    sampleSize(para_collection,para_SampleSize) {
        if (!IsObject(para_collection)) {
            return false
        }
        if (para_SampleSize > para_collection.MaxIndex()) {
            return % para_collection
        }

        this.info_Array := []
        l_dummyArray := []
        loop, %para_SampleSize%
        {
            Random, randomNum, 1, para_collection.MaxIndex()
            while (this.indexOf(l_dummyArray,randomNum) != -1) {
                l_dummyArray.push(randomNum)
                Random, randomNum, 1, para_collection.MaxIndex()
            }
            this.info_Array.push(para_collection[randomNum])
            para_collection.RemoveAt(randomNum)
        }
        return % this.info_Array
    }


    uniq(para_collection) {
        global
        if (!IsObject(para_collection)) {
            return false
        }
        dummy_Array := []
        this.info_Array := []
        Loop, % para_collection.MaxIndex() {
            printedelement := this.internal_MD5(this.objPrint(para_collection[A_Index]))
            if (this.indexOf(dummy_Array,printedelement) == -1) {
                dummy_Array.push(printedelement)
                this.info_Array.push(para_collection[A_Index])
            }
        }
        return this.info_Array
    }

    ; /--\--/--\--/--\--/--\--/--\
    ; String functions
    ; \--/--\--/--\--/--\--/--\--/

    replace(para_string := "",para_needle := "",para_replacement := "") {
        l_string := para_string
        output := StrReplace(l_string, para_needle, para_replacement, , this.limit)
        return % output
    }
    
    toLower(para_string) {
        StringLower, OutputVar, para_string
        return % OutputVar
    }

    toUpper(para_string) {
        StringUpper, OutputVar, para_string
        return % OutputVar
    }

    truncate(para_string, para_options := "") {
        if (para_options.separator && this.includes(para_string, para_options.separator)) {
            para_string := StrSplit(para_string, para_options.separator)[1]
        }
        if (para_options.length > 0 && para_string.length > para_options.length) {
            para_string := SubStr(para_string, 1 , para_options.length)
        }
        return % para_string
    }


    ; /--\--/--\--/--\--/--\--/--\
    ; Util functions
    ; \--/--\--/--\--/--\--/--\--/

    isMatch(para_obj,para_iteratee) {
        for Key, Value in para_iteratee {
            if (para_obj[key] == Value) {
                continue
            } else {
                return false
            }
        }
        return true
    }

    ; /--\--/--\--/--\--/--\--/--\
    ; Internal functions
    ; \--/--\--/--\--/--\--/--\--/

    indexOf(para_array,para_searchTerm)	{
        for index, value in para_array {
            if (this.caseSensitive ? (value == para_searchTerm) : (value = para_searchTerm)) {
                return index
            }
        }
        return -1
    }

    internal_Merge(para_collection1, para_collection2) {
        if(!IsObject(para_collection1) && !IsObject(para_collection2)) {

            ; if only one OR the other exist, display them together. 
            if(para_collection1 = "" || para_collection2 = "") {
                return para_collection2 para_collection1
            }
            ; if both are the same thing, return one of them only 
            if (para_collection1 = para_collection2)
                return para_collection1
            ; otherwise, return them together as an object. 
            return [para_collection1, para_collection2]
        }

        ; initialize an associative array
        combined := {}

        for key, val in para_collection1 {
            combined[key] := this.internal_Merge(val, para_collection2[key])
        }

        for key, val in para_collection2 {
            if(!combined.HasKey(key)) {
                combined[key] := val
            }
        }
        return combined
    }

    printObj(para_obj) {
        if this.internal_IsCircle(para_obj) {
            throw { error: "Type Error", file: A_LineFile, line: A_LineNumber }
        }
        for Key, Value in para_obj {
            if Key is not Number 
            {
                Output .= """" . Key . """:"
            } else {
                Output .= Key . ":"
            }
            if (IsObject(Value)) {
                Output .= "[" . this.printObj(Value) . "]"
            } else if (Value is not number) {
                Output .= """" . Value . """"
            }
            else {
                Output .= Value
            }
            
            Output .= ", "
        }
        StringTrimRight, OutPut, OutPut, 2
        Return OutPut
    }

    internal_IsCircle(para_obj, para_objs=0) {
        if (!para_objs) {
            para_objs := {}
        }
        for Key, Val in para_obj
        {
            if (IsObject(Val)&&(para_objs[&Val]||this.internal_IsCircle(Val,(para_objs,para_objs[&Val]:=1)))) {
                return true
            }
        }
        return false
    }

    internal_MD5(para_string, case := 0) {
        static MD5_DIGEST_LENGTH := 16
        hModule := DllCall("LoadLibrary", "Str", "advapi32.dll", "Ptr")
        , VarSetCapacity(MD5_CTX, 104, 0), DllCall("advapi32\MD5Init", "Ptr", &MD5_CTX)
        , DllCall("advapi32\MD5Update", "Ptr", &MD5_CTX, "AStr", para_string, "UInt", StrLen(para_string))
        , DllCall("advapi32\MD5Final", "Ptr", &MD5_CTX)
        loop % MD5_DIGEST_LENGTH
            o .= Format("{:02" (case ? "X" : "x") "}", NumGet(MD5_CTX, 87 + A_Index, "UChar"))
        return o, DllCall("FreeLibrary", "Ptr", hModule)
    }
}
Last edited by Chunjee on 01 Sep 2019, 08:57, edited 1 time in total.
nou
Posts: 26
Joined: 24 Feb 2019, 21:21

Re: Help with utility library

28 Aug 2019, 19:48

Alright. I'm not very experienced in writing methods, so I wont' be putting a git request. But thanks to CloakerSmoker and Cap'n Odin on discord, they wrote a merge function for arrays using recursion:

Code: Select all

Merge(obj1, obj2) {
    if(!IsObject(obj1) && !IsObject(obj2)) {

        ; if only one OR the other exist, display them together. 
        if(obj1 = "" || obj2 = "") {
            return obj2 obj1
        }
        ; if both are the same thing, return one of them only 
        if (obj1 = obj2)
            return obj1
        ; otherwise, return them together as an object. 
        return [obj1, obj2]
    }

    ; initialize an associative array
    combined := {}

    for key, val in obj1 {
        combined[key] := Merge(val, obj2[key])
    }

    for key, val in obj2 {
        if(!combined.HasKey(key)) {
            combined[key] := val
        }
    }
    return combined
}

MergeAll(objs*) {
    res := objs[1]
    for i, obj in objs {
        if(A_Index = 1) {
            Continue 
        }
        res := Merge(res, obj)
    }
    return res
}
Say you have the following arrays:

Code: Select all

a := { "a":  [{ "b": 2 }
            , { "d": 4 }]
    ,  "g": "3"}

b := {  "a": [{ "c": 3 }
            , { "e": 5 }]}

c := {  "x": [{ "d": 8 }
            , { "Z": 9 }]}
You would just invoke the function:

Code: Select all

Merge(a, b)

; or 

MergeAll(a, b, c)
I hope that's clear enough for you to incorporate that into the method you need!
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: Help with utility library

29 Aug 2019, 15:52

Thank you very much for your assistance the same to those two you mentioned. I'll get this added right now. :thumbup: :thumbup:
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: Help with utility library

29 Aug 2019, 21:38

@nou
This is working great but isn't 100% perfect yet. Integers sometimes returned as strings

Code: Select all

object := { "a": [{ "b": 2 }, { "d": 4 }] }
other := { "a": [{ "c": 3 }, { "e": 5 }] }

Merge(object, other)
;expected
;=> { "a": [{ "b": 2, "c": 3 }, { "d": 4, "e": 5 }] }

;actual
;=> { "a": [{ "b": "2", "c": 3 }, { "d": "4", "e": 5 }] }
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: Help with utility library

30 Aug 2019, 11:55

a number of shorthands have a dependency on https://lodash.com/docs/4.17.15#matches and I'm not coinvinced this is possible with ahk but I haven't given it a proper attempt yet.

If someone can please solve for the following that would be awesome :dance:

Code: Select all

A := new biga()

objects := [ { "a": 1, "b": 2, "c": 3 }, { "a": 4, "b": 5, "c": 6 } ]

A.filter(objects, A.matches({ "a": 4, "c": 6 })
;expected
;=> { "a": 1, "b": 2, "c": 3 }



in the meantime I completed .isMatch() and partially completed .find() possibly some others.

Code: Select all

object := { "a": 1, "b": 2, "c": 3 }
A.isMatch(object,{"b": 2})
;=> true
A.isMatch(object,{"b": 2, "c": 3})
;=> true

A.isMatch(object,{"b": 1})
;=> false
A.isMatch(object,{"b": 2, "z": 99})
;=> false

Code: Select all

users := [ { "user": "barney", "age": 36, "active": true }, { "user": "pebbles", "age": 1, "active": true } ]
A.find(users,"active",2) ;fromindex argument
;=> { "user": "pebbles", "age": 1, "active": true }
https://github.com/Chunjee/biga.ahk/blob/master/tests.ahk for more examples and validations
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

03 Sep 2019, 10:21

whew the number of methods has been growing and it was getting a little rough trying to track if they were all being tested. As well I want to document everything like Lodash's excellent site, so I knew there was going to be even MORE to do.

So I setup a little build pipeline that has each method in it's own file, containing the definition and a few tests. The library get's built from all of those together, while the tests are snipped out an put in one big test script.

a matching markdown file controls most of the documentation, and the test definitions from earlier get imported as the example(s) in the documentation. :geek:

I'm not 100% sold on this name yet but you can see those docs at https://biga-ahk.github.io/biga.ahk/#/ currently
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

03 Sep 2019, 15:52

all tests passing save for .truncate

May post a first pass in the functions board today or tomorrow. The last one on my must-have list is .sortBy
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

08 Sep 2019, 20:45

.truncate is passing all tests.

I'll be filling in any missing tests tomorrow and hopefully put together some good answers for 'what would I use this for?' examples post before the end of my workweek.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

09 Sep 2019, 14:17

.trim, .trimStart, .trimEnd, and .startCase were added.


While trying to replicate the Lodash example I found out that .map could not call methods of it's own class. That is fixed so this is now possible:

Code: Select all

A.map([" foo  ", "  bar  "],A.trim)
; => ["foo", "bar"]
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

10 Sep 2019, 12:43

added a bunch of methods and making the documentation page look really nice and organized.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

11 Sep 2019, 09:47

I had an internal helper function called indexOf but it turns out that is a real Lodash method too so I brought that out into it's own documented and tested method.

Got some feedback on .map for being inefficient and a bit insane so I made it more sane. Not 100% happy but it should be a lot faster now. Still room for improvement there if someone wants to take a look.


Docs site now has a working searchbar
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [help needed] utility library

15 Sep 2019, 14:31

Added .isUndefined which I use all the time in Javascript. Don't think it'll see much use in ahk but could be wrong.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

22 Sep 2019, 10:49

Added .parseInt and .repeat

.parseInt

Code: Select all

A.parseInt("08")
; => 8

A.map(["6", "08", "10"], A.parseInt)
; => [6, 8, 10]
.repeat

Code: Select all

A.repeat("*",3)
; => "***"
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

22 Sep 2019, 10:53

Fixed a bunch of bugs and missing features for .findIndex


Still not complete because of the .properties and .matches shorthands don't work at all.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

24 Sep 2019, 07:31

There was some big problems with .sortBy because I didn't look at at the tests very closely. The function was remade entirely.

I'm looking to post the library to the other subforum because this thread isn't getting too much action and it's prettymuch got all the methods I use commonly done.

I'll give the must haves list another check today, and see what I can do there.

One big thing that still needs to be done is some final decisions on the type checking that many functions do. throw { error: "Type Error", file: A_LineFile, line: A_LineNumber } is in there quite a bit and I have no idea how valid it is.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

24 Sep 2019, 11:21

found a bug in .difference while doing the realworld examples. Fixed and I think performance improved.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

24 Sep 2019, 19:33

You may find 3 realworld examples at https://github.com/biga-ahk/biga.ahk/tree/master/examples

I tried to ramp up the complexity and also show as many methods as possible. In that process I discovered at least 2 big bugs so it was productive and hopefully useful.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: [WIP] utility library

29 Sep 2019, 06:27

Added .without

I also decided to make throwExceptions an option which is ENABLED by default. To disable one would:

Code: Select all

A := new biga()
A.throwExceptions := false
I believe this is the best way to serve the standard js/lodash experience, while also allowing for ignoring of errors. I don't really want to mess with ErrorLevel but it may be needed if setting to false is something people want to do.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: catquas, Descolada, Google [Bot], mmflume and 174 guests