Sorting Objects in an Array by a property Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Gedrean
Posts: 22
Joined: 02 Jun 2019, 09:10

Sorting Objects in an Array by a property

22 Aug 2020, 13:20

EDIT: named the title something more descriptive.

** WARNING: this post is probably long, but maybe I need this much detail to explain it all **

Okay, I'm going to try to explain this as best as I can, because at this point my project has become confused and wrapped around itself.

First, I have an object I've created, it stores 3 simple properties, a String for a name, an integer for the Cost of the object, and a boolean/integer for whether it is a certain type of object or not.
So, for instance:
"Chair", 10, 1 - This is an object with the name Chair, a Cost of 10, and a 1 to indicate it is a particular type of object (a 0 indicates a different type).

I have a file I read these objects in from, line by line, in the format of Name, then a series of special characters to form a delimeter, then the Cost number, special characters for delimeter, then the object type.
So, I'd have...
Chair=+=10=+=1
Hat=+=5=+=0
Car=+=10=+=1
Etc.
Several of these objects that I'm pulling in are sharing Costs, for instance, I have multiple items with Cost 10.

I have had no issue using StrSplit and other functions to read the items all into a linear array, so I can pick them out one by one and work with them.

One function I have, however, is trying to find a RANDOM item by highest Cost. In the example above, the highest Cost items are Chair and Car, so it would randomly return one of those two.
I got the idea to store them into an associative array - that is, feed through the main array, item by item check the Cost, then store them in an associative array, where the key is the Cost.
Then it occurred to me I need each of those entries in the associative array to itself be an array, to store multiple items by the same "key".
So I now have an Associative Array (keys are the shared Cost of the objects), of simple arrays (each entry being an object with that same Cost).
I can fetch the highest Cost items, by going to the Associative Array's max index.
I can go to that referenced array, and pick a random item from it.

The problem comes when I want to remove the selected object from the original linear array, so that when this runs again, I have the remaining highest cost items, and so on, until I've exhausted the original array.
How can I programmatically remember WHERE that object came from in the original linear array?

I cannot just replace the external array with my new associative array, as other functions require the objects to be in a linear array, and not in a potentially sparse associative array.

Here's something I have so far

Code: Select all

RandomByHighest(ByRef arg_Array)
{
	local_Array := Array()
	For index, myObj in arg_Array ; Build the AssocArray here
	{
		local_Cost := myObj.Cost
		local_Array[local_Cost].Push(myObj)
	}
	local_HighestCost := local_Array.MaxIndex()
	local_Max := local_Array[local_HighestCost].MaxIndex()
	Random, local_Rand, 1, local_Max
	local_MyNewObj := local_Array[local_HighestCost][local_Rand]
	; So now, I've populated local_MyNewObj with a random object, in the highest cost key, of the whole associative array.
	; However, I want to do:
	; arg_Array.RemoveAt(the_index_of_my_local_MyNewObj)
	; How do I remember that index to remove the chosen object from the original linear array
}
Last edited by Gedrean on 25 Aug 2020, 10:11, edited 2 times in total.
User avatar
boiler
Posts: 16767
Joined: 21 Dec 2014, 02:44

Re: Arrays of Arrays complex issue

22 Aug 2020, 15:34

You can assign an ID number as one of the properties in the original object when you assign it to the linear array. It can (and probably would) start out as the same as the integer key for the linear array, although it won't always be the same because when you eventually remove items, the integer keys for the array will change, but the ID properties will remain with their associated objects. In your new associative array, each would also have that ID property. So when you've selected one of the items in your associative array to remove, you would find the item in the linear array that has the matching value for the ID property and remove that.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Arrays of Arrays complex issue

22 Aug 2020, 15:56

id sooner switch to using a rdbms rather than attempting to emulate one in code
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: Arrays of Arrays complex issue

22 Aug 2020, 18:04

Sounds like You have

Code: Select all

Array := [["Chair", 10, true], ["Hat", 5, false], ["Car", 10, true]]
personally I would prefer

Code: Select all

Array := [{"name": "Chair", "cost": 10, "bool": true},
		, {"name": "Hat", "cost": 5, "bool": false}, 
		, {"name": "Car", "cost": 10, "bool": true}]
Because you don't have to remember in your head 'oh first is the name, the 2nd one is the cost, the next one is the bool' or deal with bugs if/when they aren't in the right order.

I'm not sure "RANDOM item by highest Cost" means. Of all the cost 10 items, choose a random one?
Last edited by Chunjee on 22 Aug 2020, 21:54, edited 1 time in total.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: Arrays of Arrays complex issue

22 Aug 2020, 18:17

Find the most expensive item(s) and choose a random one:

Code: Select all

A := new biga() ; requires https://www.npmjs.com/package/biga.ahk

itemsArray := [{"name": "Chair", "cost": 10, "bool": true}
			, {"name": "Hat", "cost": 5, "bool": false}
			, {"name": "Car", "cost": 10, "bool": true}]


; find the highest cost item by sorting by cost and assinging the last element (the highest) cost to a var
highestCost := A.sortBy(itemsArray, "cost")[itemsArray.Count()].cost
msgbox, % highestCost
; => 10

; filter all items that don't have a cost of `highestCost` out
highestCostItemsArray := A.filter(itemsArray, ["cost", highestCost])
; => [["bool":1, "cost":10, "name":"Chair"], ["bool":1, "cost":10, "name":"Chair"]]

