[Class] biga.ahk (immutable utility library for arrays, strings, etc)

Post your working scripts, libraries and tools
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

[Class] biga.ahk (immutable utility library for arrays, strings, etc)

29 Sep 2019, 10:06

Image

Latest version: https://www.npmjs.com/package/biga.ahk
src: https://github.com/biga-ahk/biga.ahk

A modern immutable utility library for AutoHotKey

Mirrors functionality and method names of Lodash

:warning: alpha - The API might change, and may have defects prior to v1.0.0 :warning:

Installation

In a terminal or command line navigated to your project folder:

Code: Select all

npm install biga.ahk
You may also review or copy the library from ./export.ahk on GitHub


In your code:

Code: Select all

#Include %A_ScriptDir%\node_modules
#Include biga.ahk\export.ahk
A := new biga()
msgbox, % A.join(["a", "b", "c"], " ")
; => "a b c"
AutoHotkey v1.1.05 or higher is required

Usage

Initiate an instance of the class and use as needed

Code: Select all

A := new biga()
wordsArray := A.words("This could be real real useful")
; => ["This", "could", "be", "real, "real", "useful"]
msgbox, % wordsArray.Count()
; => 6
uniqWords := A.uniq(wordsArray)
; => ["This", "could", "be", "real", "useful"]
msgbox, % A.join(uniqWords, " ")
; => "This could be real useful"
Longer examples available at https://github.com/biga-ahk/biga.ahk/tree/master/examples

Documentation
Getting Started: https://biga-ahk.github.io/biga.ahk/#getting-started
All working methods documented at https://biga-ahk.github.io/biga.ahk

Contributing

Please make pull requests to src found at https://github.com/biga-ahk/biga.ahk
Last edited by Chunjee on 22 Jul 2020, 12:42, edited 7 times in total.
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (lodash for ahk)

29 Sep 2019, 10:08

Looking for a copy/paste of the library? Its a bit too large for one post on the forums. Please retrieve it at https://github.com/biga-ahk/biga.ahk/blob/master/export.ahk

TestDate: passing
2019-10-02: 138
2019-10-06: 184
2019-10-08: 202
2019-10-13: 215
2019-11-13: 261
2019-11-18: 293
2020-01-28: 343
2020-04-06: 362
2020-07-23: 400
2020-10-28: 439
2020-12-28: 449
Last edited by Chunjee on 28 Dec 2020, 17:39, edited 11 times in total.
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (lodash for ahk)

29 Sep 2019, 10:14

ahk Considerations
By default A.throwExceptions := true Wherever type errors can be detected, biga will throw an exception pointing out the location the error occurred. Set this to false if you would like your script to continue without being stopped by biga.ahk
By default A.limit := -1 Wherever possible biga.ahk will refer to this concerning limiting actions not specified by method arguments. Set this to 1 to get closer to the javascript experience. Currently only used by .replace ie should A.replace("aaaaaa", "a", "b") return "bbbbbb", "baaaaa", or something in between?


Examples
In the following example, we take on the role of a blogger, who wants to automate some features of their blog

Code: Select all

SetBatchLines -1
#NoTrayIcon
#SingleInstance force
#Include %A_ScriptDir%\node_modules
#Include biga.ahk\export.ahk

A := new biga()

blogPost := "This recipe is for a really scrumptious soup from Thailand. Grab a big bunch of lemongrass. We also need tomatoes."
; let's start by finding some interesting tags for this post. Breakup this post into an array of words and remove any duplicates
allWords := A.words(blogPost)
uniqWords := A.uniq(allWords)

; short words aren't useful for post tags. Let's remove anything that isn't at least 8 characters long
tagShortList := A.filter(uniqWords, Func("filterlengthFunc"))
filterlengthFunc(o) {
    global
    ; We use A.size to measure the length of the string. But it can measure objects too
    ; StrLen would also work fine here and remove the need for global scope in this function
    if (A.size(o) >= 8) {
        return true
    }
}

; the blog software wants all tags lowercase and in one long string separated by ","
lowercaseTags := A.map(tagShortList, A.toLower)
tags := A.join(lowercaseTags, ",")
; => "scrumptious,thailand,lemongrass,tomatoes"

; Let's pretend this blogpost was a lot longer and we only want {{3}} tags for this post. We can choose some tags at random from the larger collection
lessTags := A.sampleSize(lowercaseTags, 3)

; For the main page let's make a preview of the blogpost. 40 characters ought to do
postPreview := A.truncate(blogPost, 40)

; actually I prefer 15 words. You can combine different methods together for more power.
; .split creates an array limited to 15 items and .join turns it back into a string
postPreview := A.join(A.split(blogPost," ",15)," ") " [...]"
; => This recipe is for a really scrumptious soup from Thailand. Grab a big bunch of [...]

ExitApp


In this example, let's take on the role of a gaming script that announces which enemy to attack

Code: Select all

SetBatchLines -1
#NoTrayIcon
#SingleInstance force
#Include %A_ScriptDir%\node_modules
#Include biga.ahk\export.ahk

A := new biga()

enemies := [{"name": "bear", "hp": 200, "armor": 20, "status": "exposed_wound"}
    , {"name": "wolf", "hp": 100, "armor": 12}
    , {"name": "crab", "hp": 150, "armor": 99, "magicweakness": true}]

; Let's sort the enemies by hp and announce the one with the lowest hp as a target
sortedEnemies := A.sortBy(enemies, "hp")
; sorting by hp will move the lowest hp object to index 1
target := sortedEnemies[1].name

newEnemy := {"name": "weakened bear", "hp": 10, "armor": 5}
; a new enemy has appeared after we sorted the array. Instead of resorting; let's practice searching for the correct spot to insert at
mappedHP := A.map(sortedEnemies, "hp")
insertIndex := A.sortedIndex(mappedHP, newEnemy.hp)
; => 1
sortedEnemies.InsertAt(insertIndex, newEnemy)

; Assume rogues get bonus damage to anything with a status of "exposed_{{x}}" let's filter the sorted array by those and call all of them out. In this case there is only 1
exposedTargets := A.filter(sortedEnemies, Func("filterexposedFunc"))
filterexposedFunc(o) {
    global
    ; We use A.startsWith inside this function to check the status
    return % A.startsWith(o.status, "exposed")
}

; We can format our message with StartCase which is a ittle like ahk's TitleCase
callOutMessage := " Everyone attack: " A.startCase(sortedEnemies[1].name) "`n Rogues attack: " A.startCase(exposedTargets[1].name)

; Search the array of monsters for the first instance of one with "magicweakness" = true
magicweaknessTarget := A.find(sortedEnemies,"magicweakness")
callOutMessage := "Mages attack: " magicweaknessTarget.name

ExitApp


For the last example let's administer some online comments.

Code: Select all

SetBatchLines -1
#NoTrayIcon
#SingleInstance force
#Include %A_ScriptDir%\node_modules
#Include biga.ahk\export.ahk

A := new biga()

comments := [{"text": "I think...", "likes": 1, "sentiment": -9, "author": "Bob", "subscriber": false}
    , {"text": "You see...", "likes": 2, "sentiment": 10, "author": "Fred", "subscriber": true}
    , {"text": "Listen....", "likes": 9, "sentiment": 80, "author": "Barney", "subscriber": true}]

Newcomments := [{"text": "You see...", "likes": 2, "sentiment": 10, "author": "Fred", "subscriber": true}
    , {"text": "You see...", "likes": 2, "sentiment": 10, "author": "Fred", "subscriber": true}]

; You can combine two arrays with .concat. 
A.concat(comments, Newcomments)
; biga does NOT mutate inputs, changes are always delivered by the return value/object
comments := A.concat(comments, Newcomments)

; Theres a bug somewhere causing comments to be duplicated. In another example we used .uniq to remove duplicate strings, but it works on whole objects too.
comments := A.uniq(comments)

; Arrays can be reversed easily. Let's ensure that the negative comments are at the end of the array by sorting then reversing
negativeCommentsLast := A.reverse(A.sortBy(comments, "sentiment"))

; We can map the "sentiment" values to a new array for finding the average
sentimentArray := A.map(comments, "sentiment")
; => [-9, 10, 80]

; If you want to find out who is new in the comment section, how about mapping all the author names and finding the difference with regular posters
newFaces := A.difference(A.map(comments,"author"), ["Regular1","Regular2","Fred"])
; => ["Bob","Barney"]

; Let's choose a random subscriber comment to feature. Obviously we could filter by subscribers only and choose, but for the sake of example let's perform some logic
while (!featureComment) {
    comment := A.sample(comments)
    if (A.isMatch(comment, {"subscriber": true})) {
        featureComment := comment
    }
}

; Before injecting the comment into html, we can trim any leading and trailing whitespace
featureCommentText := A.trim(featureComment.text)
; let's trim any puncuation off the end as well
featureCommentText := A.trimEnd(featureCommentText, " .!?;:")

; .replace works like StrReplace but also accepts javascript formatted regular expressions. We'll remove any scripts the comment might have
featureCommentText := A.replace(featureCommentText, "/(<script>[\s\S]+<\/script>)/")

ExitApp
Last edited by Chunjee on 13 May 2020, 22:14, edited 7 times in total.
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (lodash for ahk)

29 Sep 2019, 10:49

Q & A

Why?
I like using Lodash, I really like ahk. I wrote this for myself so that I never have to miss my favorite utilities while playing with ahk. Maybe you will find it handy as well. When you get used to it you will spend way less time thinking about loops and more time thinking about your application.

What about _.{{x}}? When is that coming to biga.ahk?
It would be great if you could contribute to the project. But feel free to post your request(s) in this thread.

Cool how do I get started with it?
https://biga-ahk.github.io/biga.ahk/#/getting-started

Is this faster than plain ahk?
No, but also yes.
This is more about improving the time efficiency for the programmer than improving the time efficiency for the machine. It'll be easier and faster for you to code what you want, but it will run a few milliseconds slower than doing everything custom. SetBatchLines -1 is of course recommended.
1 hour cloud compute: $0.021811
1 hour USA programmer: $40+

Why do I have to install this with npm?
You don't. You can copy and paste it from github if you prefer.

Do I need nodeJS installed for this to work? Does this run through a browser?
No. These are all written in ahk. They just have the same method names. There are no outside dependencies.



Development Message (2021.01.08)
  • Continuing to add methods and documentation
  • At this time methods that mutate parameters such as .pull, .remove, etc are not planned to be included. As there is nearly always an immutable method of the same functionality, it is recommended to use those instead
  • Please let me know if you encounter the need for a method that hasn't been added yet
Last edited by Chunjee on 08 Jan 2021, 17:50, edited 11 times in total.
User avatar
kczx3
Posts: 1226
Joined: 06 Oct 2015, 21:39

Re: [Class] biga.ahk (utilities mirroring Lodash)

01 Oct 2019, 11:59

Why make the user instantiate the class?
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

01 Oct 2019, 17:40

kczx3 wrote:
01 Oct 2019, 11:59
Why make the user instantiate the class?

So as not to add to the global namespace without the user's knowledge. a one character variable is somewhat unconventional and allows the user to make that kind of decision.
As it is now mirrors other ahk class libraries and the Lodash norm.

Code: Select all

; ahk
ChromeInst := new Chrome("ChromeProfile")

Code: Select all

// javascript
var _ = require('lodash');
In future versions this could be changed by adding A := new biga() to the #include file if that is something the ecosystem wants.

Additionally:
Every library/module should be encapsulated as a class. The only global variable that should be used by that class is the class name.

All code inside your class should be written under the assumption that the class may become a nested class in the future.
Last edited by Chunjee on 15 Oct 2019, 16:44, edited 1 time in total.
burque505
Posts: 1423
Joined: 22 Jan 2017, 19:37

Re: [Class] biga.ahk (utilities mirroring Lodash)

01 Oct 2019, 20:05

Thank you very much for this, @Chunjee.
For my installation, the example script under "Installation" on the "Getting Started" guide page needed to be changed to

Code: Select all

#Include C:\Users\%A_UserName%\node_modules
and then worked.
I look forward a lot to using this. Your hard work on this is certainly appreciated.
Regards,
burque505
User avatar
kczx3
Posts: 1226
Joined: 06 Oct 2015, 21:39

Re: [Class] biga.ahk (utilities mirroring Lodash)

01 Oct 2019, 21:00

Aren’t all the methods static? If so, there’s probably no need to instantiate it.
guest3456
Posts: 3154
Joined: 09 Oct 2013, 10:31

Re: [Class] biga.ahk (utilities mirroring Lodash)

01 Oct 2019, 21:00

well done. keep instantiation

User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

01 Oct 2019, 21:43

burque505 wrote:
01 Oct 2019, 20:05
For my installation, [...] needed to be changed to #Include C:\Users\%A_UserName%\node_modules and then worked.
Ah that is probably the default path in a cmd console. I will update the documentation in the morning to include the instructions to navigate to the project forlder (via cd)

Thanks for the report and feedback.
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

01 Oct 2019, 22:16

kczx3 wrote:
01 Oct 2019, 21:00
Aren’t all the methods static? If so, there’s probably no need to instantiate it.
from my imperfect knowledge plus a few tests, __New() is not called when accessing the class like this, some methods will have unexpected behavior as they depend on attributes set when a new instance is created.

ie.
this.throwExceptions, this.caseSensitive, this.limit will all be ""

After reviewing GeekDude's Classes in AHK, a Dissection I see that class attributes can be set like so:

Code: Select all

static throwExceptions := true
static caseSensitive := false
static limit := -1

So I'll test that as well
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

02 Oct 2019, 13:16

in v0.3.4 the #Include is now structered

Code: Select all

class biga {
    ; class attributes
    static throwExceptions := true
    static caseSensitive := false
    static limit := -1
    ; {{methods}}
}

class A extends biga {
    ; no changes
}
Which means a "superobject" named A will be available at the global scope without creating an instance of biga(). This will continue to exist as a convenience but as a normal practice I would recommend creating an instance of any class you use.

I also added .tail and .head
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

06 Oct 2019, 16:20

v0.6.0 is now on npm

.matches is officially IN. The shorthands for .filter and .find are working and tested. I should be able to get the rest of the shorthands online this week.

Speaking of tests, there are 184 written and all passing :thumbup: :thumbup: :thumbup:
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

07 Oct 2019, 07:35

v0.7.0 adds .intersection and updates .sortBy to work with functions.
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

07 Oct 2019, 11:27

v0.8.0 adds .property (docs) and as a part of that I had to rethink and rewrite .map almost from scratch because it was faking that kind of shorthand before.
Last edited by Chunjee on 13 Oct 2019, 19:19, edited 1 time in total.
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

08 Oct 2019, 09:58

v0.9.0 adds .propertyMatches (docs) v0.9.1 adds some fixes so that it will accept both ["a", "b"] and "a.b" notation.

I had to dig deep to get this done. I've never used this but now that I understand it, I see how it can be useful. I will endeavor to add anther realworld example exploring all the shorthands (which btw are done, at least foundationally)


Filter all users by their privacy settings, which is nested under options object:

Code: Select all

objects := [{ "name": "fred", "options": {"private": true} }
    , { "name": "barney", "options": {"private": false} }
    , { "name": "pebbles", "options": {"private": false} }]

; style "a.b"
A.filter(objects, A.matchesProperty("options.private", false))
; => [{ "name": "barney", "options": {"private": false} }, { "name": "pebbles", "options": {"private": false} }]

; style ["a", "b"]
A.filter(objects, A.matchesProperty(["options", "private"], false))
; => [{ "name": "barney", "options": {"private": false} }, { "name": "pebbles", "options": {"private": false} }]
Last edited by Chunjee on 13 Oct 2019, 19:19, edited 1 time in total.
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

13 Oct 2019, 14:23

v0.10.2 is now on github and npm .every (docs) was the main addition and here is some examples:

Here we have a simple plain array that we check if every value is true in it.

Code: Select all

A := new biga()
votes := [true, false, true, true]
A.every(votes, Func("fn_istrue"))
; => false
fn_istrue(value) {
    if (value != true) {
        return false
    } 
    return true
}

Here we have more complex objects but using the .matchesProperty shorthand of [votes.1, "yes"] we can quickly check if everyone voted "yes" on option 1.

Code: Select all

A := new biga()
userVotes := [{"name":"fred", "votes": ["yes","yes"]}
            , {"name":"bill", "votes": ["no","yes"]}
            , {"name":"jake", "votes": ["no","yes"]}]

A.every(userVotes, ["votes.1", "yes"])
; => false
Hmmm how about if we check the 2nd voting option with [votes.2, "yes"]

Code: Select all

A.every(userVotes, ["votes.2", "yes"])
; => true
:thumbup: :thumbup: :thumbup:
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

15 Oct 2019, 10:50

v0.11.0 brings .partition (docs) and fixes what I would think was a big bug with some methods that can call functions.

Here is an example where we partition those voting users into two arrays, one that voted yes on option.1 and a second array that voted no.

Code: Select all

A := new biga()
userVotes := [{"name":"fred", "votes": ["yes","yes"]}
            , {"name":"bill", "votes": ["no","yes"]}
            , {"name":"jake", "votes": ["no","yes"]}]

A.partition(userVotes, ["votes.1", "yes"])
/* => [{"name":"fred", "votes":[1:"yes", 2:"yes"]}]
, [{"name":"bill", "votes":[1:"no", 2:"yes"]}, {"name":"jake", "votes":[1:"no", 2:"yes"]}]
*/
User avatar
Chunjee
Posts: 754
Joined: 18 Apr 2014, 19:05
GitHub: Chunjee

Re: [Class] biga.ahk (utilities mirroring Lodash)

21 Oct 2019, 09:58

v0.12.0 is now available on npm and github. .find and .filter now work with all three shorthands. between the two methods I think three-ish were missing previously.

I removed the test A.every(["", "", ""], A.isUndefined) ; => true because I just couldn't get it working and I got tired of messing with it. If someone could figure out that out it would be a valued addition.

Return to “Scripts and Functions”

Who is online

Users browsing this forum: No registered users and 22 guests