Can I sort an array? Can I call a method if given a reference?

Sandiman

Member
Couple of questions here, I'm hoping someone will know the answer to one or both.

1. Is there built-in functionality to sort an ASH array?

2. Is there a way to call a method if given a reference to the method? The only reference I can think of in ASH is the method's name. The code I'm looking for would (in theory) work something like this:
Code:
string method = "print";
call_function( method, "foo" );

The above code would then print "foo". Is there any functionality resembling this?
 

zarqon

Well-known member
1. Don't think so. If you write a function that does we will all worship you though.

2. You can come close by using cli_execute().
 

Sandiman

Member
After thinking about this for a while, I've come to the conclusion that ASH "arrays" are really hash tables -- a "foreach" loop is going to access items as if they were inherently sorted, based on the value of the first key in the map on which we're looping. As such, the only way to sort them according to different criteria would be to add a new value as the initial key. If I were to try to create some sort of "sortable array," it'd have to be a custom record structure, which doesn't seem as useful to me.

As an example, assume I have the following array:
Code:
string[string] my_array;
my_array["foo"] = "bar";
my_array["cat"] = "dog";

Looping through my_array with a foreach loop is always going to process 'cat' before 'foo'. If I wanted 'foo' to come first, I'd need to do something like:
Code:
string[int, string] my_array;
my_array[0, "foo"] = "bar";
my_array[1, "cat"] = "dog";

Like I said, that seems less useful to me -- any abstraction I could make seems like it would be more complicated than just manually reworking the arrays. If people think it'd still be useful, I might take a look next week if I'm bored. Any thoughts?
 

Veracity

Developer
Staff member
ASH maps are, in fact, implemented with Java TreeMaps, which are sorted according to the key.

ASH does allow real Java arrays internally. A single variable in the Parser.java controls whether they are available to scripts. By default, they are not.
Code:
	private static final boolean arrays = false;

If I enable that, the following syntax is available:

Code:
item [10] my_array

declares an array of items with 10 elements, indexed from 0 to 9. Compare to

Code:
item [int] my_array

which declares a map of items indexed by integers. The syntax for accessing elements in either is identical. However, the array will generate a runtime error if you give a bad index, unlike the map, which allows any index.

I created maps and arrays for ASH at the same time; they look remarkably similar internally. holatuwol did not want me to publicly release arrays, though, since he felt that they would be harder to document than maps, given the runtime errors. So, I compromised by leaving in my code to handle arrays, but disabling it via that parser variable.
 

Sandiman

Member
Makes perfect sense. It's always nice to know things were done on purpose -- I get that feeling a lot with KoLmafia, which is probably why I enjoy it so much.

Thanks for all the work, it's a great program.
 

Veracity

Developer
Staff member
You know, I might release arrays. There's enough else which is not documented in ASH that I'm not sure waiting for documentation is worth it.

I actually had a bug in checking upper bounds - using < rather than <= - but when I fixed it, the following little script:

Code:
int [10] my_array;

foreach index in my_array
  my_array[index] = 10 - index;

foreach index in my_array
  print( "my_array[" + index + "] = " + my_array[index] );

print( "" );
print( "my_array[6] = " + my_array[6] );
print( "my_array[10] = " + my_array[10] );

generates

> arrays.ash

my_array[0] = 10
my_array[1] = 9
my_array[2] = 8
my_array[3] = 7
my_array[4] = 6
my_array[5] = 5
my_array[6] = 4
my_array[7] = 3
my_array[8] = 2
my_array[9] = 1

my_array[6] = 4
Array index out of bounds

runtime errors are messy in ASH, since we currently have no way to associate them with the line of the script that caused them.

Regarding sorting:

it is true that the indices of ASH maps are sorted, in that foreach will iterate through them in natural order, but nothing stops you from sorting the values any way you want. So, I am confused by your comment that given:

Code:
string[string] my_array;
my_array["foo"] = "bar";
my_array["cat"] = "dog";

Looping through my_array with a foreach loop is always going to process 'cat' before 'foo'. If I wanted 'foo' to come first ...

Well, yes - but you are treating the map index as part of the data. That's weird. If you really want that, declare a record and make a map of those records indexed by integer.

Code:
record {
  string key;
  string value;
} [int] map;

map[0].key = "foo";
map[0].value = "bar";
map[1].key = "cat";
map[1].value = "dog";

... and foreach will give you {"foo","bar"} before it gives you {"cat","dog"}.

And you can change the sorting by exchanging map[0] with map[1].
 

Sandiman

Member
Well, yes - but you are treating the map index as part of the data. That's weird. If you really want that, declare a record and make a map of those records indexed by integer.

Exactly. In my example, I was trying to use maps as I've seen them in the tutorials, in case people looking for more info stumbled across this thread. I've actually used the sort-by-creating-records method in some scripts I've written.