; choose one random item from the highest cost array
randomItem := A.sample(highestCostItemsArray)

; msgbox the randomly selected items name
msgbox, % randomItem.name
; => "Chair"
Last edited by Chunjee on 23 Aug 2020, 05:14, edited 1 time in total.
Gedrean
Posts: 22
Joined: 02 Jun 2019, 09:10

Re: Arrays of Arrays complex issue

23 Aug 2020, 01:49

Chunjee wrote:
22 Aug 2020, 18:04
Sounds like You have

Code: Select all

Array := [["Chair", 10, true], ["Hat", 5, false], ["Car", 10, true]]
personally I would prefer

Code: Select all

Array := [{"name": "Chair", "cost": 10, "bool": true},
		, {"name": "Hat", "cost": 5, "bool": false}, 
		, {"name": "Car", "cost": 10, "bool": true}]
Because you don't have to remember in your head 'oh first is the name, the 2nd one is the cost, the next one is the bool' or deal with bugs if/when they aren't in the right order.

I'm not sure "RANDOM item by highest Cost" means. Of all the cost 10 items, choose a random one?
Yes, I am using something like your latter example. Namely I created a class (because I'm used to OOO programming), and gave it a few named properties and methods.

And yes, the goal is to find all the items that have the highest cost, choose a random one of those.
User avatar
Chunjee
Posts: 1400
Joined: 18 Apr 2014, 19:05
Contact:

Re: Arrays of Arrays complex issue

23 Aug 2020, 05:31

Gedrean wrote:
23 Aug 2020, 01:49
And yes, the goal is to find all the items that have the highest cost, choose a random one of those.
In my 2nd post I did that. Kinda fun.


filter by functor and property:

Code: Select all

A := new biga() ; requires https://www.npmjs.com/package/biga.ahk

itemsArray := [{"name": "Chair", "cost": 10, "requiresLicense": false}
			, {"name": "Hat", "cost": 5, "requiresLicense": false}
			, {"name": "Car", "cost": 10, "requiresLicense": true}
			, {"name": "Toothpick", "cost": 1, "requiresLicense": false}]


; filter all items that cost less than 3 out
filteredItems := A.filter(itemsArray, func("fn_CostsMoreThan3"))
; => [["cost":10, "name":"Chair", "requiresLicense":0], ["cost":5, "name":"Hat", "requiresLicense":0], ["cost":10, "name":"Car", "requiresLicense":1]]

filteredItems := A.filter(filteredItems, {"requiresLicense": true})
; => [["cost":10, "name":"Car", "requiresLicense":1]]

; choose one random item from the filtered array
randomItem := A.sample(filteredItems)

; msgbox the randomly selected items name
msgbox, % randomItem.name
; => "Car"


; ---functions---
fn_CostsMoreThan3(item)
{
	if (item.cost > 3) {
		return true
	}
}
Sorry I don't fully understand some of other the array juggling problems presented in the OP. I would leave your main array intact always, then make clones that allow to to manipulate/exhaust all elements without making changes to the main array.
Gedrean
Posts: 22
Joined: 02 Jun 2019, 09:10

Re: Arrays of Arrays complex issue

23 Aug 2020, 22:49

swagfag wrote:
22 Aug 2020, 15:56
id sooner switch to using a rdbms rather than attempting to emulate one in code
I'm starting to feel that way too, but I don't really have an ability to swap out what I'm doing.

This is basically doing a really cruddy sort function, which... I mean, if I could build a sort function in AHK that was anything better than O(N^2) I'd be super happy. Haha.
Gedrean
Posts: 22
Joined: 02 Jun 2019, 09:10

Re: Arrays of Arrays complex issue  Topic is solved

25 Aug 2020, 10:09

So, it turns out I was making this more complicated than it needed to be...

With my original code, I was trying to push the object I pulled from the original array, into the sorting array.
Then complaining I lost the index.

What I forgot was two things:
1. In the loop, I have access to the index number of the object.
2. I can use RemoveAt to not only remove the desired random object from the original array, but retrieve it to do things with, knowing only the index... which I have in the loop.

Instead of pushing the objects into the array (and duplicating a lot of ram) I can push the indexes into the array, then pick one of THOSE at random.

As a result, here's the properly finished code that works:

Code: Select all

RandomByHighest(ByRef arg_Array)
{
	local_Array := Array()
	For index, myObj in arg_Array ; Build the AssocArray here
	{
		local_Cost := myObj.Cost ; Get the cost of the object
		local_Array[local_Cost].Push(index) ; Push the INDEX of the object in question, not the object itself... because I can still retrieve the object by the index
	}
	; Now, local_Array ranges from 5-10-15-whatever costs are
	local_HighestCost := local_Array.MaxIndex() ; This finds out the highest cost item(s)
	local_Max := local_Array[local_HighestCost].MaxIndex() ; This is the max number of entries I have to pick from in that highest cost list
	Random, local_Rand, 1, local_Max ; Random number amongst them
	local_MyIndex := local_Array[local_HighestCost][local_Rand] ; Fetch the index number located at that random location
	local_MyNewObj := arg_Array.RemoveAt(local_MyIndex)
	; now the object is removed from the original array, and is in my new object item here.  I can return that.
	return local_MyNewObj
}

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot], jrachr, sbrady19, wilkster and 120 guests