I have a list called missing-letters. I want to have a part where I count the number of entries of a particular string (eg. “X”) in missing-letters, store the number in a variable, then get rid of all the instances of it. However, list size gets wonky once you start removing the items one at a time, and counting them also becomes a problem. Is there an easy way to do this?
Does it need to be a list?
missing-letters is initially "xyzzy".
when play begins:
let string be the substituted form of missing-letters;
let original-count be the number of characters in string;
replace the regular expression "z" in string with "";
let new-count be the number of characters in string;
now missing-letters is string;
say "[missing-letters] [original-count - new-count].";
I can try this - let me see if it will work…
What does this even mean?
I would guess the OP is referring to altering the list’s size while running through it. The docs warn users not to do this in WI §21.4 Testing and iterating over lists:
But it’s important never to change a list that’s being repeated through. The following:
let L1 be {1, 2, 3, 4}; repeat with n running through L1: remove n from L1;leaves L1 containing {2, 4}, since the removals from the list cause it to shuffle back even while we repeat through it - a bad, bad idea.
I would just make a second list.
let the new list be a list of texts;
let the X count be zero;
repeat with the character running through missing-letters:
if the character is "X":
increment the X count;
otherwise:
add the character to the new list;
now missing-letters is the new list.
This sounds like a bug in the library to me.
Of course, if repeat with n running through L1 is implemented along the lines of for(i = 0; i < L1.length; i++), then removing an element while repeating through it will, naively, cause you to skip elements. But that’s something that’s trivially solved by just being aware of it and readjusting your index after you remove an element.
Given that Inform aims for natural-language programming, I’d argue that it should be aware of the possibility of removing while iterating and should readjust the index if that happens.
There is no way to catch all cases like this. Any function called in the loop could remove an element anywhere in the list; the loop code has no way to know it or adjust the index appropriately.
The best it could do is set a “modified” flag on the list, and then display a runtime error after the fact.
Yeah, the problem is unfortunately inherent to the fact that lists are mutable in Inform. You could in theory clear out the entire list halfway through the search, then populate it with entirely new values, and there’s no sensible way for the library to cope with that.
Currently, Inform guarantees that it’s safe to append new elements to the end during iteration…and nothing else. It would be nice if it could also handle removal, but beyond that, there be dragons.
I can certainly agree that it’s not an easy task, but I would stop short of saying there’s “no way” to do it.
Setting a “modified” flag gets you partway there, but it doesn’t really help the issue, and punishes people who do it intentionally while really knowing the consequences.
I think setting an “iterating” flag on the list gets you closer. Then removing or adding an element can behave differently if it knows something’s already iterating over it. Actually, perhaps the current index variable could itself be stored as a hidden property associated with the list, which remove and add could then update. It sounds a bit silly, and they’d build up if you have deeply-nested iteration, but… I think it would work in all cases.
It’s slightly more complicated with immutable lists, as that means “remove 5 from L” when L = “{1,2,5,4,3}” is completely equivalent to “now L is {1,2,4,3}”. But, I don’t think it’s that much more complicated. Mostly it means the iterating index needs to be associated with the “list variable” rather than with the “list value”?
This is a neat idea, and one which I hadn’t come across. It adds a ton of mechanism, to be sure. Essentially a loop (or its presence in the array) is now an allocated object which has to be tracked and cleaned up. (Extra work if you return from the middle of the loop, etc.)
The tradeoff does not look tempting to me, is all I can say.
I am also not convinced by your original idea that “natural-language programming” calls for this sort of support. (As distinct from other languages, which seem to have settled on the run-time error as best common practice.) Loops is loops.
Another option would be caching the list when the loop begins, so no matter what happens to it, the loop always iterates over the elements exactly as they appeared when it started. This adds some overhead to every loop over a list, but I7 lists already have a lot of overhead; it’s not like loops through them are blazingly fast as it is.
I think that’d be less intuitive to users, but sure, it does solve the problem.