I can understand the hesitance in opening up ASH to runtime errors. It's hard to un-ring the bell, so to speak. That being said, I think it's safe to say that an array bound error will likely happen in one of two cases:
1. Looping on the array
2. Using an improperly-set (or undefined value) to access the array.

Item 1 wouldn't be that hard to track down, as it's probably the first place people look when debugging. Array out of bounds? Probably a bad "=" sign in your loop condition somewhere. I'd place the difficulty of fixing this problem well below tracking down most ASH runtime errors.

Item 2 is, unfortunately, ridiculously hard to track, especially if the problem is not easily reproducible (e.g., occurs only when fighting a specific monster). However, as you say, ASH scripts do not currently report script lines for any runtime errors; as such, I don't see this as more problematic than other runtime errors.

Really, it comes down to whether your experience tells you whether releasing arrays will cause more harm or good. I'm a programmer myself, but I don't have your experience in creating language -- I'm an application developer by trade, which is a few layers of abstraction away from what we're talking about. Still, if you want to bounce ideas around, I'd be happy to play sounding board.
 

Sandiman

Member
Sorry to resurrect, but I'm writing some new scripts and would very much like a concrete answer to my #2 question from above:

2. Is there a way to call a method if given a reference to the method? The only reference I can think of in ASH is the method's name. The code I'm looking for would (in theory) work something like this:
Code:
string method = "print";
call_function( method, "foo" );
The above code would then print "foo". Is there any functionality resembling this?

zarqon pointed out that cli_execute() will do exactly that. However, I'm only aware of how to use cli_execute() to call built-in cli functions (as opposed to user-created functions in scripts), and the end goal of this is to call functions that I myself have written.

Does anyone know either a) how to bend cli_execute to my warped will or b) whether this is impossible with current mafia functionality?
 

zarqon

Well-known member
Use the CLI "call" command.

Example:

printsomething.ash
Code:
void main(string something) {
  print(something);
}

otherscript.ash
Code:
void main() {
  string foo = "I want to print this."
  cli_execute("call printsomething.ash "+foo);
}

Running otherscript.ash will print "I want to print this." (I think -- not sure about how the spaces are handled when passing arguments in the CLI.)

It's very circular, and every function you wanted to call would have to have its own ASH script, but it will allow your warped will to have its way.
 

Sandiman

Member
Two things:

1. I was under the impression that it was a bad idea to call one ASH script from within another. Does wrapping the call in a cli_execute make this "better"?

2. The cli_execute solution has some inherent problems of its own. To my knowledge, cli_execute() has no return value, and the "call" command can only take one argument. I can see that being used to make stateless conversions on referenced data sources (maps, etc.), but I don't see it having a wide use outside of that.

I'm getting the feeling that the answer to this is "stop trying to break things; the language works fine as it is -- you're just being difficult." ;)
 

Veracity

Developer
Staff member
I think one issue is that your "call_function" idea requires a fair bit of coding within ASH, and nobody in a position to do so has volunteered to put it in. I'm not opposed, in principle, but it's not trivial.

It's like (apply #'function args...) in Lisp, or invokeMethod in Java.

Both of those take variable numbers of arguments.
Both to some amount of arg checking, in order to find a function/method which can take the specified arguments. Lisp doesn't check data types, Java does.

In ASH, it would be, essentially, a run-time call on the same mechanism used at compile time to find a function given a set of arguments of known types. If it doesn't find it, it'd throw a runtime error - which, as I mentioned with arrays, is less desirable, since we don't have file/line data available to help you find where it happened.

If it does find a matching function, it's not too hard to call it with the specified arguments.

Add in choosing a correct syntax for specifying this - perhaps making it look like a pseudo-function with variable number/type of arguments, as you suggest, is good, or perhaps there is a better syntax - and this becomes a project, rather than just a new little feature.

That's a lengthy answer to your question.

Executive summary: no, there is currently no way to do this in ASH, although it's an interesting problem and I might do it.

Perhaps you could motivate me by telling me what you need it for?
 

zarqon

Well-known member
Look at the recent incarnations of the bounty-hunting script -- Raorn tweaked it to it take a single string parameter, which it is designed to break down into multiple possibilities. I regularly call this script from within my "daily routine" scripts using the CLI call command.

If you really need a return value, you could have the called script set a property (or save a map) which is read by the calling script.

The CLI "call" solution is quite ugly, but it works.
 

Sandiman

Member
@zarqon
Those are excellent points. I see now that the call solution is indeed viable, and I may end up using it. Thanks!

@Veracity
Code:
It's like (apply #'function args...) in Lisp, or invokeMethod in Java.
You're exactly right. I actually learned to program in Scheme, so I tend to yearn for the power of Lisp languages. ;) I'm not so good at motivation, but I'll tell you what I'm trying to use it for at the moment and see how that strikes you.

