ASH Maps question.

Nightmist

Member
Hmmm just wondering but is there a function in ASH that will "remove" a section of a ASH map? (The manual doesnt even have any documentation on maps so im left with asking the people here >>)
 

Veracity

Developer
Staff member
There are functions to count the number of keys in a map and test whether a particular key is present. You can insert a new key/value pair into the map using an assignment statment. But, I don't think there's currently a way to remove a key. (I say "I think" since I'm at work now and can't check - or change - the code.)

It's a reasonable request, so I'll do it when I get home.
 

Tirian

Member
Ooh, neat. I've got a few things of my own that I'd love to have clarified.

Currently, to the best of my investigation, if you query a map with a key that is not defined, it returns the most "zero-ish" member of the codomain. Is this something we can count on?

Also, will the "foreach" iterator go through a map in the order in which the keys were initialized, or will it be less predictable than that?
 

Veracity

Developer
Staff member
[quote author=Tirian link=topic=289.msg1532#msg1532 date=1153009566]Currently, to the best of my investigation, if you query a map with a key that is not defined, it returns the most "zero-ish" member of the codomain.  Is this something we can count on?[/quote]
Yes. Every data type has an "initial" value and that is what you get if you try to map an unset key in a map.

Also, will the "foreach" iterator go through a map in the order in which the keys were initialized, or will it be less predictable than that?
Make no assumptions about the order of the keys.

Regarding the "delete key" operator, I've been pondering it. What you currently have available:

map[index] = value;
boolean map contains index;
int count(map);

A "delete" operator takes a key of whatever type is needed by the map slice. So, we'd need something like:

delete(map, key);

...where the type of key depends on the expected type of the key to the map slice you give. ASH doesn't currently have functions - even built-in ones - for which an argument can be of any type, presumably checked at run time, rather than compile time, which is what we really want.

I'm thinking:

Just like we have

boolean <map> contains <key>

where <key> is of the "correct type for the map slice" - and is checked at compile time - perhaps we can have:

<value type> <map> delete <key>

where <key> is of the correct type and the return value of the expression is the value that was in the map for that key before.

string [int] foo;
foo[5] = "xyz";

foo contains 5 --> true
foo contains 6 --> false
foo[5] --> "xyz";
foo[6] --> "" // default value for a string

new API:

string old = foo delete 5;
old --> "xyz";
old = foo delete 5;
old --> "" // default value for a string, since the key is not present

What do you think?
 

Tirian

Member
This must be why non-OO languages don't use maps. :D

I think I prefer the look of the way Perl does this, which would translate to ASH as "old = delete foo[5];" So that it looks to the human like a value, but the language has to do a little extra work to detach the key from the map (which might be bar[2][$item[cog]]["semprini"] instead of "foo").
 

Veracity

Developer
Staff member
OK. I just added a "remove <aggregate reference>" operator to ASH.

An aggregate reference is "<map name>[ <index 1> ... <index n> ]"
<map name>[ <index 1> ... <index n-1> ] specifies the "slice"
<index n> specifies the "key"

Which is just what you expect, if you fully specify the indices; for a single dimensional map, "map[10]" -> "map" is the slice and 10 is the key.

"remove" removes the "key" from the "slice".

Code:
string [int] map1;

map1[5] = "foo";
print( count( map1 ) + " " + map1 contains 5 + " " + map1[5] );
print( "remove: " + remove map1[5] );
print( count( map1 ) + " " + map1 contains 5 + " "  + map1[5] );
print( "remove: " + remove map1[5] );

int [string, string] map2;

map2["me","you"] = 17;
print( count( map2["me"] ) + " " + map2["me"] contains "you" + " " + map2["me","you"] );
print( "remove: " + remove map2["me", "you"] );
print( count( map2["me"] ) + " " + map2["me"] contains "you" + " " + map2["me","you"] );
print( "remove: " + remove map2["me", "you"] );

print( count( map2 ) + " " + map2["me"] );
print( "remove: " + remove map2["me"] );
print( count( map2 ) + " " + map2["me"] );
yields:

 > remove.ash
1 true foo
remove: foo
0 false
remove: 
1 true 17
remove: 17
0 false 0
remove: 0
1 aggregate int [string]
remove: aggregate int [string]
0 aggregate int [string]
Script succeeded!
 
Just so I do not have to start a new thread, I have a map question:

Is it possible to "pull" a key value from a map for a specific result?

I have tried to read Veracity's notes on maps... But for some reason I can't wrap my brain around them fully... Maybe I am getting just too old to understand programming as easily as I could. :p

But let me clarify:

If you made a map for MP restorers, where the two keys are the min MP and the max MP, could you pull the key value from the item assigned to it?


And another question, that is pretty much moot if the above is false:


Can maps have more than two keys?
 

Nightmist

Member
[quote author=Presto Ragu link=topic=289.msg1665#msg1665 date=1153801758]
If you made a map for MP restorers, where the two keys are the min MP and the max MP, could you pull the key value from the item assigned to it?


And another question, that is pretty much moot if the above is false:


Can maps have more than two keys?
[/quote]

I'm sort of confused at what your asking but something like this might suit your needs (Total guess at what your trying to do.).
Code:
int [ item, string ] HP_Restore_Map;
HP_Restore_Map [ $item[none], "min"] = 5;
HP_Restore_Map [ $item[none], "max"] = 10;

print( HP_Restore_Map [ $item[none], "min"]);
print( HP_Restore_Map [ $item[none], "max"]);
This will print the minimum and then max. (Although you could just use 2 different maps.)
 

Tirian

Member
Yup, I was just writing what Nightmist just said, that two maps seems like it would be the best way to do that. Assuming we're understanding your MP restorer question correctly.

As far as figuring out how to use maps, I've started working them into my scripts, so maybe seeing them being used in context would give you some ideas to reinforce Veracity's primer:

http://kolmafia.us/index.php/topic,139.0.html -- the bounty hunter section contains a map of all of the items that you could want to redeem with the bounty hunter, iterated through with the foreach command.

http://wiki.kolmafia.us/?page_id=12 -- sample 7 shows a different use of maps where we don't care about the value but are just using a bunch of keys like a set and using the "contains" operator to see if a value is in the set.

http://kolmafia.us/index.php/topic,297.0.html -- here I'm building maps to contain my logic for when I've picked up all the items I could want from adventuring in a certain location, and then passing that map to a function to see if I've completed it.
 

Veracity

Developer
Staff member
[quote author=Presto Ragu link=topic=289.msg1665#msg1665 date=1153801758]
Is it possible to "pull" a key value from a map for a specific result?[/quote]
A map defines a "mapping" from keys of a particular type to values of a (potentially different) particular type. So, if you want to map integers to strings, your map could contain:

Code:
1 -> "first string"
8 -> "another string"
12 -> "another string"

Note that the keys are unique within a given map - the same key does not map to multiple values - but the values are not unique. At least partly for that reason, ASH provides no "inverse mapping" facility which will tell which key(s) map to a specified value.

[quote author="Presto Ragu"]If you made a map for MP restorers, where the two keys are the min MP and the max MP, could you pull the key value from the item assigned to it?[/quote]

Maps cannot have "two keys". Any given map has exaqctly ONE key, which is used as described above. Now, ASH provides syntax for making multi-dimensional maps, but internally, those are really "maps of maps of ...".

Code:
[1,2] -> "a string"
[1,3] -> "another string"
[2,3] -> "yet another string

This looks like two keys, and for many purposes you can simply use them like that. But what it really is is two layers of maps, and there are times when you need to be aware of that. What that really is:

mapping from integer to ( mapping from integer to string )

Code:
1 -> <map from integer to string>
    2 -> "a string"
    3 -> "another string"
2 -> <map from integer to string>
    3 -> "yet another string"

If you look at things like that, you can see that this is how you can store multiple values for a given key; the key "1" has two values, which are referenced in turn by a second integer.

You can often think of the two values as both being keys of the same map, but they're not really, and if you look at how you'd use foreach, it might be more obvious:

Code:
string [int, int] map1;

foreach key1 in map1
    // key1 is as integer and the value is a map from int to string
    foreach key2 in map1[key1]
        // key2 is an integer and the value is a string
        print("[" + key1 + "," + key2 + "] -> " + map[key1, key2] );

In your example, I'm not sure you want to map from "min HP" AND "max HP" to an item. In fact, using either "min HP" or "max HP" as a key in a map is questionable, since maps require their keys to be unique; what result would you expect if you had two different items with the same "min HP" restored?

[quote author="Presto Ragu"]Can maps have more than two keys?[/quote]

A map can have exactly one key, but if you want to make "maps of maps of ..." and access such using [key, key, ...] index syntax, there is no limit to the number of dimensions.
 
Well...

First, since most of you hadn't noticed, I have already grasped the basic uses of maps.

If you check my new version of MuseumStockUp here, and RestorerSelector here, you will see that simple uses I can handle already. So, script examples weren't even needed either.

I was simply looking for answers to specific questions.

And since my intentions were not quite received correctly, let me try again.

I personally despise the idea of having to define multiple maps for multiple pieces of data about the same "object." Perhaps it is my stubbornness... But when Veracity claimed that maps were much more usefull than arrays, I expected them to behave similarly.

What I had in mind was a multiple dimension map that held all of the usefull information about foods and drinks.

For example:

Code:
item [int, stat, int, int] FoodItem;
FoodItem [<fullness>, <main stat increase, if any>, <minimum adventures gained>, <maximum adventures gained>] = $item[<whatever>];

This would allow the user to reference almost any data that they wanted related to a food item. You would be able to average the adventure gain, and divide by the fullness in order to get the adventures per fullness if you want.

It seems silly to my mind to have multiple maps to hold the different values when there should already be a way to reference the values in one multiple dimensional map.


But I guess I am too old school to see how maps are better than arrays yet... :p
 

Nightmist

Member
[quote author=Presto Ragu link=topic=289.msg1676#msg1676 date=1153889291]
It seems silly to my mind to have multiple maps to hold the different values when there should already be a way to reference the values in one multiple dimensional map.
[/quote]

Thats interesting although in effect it just compresses the number of lines you need for a single item down to a single line (And then again maps just compress a series of if-return functions into less lines...)

I would be rather interested in how you could define which value you expected returned though.


Although to answer your original question with the examples you just gave then no it isnt current possible with the current ASH map system. (Well possible as far as I know of)
 

Veracity

Developer
Staff member
[quote author=Presto Ragu link=topic=289.msg1676#msg1676 date=1153889291]I personally despise the idea of having to define multiple maps for multiple pieces of data about the same "object."  Perhaps it is my stubbornness...  But when Veracity claimed that maps were much more usefull than arrays, I expected them to behave similarly.[/quote]

I believe that my position was that you can do anything with a map that you can with an array, and more. Nothing you've said makes me want to change that position.

[quote author="Presto Ragu"]What I had in mind was a multiple dimension map that held all of the usefull information about foods and drinks.

For example:

Code:
item [int, stat, int, int] FoodItem;
FoodItem [<fullness>, <main stat increase, if any>, <minimum adventures gained>, <maximum adventures gained>] = $item[<whatever>];
[/quote]

You are asking, essentially, for data structures, and for a map to be able to go from item to your structure.

You can get the effect you want, today, using maps as they exist in ASH.
Nightmist told you exactly how to do it.

int [item, string] map;
map[ $item[foo], "fullness" ] = 2;
map[ $item[foo], "min stat increase" ] = 5;
map[ $item[foo], "max stat increase" ] = 10;
map[ $item[foo], "min adventure gained" ] = 6;
map[ $item[foo], "max adventure gained" ] = 16;

Since you seem to be using "arrays" as your model for what you seek, this is the equivalent of you having an array of <n> integers, and the value you fetch from the map is such an array. The second index of the map is the index into your array.

Since you refer to "old school" thinking, you'd probably do it like this:

int FULLNESS = 0;
int MIN_ADVENTURES = 1;
int MAX_ADVENTURES = 2;

int [ item, int] map;
map[ $item[foo], FULLNESS ] = 3;
map[ $item[foo], MIN_ADVENTURES ] = 6;
map[ $item[foo], MAX_ADVENTURES ] = 16;

...which has precisely the same effect as what was done above with a string as the second index.

Now, if you wanted different data types - integers, stats, whatever - then you couldn't store them in a single map. You could not say:

map[ $item[foo], "stat" ] = $stat[muscle];

to store a "stat" in your map whose values are ints.

But then again, you couldn't store them in a single array in most languages, either. You would need a data structure:

struct x {
   int field1;
   stat field 2;
   item field 3
   ...
};

However, since you mention "arrays" as what you want the power of, this is moot; it's more than an "array" would give you.

[quote author="Presto Ragu"]This would allow the user to reference almost any data that they wanted related to a food item.  You would be able to average the adventure gain, and divide by the fullness in order to get the adventures per fullness if you want.[/quote]

Go for it. Use ASH maps as they exist today to hold the three integers you mention, indexed by item and by string, as described above.

[quote author="Presto Ragu"]But I guess I am too old school to see how maps are better than arrays yet...  :p[/quote]

I can give you examples of things that you can do with an ASH map that you can't do with an "array". But so far you have failed to give an example of something you can do with an "array" that you can't do with an ASH map.
 

macman104

Member
[quote author=Veracity link=topic=289.msg1679#msg1679 date=1153892145]But then again, you couldn't store them in a single array in most languages, either. You would need a data structure:

struct x {
int field1;
stat field 2;
item field 3
...
};[/quote]Completely irrelevant to the discussion, I was just excited because I just read about structures for a program I was working on, and I understood what you wrote. Yay!
 

Veracity

Developer
Staff member
Looking again at what you request:

[quote author=Presto Ragu link=topic=289.msg1676#msg1676 date=1153889291]
Code:
item [int, stat, int, int] FoodItem;
[/quote]

Yup. You are explicitly asking for "structs".

struct FoodItem {
    int fullness;
    stat mainStat;
    int minAdventures;
    int maxAdventures;
};

And a way to map an "item" to such a structure and reference the various fields.

Sorry. All your talk about "arrays" mislead me, since an "array" in just about any language I've heard of wouldn't help you here; an "array" is defined to be a data that holds data of a particular type - which can be an aggregate types like a "struct", in languages that provide that.

A "struct" holds items of disparate types and thus can't be modeled by an array. It's an orthogonal concept.

Now, if we added "structs" to ASH to allow you to group together disparate data types as you desire, I'd expect that you could store them in a map indexed by items to achieve the result you want.

But an ASH "map" is not an implementation of a "stuct".
It's merely a superset of an "array".
 
Well, when I was doing Pascal programming back in High School... Some 15 or so years ago, what we used that were called arrays allowed us to define what the dimensions were, what those dimensional values were, and reference them as necessary.

Maybe they were structs, and we never learned to call them what they really were...

I will grant you that.

But that is what I think of when I think of arrays. In short, it would be like building a spreadsheet within the script and being able to reference each cell as you needed. Not make five or six pages, and have to reference them separately.

But, I did receive my answers. Thank you.
 

Veracity

Developer
Staff member
[quote author=Presto Ragu link=topic=289.msg1684#msg1684 date=1153931271]
Well, when I was doing Pascal programming back in High School...  Some 15 or so years ago, what we used that were called arrays allowed us to define what the dimensions were, what those dimensional values were, and reference them as necessary.[/quote]

I used to program in Pascal. Professionally, even. But the last time I even looked at a Pascal program was in 1979, so I am rather rusty.

The Wikipedia has an article that compares Pascal and C, including arrays.

Near as I can tell, the main difference is that you can use user-defined types - enums, in particular - as array indices. So, you can do:

Code:
type enum = (red, green, blue);
myarray = array[enum] of integer;

and reference it with

Code:
myarray[red]

But notice: the element type is specified - "integer" in that case.

This provides nothing that ASH doesn't provide.

You want to "define what the dimensions were, what those dimensional values were, and reference them as necessary"? OK.

item [int, int, stat, int] testMap;
testMap[ 5, 5, $stat[ muscle ], 123 ] = $item[ knob sausage chow mein ];

The key thing in your original request which is absent in ASH - and in every other language's implementation of "arrays", including Pascal's - is the ability to say "I have this value - $item[ knob sausage chow mein ] - and I want to get the set of array (map) indices where that value is stored." You want some sort of content-addressable data structure, and ASH - and Pascal and C and Java and ... - don't provide that facility. EXCEPT by turning it around and making the value - the item, in this case - be what you index on and the set of indices be a composite data type that you get by indexing on the item.

And that is why we-all have been trying to steer you into thinking about structs - "records", as they are called in Pascal - as that is the language construct that provides what you are asking for.

Arrays - and maps - are "aggregate" data types: they hold multiple pieces of data of the same type.

Structs - and records - are "compound" data types: they hold multiple pieces of data of diverse types.

In short, it would be like building a spreadsheet within the script and being able to reference each cell as you needed.  Not make five or six pages, and have to reference them separately.

Can a spreadsheet reference a cell by its contents, and not by row and column? Can you say "what are the row and column where the number 100.23 appears?" I don't use spreadsheets, so I'm in no position to say, but I'd never heard that they were content-addressable...
 
Well, once I got over the problem of things not working like I thought they should, I have figured out that it should be possible to get it to work.

It is very convoluted, and frankly there are problems with it...

First, I can't seem to get my sample script to work. That is likely due to the fact that I just haven't grasped the nuances of maps yet.

Second, without the ability to be able to pass data from functions by reference, it becomes operationally more efficient to use multiple maps. Although... As far as defining goes, it is easier to do it with one map.

But I will present my sample script for everyone to look over, and see what I was thinking when I wrote it. Bear in mind, there is an error that prevents it from functioning properly.


EDIT:: Oh, founditem was intended for a check if an item was even found. I forgot to finish that logic, since I never got the basics working. The line would look something like:
Code:
if( !founditem)
{
   return "The requested item could not be found";
}

And that would be before the checks for testvalue.
 

Attachments

  • SampleScript.ash
    1.2 KB · Views: 63

Tirian

Member
The first problem is that your data structure is not conducive to maps at the moment. For simplicity's sake, I got rid of the function in the middle just to test your assignment.

Code:
item [int, stat, int, int] FoodItem;

FoodItem [6, $stat[muscle], 21, 29] = $item[spaghetti with skullheads];
FoodItem [6, $stat[moxie], 21, 29] = $item[fettucini inconnu];
FoodItem [6, $stat[mysticality], 21, 29] = $item[gnoccetti di nietzsche];

void main()
{
	print(count(FoodItem));
}

You can verify that the output is 1, not 3. This is because FoodItem is not a map with four keys, it is a chain of four maps. The first assignment is 6 => (muscle => (21 => (29 => s.w.s))), and your second assignment overwrites that with a new value for 6.

This seems to work:

Code:
boolean [ item, int, stat, int, int ] FoodItem;
FoodItem [ $item[spaghetti with skullheads], 6, $stat[muscle], 21, 29] = true;
FoodItem [ $item[fettucini inconnu], 6, $stat[moxie], 21, 29] = true;
FoodItem [ $item[gnoccetti di nietzsche], 6, $stat[mysticality], 21, 29] = true;

string unravel (item food_key, string value_wanted)
{
	if ( !FoodItem contains food_key )
	{
		print( "no such food!" );
		return "";
	}
	foreach fullness in FoodItem[ food_key ]
	{
		if ( value_wanted == "fullness" ) return int_to_string( fullness );
		foreach mainstat in FoodItem[ food_key, fullness ]
		{
			if ( value_wanted == "mainstat" ) return stat_to_string( mainstat );
			foreach minadventures in FoodItem[ food_key, fullness, mainstat ]
			{
				if ( value_wanted == "minadventures" ) return int_to_string( minadventures );
				foreach maxadventures in FoodItem[food_key, fullness, mainstat, minadventures]
				{
					if ( value_wanted == "maxadventures" ) return int_to_string( maxadventures );
				}
			}
		}
	}
	print( "Illegal value sought!" );
	return "";
}

void main()
{
	print( unravel($item[spaghetti with skullheads], "fullness"));
	print( unravel($item[fettucini inconnu], "maxadventures"));
}
 
[quote author=Tirian link=topic=289.msg1693#msg1693 date=1153978960]
You can verify that the output is 1, not 3.
[/quote]

Well... That is just... Wow...

I think I need to rewrite my Restorer script now. Because if that is true, then I am getting maps of 1 item each...

Can you verify that this is the way you wanted it to work Veracity? If so, then I for one vote for plain old arrays. Even the ones that aren't sructs. You can keep your versatility, I'd just take simplistic functionality at this point. :S


EDIT:: That can't be right Tirian...

My Restorer script assigns 18 items. 1 map with 9 items that have a first key of "hp", and 9 items that have a first key of "mp." And that works as intended... The following ones do not overwrite the previous ones.

I think it is official.

I do not understand. :p
 
Top