Is there a complement to string_split()? e.g. implode()

zarqon

Well-known member
I'm working on a script that handles comma-delimited lists, and I've been searching ASH for a string-handling function that will assemble a string[int] such as what results from split_string() into a string. Basically, the equivalent of PHP's implode().

PHP:
string[int] animals
animals[0] => "lions"
animals[1] => "tigers"
animals[2] => "bears"
animals[3] => "oh my!"

Then, using my imaginary desired function
string implode(string[int] source, string glue)

PHP:
string animalstring = implode(animals,", ");

animalstring => "lions, tigers, bears, oh my!"

Using ashref didn't show me anything that seemed to do this, but perhaps I didn't search correctly.

Anyone know of a solution?

And while I'm at it, anyone know of a way to delete one index => value pair from a map? (besides copying everything except that one pair over to a new map).
 

Sandiman

Member
Hopefully this will help:

Code:
string implode(string[int] source, string glue)
{
	string result = "";
	int c = count(source);
	int i = 0;

	// Compiling most of the data...
	while (i < c - 1)
	{
		result = result + source[i] + glue;
		i = i + 1;
	}

	// Compiling final
	result = result + source[i];
	return result;
}

void main()
{
	string[int] animals;
	animals[0] = "lions";
	animals[1] = "tigers";
	animals[2] = "bears";
	animals[3] = "oh my!";
	
	string animalstring = implode(animals,", ");
	
	print( animalstring );
}

Running that script prints:
Code:
lions, tigers, bears, oh my!

The deletion of an index is done with `remove <aggregate reference>` (it's in the ASH maps tutorial here: http://kolmafia.sourceforge.net/advanced.html#maps).
 

zarqon

Well-known member
@Sand: You are too kind.

OK, rather than start a new thread, I'll see if anyone knows here. Is there already existing functionality in ASH to pass a variable by reference?

Not by reference:

Code:
string add_five_zs(string source) {
  return source+"zzzzz";
}

string needszs = "abc";
needszs = add_five_zs(needszs);
print(needszs);      // prints: abczzzzz

By reference:

Code:
void add_five_zs(string &target) {
  target = target + "zzzzz";
}

string needszs = "abc";
add_five_zs(needszs);
print(needszs);      // prints: abczzzzz

Those two would do the same thing, in a world where ASH supports passing by reference.

I have a few situations where it would be super handy.
 

jasonharper

Developer
Zargon: there is no passing by reference in ASH. A couple of possibilities (that may or may not apply, depending on how close your examples were to your actual need):

If your variables were buffers instead of strings, you could modify them in place with append() or insert():
Code:
void add_five_as(buffer source) {
  source.append("aaaaa");
}

If your values were in a map, instead of separate variables, you could just pass in a key:
Code:
void add_five_as(string[string] map, string key) {
  map[key] = map[key] + "aaaaa";
}
 

zarqon

Well-known member
I'd be looking to alter a map by passing the map. Correct me if I'm wrong, but your second example, if I passed it a map in the first variable, would not alter the map itself, just the function's copy of that map, right? Or is there special handling for maps?
 

Alhifar

Member
I just tested it with that script to add 5 A's there. It worked just fine.

Code:
void add_five_as(string[string] map, string key) {
  map[key] = map[key] + "aaaaa";
}
void main()
{
	string[string] thing;
	thing["test"]="abc";
	print(thing["test"]);
	add_five_as(thing,"test");
	print(thing["test"]);
}

Returned:

Code:
abc
abcaaaaa
 

Veracity

Developer
Staff member
[quote author=zarqon link=topic=2030.msg10017#msg10017 date=1227806338]
I'd be looking to alter a map by passing the map. Correct me if I'm wrong, but your second example, if I passed it a map in the first variable, would not alter the map itself, just the function's copy of that map, right? Or is there special handling for maps?
[/quote]
Maps and records and arrays are already passed by reference.
 

jasonharper

Developer
[quote author=zarqon link=topic=2030.msg10017#msg10017 date=1227806338]
I'd be looking to alter a map by passing the map. Correct me if I'm wrong, but your second example, if I passed it a map in the first variable, would not alter the map itself, just the function's copy of that map, right? Or is there special handling for maps?
[/quote]
Passing a parameter never copies anything - both the caller and the function see the exact same object, and any changes the function makes to that object are visible to the caller. However, strings are immutable objects, no such changes are possible. I think the only mutable types in ASH are buffers, maps, and records.
 

zarqon

Well-known member
Oh, sweet. Totally didn't know that. How many scripts have I written now under the assumption that function parameters, like in other languages I'm familiar with, made copies of the passed variable...? Amazing.

@Alhifar: thanks for doing my homework for me. :)

As always, Veracity, Jason -- you rock.
 

zarqon

Well-known member
Inspired by Sandiman's implode solution, I rewrote it a bit to take advantage of maps being automatically sorted by key:

Code:
string implode(string[int] source, string glue) {
  string result = "";
  foreach key in source {
   result = result + source[key];
   if (source contains (key + 1)) result = result + glue;
  }
  return result;
}

This will not give the desired result for maps with non-sequential keys, but for anything that is a result of split_string(), it will do nicely.

Can someone in the know tell me which is the better solution from a speed or memory-use standpoint? I'm curious what goes on behind the scenes with for vs. foreach.
 

jasonharper

Developer
[quote author=zarqon link=topic=2030.msg10240#msg10240 date=1228547599]Can someone in the know tell me which is the better solution from a speed or memory-use standpoint? I'm curious what goes on behind the scenes with for vs. foreach.
[/quote]
This is about as efficient as it's going to get in ASH:
Code:
string join(string joiner, string[int] pieces) {
	buffer buf;
	boolean first = true;
	foreach index in pieces {
		if(!first) buf.append(joiner);
		first = false;
		buf.append(pieces[index]);
	}
	return buf.to_string();
}

print(join(", ", "lions/tigers/bears/oh my!".split_string("/")));
It uses a boolean flag rather than another map lookup to determine when not to use the glue, but the big win is using a buffer to accumulate the string fragments. Buffer.append() takes time proportional to the length of the fragment being added, which will remain roughly constant for each iteration of the loop. String concatenation, on the other hand, takes time proportional to both strings being added, so it will get slower and slower as the result gets longer.
 

zarqon

Well-known member
Continuing my thread of random ASH questions...

Could ASH support this syntax:

variable = (condition) ? valueiftrue : valueiffalse;

This would have many useful applications. One example:

Code:
boolean arenaisfrat = true;

item flyer = arenaisfrat ? $item[rock band flyers] : $item[jam band flyers];

I ask because I recently noticed that Java supports this. I've found it quite handy in PHP and would love to see it in ASH.
 
Top