I'm updating my script that's meant to maximize the output of your consumables items (food and booze). Currently it just maximizes adventures, but I would like to expand the functionality to give priority to things like stats, and to allow secondary/tertiary/etc. sorting priorities (i.e., sorting first by adventures, then by main stat gain). Here's the almost-code for what I'm thinking of doing:

Code:
record item_stats
{
  int adventures;
  int muscle;
  int myst;
  int moxie;
  int fullness;
}

// sorts[0] has highest priority, while sorts[count(sorts) - 1] has lowest.
// All strings stored in 'sorts' must be functions that take a single item and return an integer.
string[int] sorts;
sorts[0] = "get_adventures";
sorts[1] = "get_mainstat";
sorts[2] = "get_fullness";

// This is the sorted version of all_consumable_items
item[int] results;

// Looping on all consumable items
foreach current_item in all_consumable_items
{
  // Attempting to find the index where we should insert current_item within results
  int results_index = 0;
  int results_count = count(results);
  while (results_index < results_count)
  {
   // Determining if current_item has higher priority than the item currently stored in results.
   int sorts_index = 0;
   int sorts_count = count(sorts);
   while (sorts_index < sorts_count)
   {
     int stored = call_function(sorts[sorts_index], results[results_index]);
     int current = call_function(sorts[sorts_index], current_item);

     // Halt if we've found the correct spot
     if (current > stored)
      break;

     // Otherwise, update the sorts index and continue
     sorts_index = sorts_index + 1;
   }

   // If sorts_index is less than sorts_count, we broke from the inner loop.
   // If that is the case, then we should be inserting the item at the current results_index.
   if (sorts_index < sorts_count)

   // Otherwise, increment the counter and continue
   results_index = results_index + 1;
  }

  // Now results_index is the correct value at which we should insert our item
  results[results_index] = current_item;
}

Clearly the above code hasn't been tested, so hopefully you can see where I'm going even if I failed to implement the algorithm. I know that a simple 'get' method isn't exactly earth-shattering, but the functionality would allow people to do some really cool things. At the same time, I fully realize this isn't a trivial task if the functionality is not already in the system, so a simple "no, stop being greedy and write code like a normal person" will suffice just fine. :) I've gotten my answer for 'does this functionality exist in the system' has been answered. Thanks.
 

dj_d

Member
Sandiman - I'm a day or two from posting a script that does something quite similar, inspired by your sorting script. And let me chime in... I really wish it were easier to sort maps!
 

Bale

Minion
[quote author=dj_d link=topic=1856.msg10054#msg10054 date=1227979804]
Sandiman - I'm a day or two from posting a script that does something quite similar, inspired by your sorting script. And let me chime in... I really wish it were easier to sort maps!
[/quote]

In my recovery script I'm sorting a map. Actually I use two maps which contain all the data split between them. One is item [int] map1 and it is a simple ordinal list of the items that I am sorting. The other is int [item] map2 and it contains the value of each item. I then sort map1 based on the value of map2. (ie, if map2[ map1[key] ] > map2[ map1[key+1] ], then swap map1[key] and map1[key+1] ). That worked really well for me. I suppose it even has the benefit that maps are passed by reference.
 

Veracity

Developer
Staff member
OK, I just put in an experimental feture to let you invoke a function whose name is known at runtime.

call [type] <identifier>( param, ... );

[type] is optional. If you omit it, the expression is of type void, and the function's return value can't be used.

Here's a little script which uses this feature:

Code:
int foo( int a, int b )
{
  return a + b;
}

String a = "foo";

float c = call float a( 2, 3 );

print( "c = " + c );

a = "print";

call a( "print 1" );

print( "done" );

gives this:

> test/invoke.ash

c = 5.0
print 1
done

I'm open to suggestions, but I'll preemptively point out that I don't see how we can avoid explicitly specifying the expected return type of the function without generating data type errors at runtime, potentially, which is a huge can of worms.
 

dj_d

Member
I'm using this in a script now, and it's a HUGE simplifier. Much thanks!

(now if I could just get you to implement +=... :) )
 

mredge73

Member
OK, I just put in an experimental feture to let you invoke a function whose name is known at runtime.

call [type] <identifier>( param, ... );

[type] is optional. If you omit it, the expression is of type void, and the function's return value can't be used.

Here's a little script which uses this feature:

Code:
int foo( int a, int b )
{
  return a + b;
}

String a = "foo";

float c = call float a( 2, 3 );

print( "c = " + c );

a = "print";

call a( "print 1" );

print( "done" );

gives this:



I'm open to suggestions, but I'll preemptively point out that I don't see how we can avoid explicitly specifying the expected return type of the function without generating data type errors at runtime, potentially, which is a huge can of worms.

I just followed this link from another thread and was wondering how this worked exactly:

Code:
> ash string a="my_name"; call a();

Calling "my_name", which returns string but void expected ()
Returned: void

Is this only limited to void functions?
 
Top