ASH - iterating through a list of items

izchak

Member
First off, I would like to thank holatuwol, veracity, daychilde, and everyone else who has made kolmafia and ASH scripting possible. They have provided the community with a great tool, and I really appreciate it.

Now, as the title suggests, I want to iterate through a list of items and do stuff with some of them.
In fact, I'm interested in checking if I have any fruit or booze to improve via Nash Crosby's still, but this kind of loop-thru-item code could have many other uses.
I'm mostly a perl scripter by trade, so perhaps it would be best to illustrate with some pseudo perl code how I would go about accomplishing this, before I translate it to ASH.
Code:
foreach $garnish in ("grapefruit", "lemon", "olive", "orange", "soda water", "strawberry") {
	if item_amount($garnish) print "hey, you have some $garnish";
}
Define a list of strings, iterate through it, foreach item, check if I have some of them. If so, print.

So far, the closest I have come to translating this to ASH is this:
Code:
string [int] supergarnishes;
supergarnishes[0] = "grapefruit";
supergarnishes[1] = "lemon";
supergarnishes[2] = "olive";
supergarnishes[3] = "orange";
supergarnishes[4] = "soda water";
supergarnishes[5] = "strawberry";

int i = 0;
while(i < count(supergarnishes) ){
	if(item_amount( string_to_item(supergarnishes[i])  > 0) {
		print("hey, you have some " + supergarnishes[i]);
	}

	i = (i + 1);
}

Now, I'm fairly sure there is a way I can use the ASH foreach statement instead of my somewhat klunky (int i, while, i++) loop, but my klunky looping isn't the problem. I get this error:
Cannot apply int to string_to_item() (test.ash, line 11)

So, I guess supergarnishes[ i ] is returning an int, instead of the actual string. How would I rewrite that little bit to give me the string, instead of an int?
Should I be using some kind of temp string inside my while() loop?

Furthermore, if someone could show me a cleaner way to loop through a list of items, doing stuff with them, even if just via a swift hyperlink, that'd be great!
For instance, is there a better way to define a bunch of list elements at once?

I apologize if this is a repeat of someone else's question, or if this is clearly illustrated elsewhere and I have missed it, but I am fairly stumped and would greatly appreciate it if someone could help me.
 

Nightmist

Member
[quote author=izchak link=topic=779.msg3761#msg3761 date=1171857891]
I'm mostly a perl scripter by trade, so perhaps it would be best to illustrate with some pseudo perl code how I would go about accomplishing this, before I translate it to ASH.
Code:
foreach $garnish in ("grapefruit", "lemon", "olive", "orange", "soda water", "strawberry") {
	if item_amount($garnish) print "hey, you have some $garnish";
}
[/quote]
In ash your looking at this:
Code:
item [ int] MapName
MapName [ 0] = $item[grapefruit];
MapName [ 0] = $item[lemon];

foreach Key1 in MapName
{
 if( item_amount( Key1)) {  print( "Yay, we have: "+item_amount( MapName[Key1])+" of "+MapName[Key1]); }
}
My syntax might be wrong but you should get the basic idea of foreach loops in ASH from that >>.


PS. The error your getting is because you didn't close of the item_amount before you did the "> 0" comparason to it. (Comparing a item to int instead of the item_amount)
 

Veracity

Developer
Staff member
[quote author=izchak link=topic=779.msg3761#msg3761 date=1171857891]Now, I'm fairly sure there is a way I can use the ASH foreach statement instead of my somewhat klunky (int i, while, i++) loop, but my klunky looping isn't the problem. I get this error:

Cannot apply int to string_to_item() (test.ash, line 11)

So, I guess supergarnishes[ i ] is returning an int, instead of the actual string. How would I rewrite that little bit to give me the string, instead of an int?[/quote]
Well, in the code as listed:

Code:
int i = 0;
while(i < count(supergarnishes) ){
    if(item_amount( string_to_item(supergarnishes[i])  > 0) {
        print("hey, you have some " + supergarnishes[i]);
    }

    i = (i + 1);
}

...you appear to be missing a parenthesis. Fix:

Code:
int i = 0;
while (i < count(supergarnishes) ) {
    if ( item_amount( string_to_item(supergarnishes[i]) )  > 0) {
        print("hey, you have some " + supergarnishes[i]);
    }

    i = (i + 1);
}

But perhaps you would prefer this:

Code:
boolean [item] supergarnishes;
supergarnishes[ $item[grapefruit] ] = true;
supergarnishes[ $item[lemon] ] = true;
supergarnishes[ $item[olive] ] = true;
supergarnishes[ $item[orange] ] = true;
supergarnishes[ $item[soda water] ] = true;
supergarnishes[ $item[strawberry] ] = true;

foreach key in supergarnishes
    if (item_amount( key ) > 0)
        print( "hey, you have some " + key );
 

izchak

Member
[quote author=Nightmist link=topic=779.msg3762#msg3762 date=1171858772]

In ash your looking at this:
Code:
item [ int] MapName
MapName [ 0] = $item[grapefruit];
MapName [ 0] = $item[lemon];

foreach Key1 in MapName
{
 if( item_amount( Key1)) {  print( "Yay, we have: "+item_amount( MapName[Key1])+" of "+MapName[Key1]); }
}
My syntax might be wrong but you should get the basic idea of foreach loops in ASH from that >>.


PS. The error your getting is because you didn't close of the item_amount before you did the "> 0" comparason to it. (Comparing a item to int instead of the item_amount)
[/quote]

Aha!
Based upon your fix, Nightmist, I came up with this instead...
Code:
item [int] supergarnishes;
supergarnishes[0] = $item[grapefruit];
supergarnishes[1] = $item[lemon];
supergarnishes[2] = $item[olive];
supergarnishes[3] = $item[orange];
supergarnishes[4] = $item[soda water];
supergarnishes[5] = $item[strawberry];

foreach garnish in supergarnishes {
	if( item_amount( supergarnishes[garnish]) > 0) {
		print( "Yay, we have: "+item_amount( supergarnishes[garnish])+" of "+ supergarnishes[garnish]); 
	}
}

Which effectively answers one of my original questions. Thank you, Nightmist!
I'll tackle Veracity's approach in a separate post, as it effectively refactors my code, which is also what I was hoping for...
[quote author=Veracity link=topic=779.msg3763#msg3763 date=1171859578]

.....

But perhaps you would prefer this:

Code:
boolean [item] supergarnishes;
supergarnishes[ $item[grapefruit] ] = true;
supergarnishes[ $item[lemon] ] = true;
supergarnishes[ $item[olive] ] = true;
supergarnishes[ $item[orange] ] = true;
supergarnishes[ $item[soda water] ] = true;
supergarnishes[ $item[strawberry] ] = true;

foreach key in supergarnishes
    if (item_amount( key ) > 0)
        print( "hey, you have some " + key );
[/quote]

Ooh, thank you, Veracity!
I like the way you refactored my list, it makes for a far more usable data structure than my original list of strings.

Just to clarify, is there any kind of way to 'push' a bunch of elements onto a map/list, or do you need to assign them one by one, as in your example?

I suspect that half of the reason I have problems with ASH maps is that I keep thinking of them as a list, but a map is an entirely different structure...

Anyways, thank you for your swift response, it answers my question, and more!
 

Nightmist

Member
[quote author=izchak link=topic=779.msg3765#msg3765 date=1171861001]
Just to clarify, is there any kind of way to 'push' a bunch of elements onto a map/list, or do you need to assign them one by one, as in your example?

I suspect that half of the reason I have problems with ASH maps is that I keep thinking of them as a list, but a map is an entirely different structure...
[/quote]
Uhhh I'm not too clear on this but I'm going to take a blind guess and hope for the best >>.
Code:
boolean [ string, int,  item] MapName;
MapName[ "Fruit1", 1, $item[grapefruit]] = true;
MapName[ "Fruit2", 2, $item[lemon]] = true;

foreach String1 in MapName
{
 foreach Int1 in MapName[ String1]
 {
  foreach Item1 in MapName[ String1, Int1]
  {
   print( "The boolean at ["+String1+","+Int1+","+Item1+"] is a "+MapName[ String1, Int1, Item1]);
  }
 }
}

Heh I just think of a map as a "map" as in literally a "map"... like assuming we have a "Map[ 5, 10] = $location[Dire warren];" I like to think at the coordinates "5 10" we have the dire warren. (Like as if they were x-y coordinates)
 

izchak

Member
Well, for the interested, this is what this chunk 'o' code eventually transformed into:
Code:
# this tries to use the still, improving whatever you have that can be improved
# however, it will only improve a single item, basically.
# I coded this the way I did on the assumption that one would have multiples of ten
# of several base drinks/mixers, eg. 10 lemons, 30 olives, 10 wines, etc.
boolean utilize_still(){
	if(my_class() != $class[disco bandit] && my_class() != $class[accordion thief]) {
		print("youre not a moxie class, and hence cannot use the still");
		return false;
	}
	if(stills_available() == 0) {
		print("you have no uses left at the still");
		return false;
	}
	item [item] spirits_n_mixers;
	spirits_n_mixers[ $item[grapefruit] ] = $item[tangerine];
	spirits_n_mixers[ $item[lemon] ] = $item[kiwi];
	spirits_n_mixers[ $item[olive] ] = $item[cocktail onion];
	spirits_n_mixers[ $item[orange] ] = $item[kumquat];
	spirits_n_mixers[ $item[soda water] ] = $item[tonic water];
	spirits_n_mixers[ $item[strawberry] ] = $item[raspberry];
	spirits_n_mixers[ $item[bottle of gin] ] = $item[bottle of Calcutta Emerald];
	spirits_n_mixers[ $item[bottle of rum] ] = $item[bottle of Lieutenant Freeman];
	spirits_n_mixers[ $item[bottle of tequila] ] = $item[bottle of Jorge Sinsonte];
	spirits_n_mixers[ $item[bottle of vodka] ] = $item[bottle of Definit];
	spirits_n_mixers[ $item[bottle of whiskey] ] = $item[bottle of Domesticated Turkey];
	spirits_n_mixers[ $item[boxed wine] ] = $item[boxed champagne];

	print("checking spirits, mixers, and crosby nash's still...");
	foreach key in spirits_n_mixers {
		if( item_amount( key ) > 0 && stills_available() > 0 && item_amount(key) >= stills_available() ) {
			print( "hey, you have " + item_amount(key) + " " + key );
			print( "you have " + stills_available() + " uses left at the still");
			print( "making " + stills_available() + " " + spirits_n_mixers[key]);
			create(stills_available(), spirits_n_mixers[key]);
			return true;
		}
	}
	print("you must have had no usable spirits or mixers, or not enough of what you did have");
	return false;
}

I'm not entirely sure if this warrants a new post in the 'writings in progress', or 'turn-burning' forums, since this forms part of a larger script I am overhauling for modern castlefarming. When I feel that large script is flexible and stable (and tested) enough for more general use, I'll post it up on one of those forums!
Until then, I just felt like throwing this here, seeing as I mentioned it here...

My gratitude is extended to Veracity and Nightmist for their rapid responses!
 

holatuwol

Developer
I suspect that half of the reason I have problems with ASH maps is that I keep thinking of them as a list, but a map is an entirely different structure...

Yeah. You can do list-like operations with them, but in the end, they're maps. There is a flag in the source code to enable the "array" data structure, but one of the reasons I asked Veracity not to enable it is that KoLmafia doesn't support the main thing people would want out of a list -- a way to initialize a bunch of elements at once.

The implementation of file_to_map technically hasn't changed this, though it does make things a lot easier to read for people who like to initialize large blocks of data at once. Though, KoLmafia's limitations theoretically wouldn't stop anyone from writing their own initializers, provided they know what they're doing and they don't mind not having type validation at 'compile' time.


PHP:
void push( boolean [item] list, string data )
{
    string [int] items = data.split_string( "\\s*,\\s*" );

    foreach key in items
        list[ string_to_item( items[key] ) ] = true;
}

boolean [item] supergarnishes;
supergarnishes.push( "grapefruit, lemon, olive, orange, soda water, strawberry" );

foreach key in supergarnishes
{
    if ( key.item_amount() > 0 )
        print( "hey, you have some " + key );
}


Since you're versed in PERL, taking that and extending it to build a full blown handler for parsing out { 'yo' => 'hi' } type stuff isn't difficult, but it does seem like a lot of work. :) It's a lot easier just to continue thinking of everything as a map, since as Veracity has shown, it results in much more elegant solutions.
 

Metraxis

Member
Similarly, if you had some reason you really preferred strings, you could do something like this:

Code:
boolean add(boolean [item] container, string candidate) {
  return container[string_to_item(candidate)] = true;
}

boolean [item] supergarnishes;
supergarnishes.add("grapefruit");
...etc...
 

QSquared

New member
okay sorry this is the place that I really shoudl be replying to, even though the other item is about the CSNY skill

I've taken this script, and I'm trying to do a bouble-sort of the items once they are loaded by the value of how many of each item we have.

I plan to do this so that I can work my way through my stockpile of SHCC items and turn them into SuperGarnishes/SuperLiqours as part of my breakfasting script.

However I'm having some trouble making it work, I thought i had the syn tax right, am I missing something, the result of this script's output is nothign at all, which my guess means that KOLMafia is terminating silently somewhere int he script.

Any help would be a plus!

# this tries to use the still, improving whatever you have that can be improved
# however, it will only improve a single item, basically.
# I coded this the way I did on the assumption that one would have multiples of ten
# of several base drinks/mixers, eg. 10 lemons, 30 olives, 10 wines, etc.


if(my_class() != $class[disco bandit] && my_class() != $class[accordion thief]) {
print("youre not a moxie class, and hence cannot use the still");
return false;
}
if(stills_available() == 0) {
print("you have no uses left at the still");
return false;
}
item [item] spirits_n_mixers;
spirits_n_mixers[ $item[grapefruit] ] = $item[tangerine];
spirits_n_mixers[ $item[lemon] ] = $item[kiwi];
spirits_n_mixers[ $item[olive] ] = $item[cocktail onion];
spirits_n_mixers[ $item[orange] ] = $item[kumquat];
spirits_n_mixers[ $item[soda water] ] = $item[tonic water];
spirits_n_mixers[ $item[strawberry] ] = $item[raspberry];
spirits_n_mixers[ $item[bottle of gin] ] = $item[bottle of Calcutta Emerald];
spirits_n_mixers[ $item[bottle of rum] ] = $item[bottle of Lieutenant Freeman];
spirits_n_mixers[ $item[bottle of tequila] ] = $item[bottle of Jorge Sinsonte];
spirits_n_mixers[ $item[bottle of vodka] ] = $item[bottle of Definit];
spirits_n_mixers[ $item[bottle of whiskey] ] = $item[bottle of Domesticated Turkey];
spirits_n_mixers[ $item[boxed wine] ] = $item[boxed champagne];


for i from 0 upto 11 [ by 1 ]{
ihold=i+1;
for j from 11 downto ihold [ by 1 ]{
jhold=j+1;
iggypop=j-i;
if item_amount( j ) < item_amount( iggypop ){
tempy=spirits_n_mixers(j);
spirits_n_mixers(j)=spirits_n_mixers(iggypop);
spirits_n_mixers(iggypop)=tempy;
}

}
}


print("checking spirits, mixers, and crosby nash's still...");
foreach key in spirits_n_mixers {
if( item_amount( key ) > 0 && stills_available() > 0 && item_amount(key) >= stills_available() ) {
print( "hey, you have " + item_amount(key) + " " + key );
print( "you have " + stills_available() + " uses left at the still");
print( "making " + stills_available() + " " + spirits_n_mixers[key]);

return true;
}
}
print("you must have had no usable spirits or mixers, or not enough of what you did have");
return false;
 

macman104

Member
The "validate <scriptname>" is a great tool. As it will spit out the errors for you. Also, I can't seem to replicate the script failing silently, as I get everytime "Script parsing error (Test.ash, line x)" where x is the line starting the for loop.

Also, like I said in the other thread, the code that is
Code:
item_amount(j)
or other various integers is no good, because you're asking for the item_amount of an integer. You are trying to determine your item amount for the nonSHCC items, correct? So things like the grapefruit, lemon and such, right? I'm trying to think of a good solution to this, and the only way I can think of is to create a second map indexing the non SHCC items from an int, so you'd have
Code:
item [int] nonSHCCs;
nonSHCCs[0] = $item[grapefruit];
nonSHCCs[1] = $item[lemon];
nonSHCCs[2] = $item[olive];
nonSHCCs[3] = $item[orange];
nonSHCCs[4] = $item[soda water];
nonSHCCs[5] = $item[strawberry];
nonSHCCs[6] = $item[bottle of gin];
nonSHCCs[7] = $item[bottle of rum];
nonSHCCs[8] = $item[bottle of tequila];
nonSHCCs[9] = $item[bottle of vodka];
nonSHCCs[10] = $item[bottle of whiskey];
nonSHCCs[11] = $item[boxed wine];

Here's *my version* of the loop, with the new map. However, I still get a "script parsing" error
Code:
item [item] spirits_n_mixers;
spirits_n_mixers[ $item[grapefruit] ] = $item[tangerine];
spirits_n_mixers[ $item[lemon] ] = $item[kiwi];
spirits_n_mixers[ $item[olive] ] = $item[cocktail onion];
spirits_n_mixers[ $item[orange] ] = $item[kumquat];
spirits_n_mixers[ $item[soda water] ] = $item[tonic water];
spirits_n_mixers[ $item[strawberry] ] = $item[raspberry];
spirits_n_mixers[ $item[bottle of gin] ] = $item[bottle of Calcutta Emerald];
spirits_n_mixers[ $item[bottle of rum] ] = $item[bottle of Lieutenant Freeman];
spirits_n_mixers[ $item[bottle of tequila] ] = $item[bottle of Jorge Sinsonte];
spirits_n_mixers[ $item[bottle of vodka] ] = $item[bottle of Definit];
spirits_n_mixers[ $item[bottle of whiskey] ] = $item[bottle of Domesticated Turkey];
spirits_n_mixers[ $item[boxed wine] ] = $item[boxed champagne];

item [int] nonSHCCs;
nonSHCCs[0] = $item[grapefruit];
nonSHCCs[1] = $item[lemon];
nonSHCCs[2] = $item[olive];
nonSHCCs[3] = $item[orange];
nonSHCCs[4] = $item[soda water];
nonSHCCs[5] = $item[strawberry];
nonSHCCs[6] = $item[bottle of gin];
nonSHCCs[7] = $item[bottle of rum];
nonSHCCs[8] = $item[bottle of tequila];
nonSHCCs[9] = $item[bottle of vodka];
nonSHCCs[10] = $item[bottle of whiskey];
nonSHCCs[11] = $item[boxed wine];

int loopLength = nonSHCCs.count();

for i from (loopLength - 1) downto 0 [ by 1 ]
{
	for j from 0 upto i [ by 1 ]
	{
		item item1 = nonSHCCs[i];
		item item2 = nonSHCCs[j];
		if(item1.item_amount() > item2.item_amount())
		{
			item tempItem = spirits_n_mixers(item1);
			spirits_n_mixers(item1) = spirits_n_mixers(item2);
			spirits_n_mixers(item2) = tempItem;
		}
  
	}
}

EDIT: K, scratch that, still doesn't work, almost there though.
 

holatuwol

Developer
Remove the braces around [by 1]. The braces in the documentation indicate that the 'by' part is optional, not that you have to add braces around it. ... Yeah, it's not easy to show what's optional and what's not. Maybe resort to color...
 

macman104

Member
[quote author=holatuwol link=topic=779.msg5107#msg5107 date=1183846730]
Remove the braces around [by 1]. The braces in the documentation indicate that the 'by' part is optional, not that you have to add braces around it. ... Yeah, it's not easy to show what's optional and what's not. Maybe resort to color...[/quote]Maybe no brackets, and I assume the default behavior is a step of 1? If that's true, you could show an example with "by 2" (no quotes), and just say that excluding the part with the "by" option makes the loop default to the standard behavior of stepping by 1 (if that is the case). Like the attached file maybe?
 

Attachments

  • ForLoops.html
    609 bytes · Views: 57

macman104

Member
Ok! I finally got this thing working. The thing that was throwing me off was because the statement
Code:
for i from 11 downto 0 by 1
{
	for j from 0 upto i by 1
	{}
}
is the same as
Code:
for(int i = 11; i >= 0; i--)
{
	for(int j = 0; j <= i; j++)
	{}
}
when I was really trying to accomplish was
Code:
for(int i = 11; i > 0; i--)
{
	for(int j = 0; j < i; j++)
	{}
}
So I had to change it to
Code:
for i from 11 downto 1 by 1
{
	for j from 0 upto (i - 1) by 1
	{}
}
So...yea, anyway, new code posted here. To see the script work, run it as is. I created a 3rd map with fake item amount values to test out the script. I also up'ed a second version that simply functions as the script (no fake values or printing)
 

Attachments

  • DemonstrateSortGarnishes.ash
    2.2 KB · Views: 45
  • SortGarnishes.ash
    1.6 KB · Views: 46

QSquared

New member
[quote author=holatuwol link=topic=779.msg5107#msg5107 date=1183846730]
Remove the braces around [by 1]. The braces in the documentation indicate that the 'by' part is optional, not that you have to add braces around it. ... Yeah, it's not easy to show what's optional and what's not. Maybe resort to color...
[/quote]

I vote Colors, or at least Bold & italics OR a note that says braces = optional sections thats a bit more prominent (is there on I didn't check honestly)

~Q
 

Veracity

Developer
Staff member
[quote author=macman104 link=topic=779.msg5111#msg5111 date=1183850984]
Ok!  I finally got this thing working.  The thing that was throwing me off was because the statement
Code:
for i from 11 downto 0 by 1
{
	for j from 0 upto i by 1
	{}
}
is the same as
Code:
for(int i = 11; i >= 0; i--)
{
	for(int j = 0; j <= i; j++)
	{}
}
[/quote]

What would think of an additional keyword for "for":

for j from 0 to i by 1

and

for j from i to 0 by 1

with "by 1" being the default in either case. Those would be like either "upto" or "downto" depending on whether the start was less than the end or vice versa, as evaluated at runtime. And yes, the end point would continue to be inclusive; the iteration would not stop until the iteration variable increments PAST the end point...

I've been considering this for a while...
 

QSquared

New member
[quote author=Veracity link=topic=779.msg5113#msg5113 date=1183864979]


What would think of an additional keyword for "for":

for j from 0 to i by 1

and

for j from i to 0 by 1

with "by 1" being the default in either case. Those would be like either "upto" or "downto" depending on whether the start was less than the end or vice versa, as evaluated at runtime. And yes, the end point would continue to be inclusive; the iteration would not stop until the iteration variable increments PAST the end point...

I've been considering this for a while...
[/quote]


I would MUCH prefer this, as this is how C/VB/VBScript/JAVA/JAVAScript all do it, less confusion in my mind to be more similar.
 
Top