Just making sure I'm thinking about things *wrong*... (list and list references in code)

In my ever-onward approach to misunderstanding Tinderbox, as I try to mangle code to map Timeline view layouts onto Map view layouts, I’m writing quite a chunk of Action code, which may or may not be a terrible idea.

One issue I’m having is lists, specifically, list items.

I’m making a list like this:

myList:list=[]

and wanting to add items by simply stating

myList[1]=0;

And wanting to do things like myList[itemNumber]=myList[itemNumber]+1;

But I don’t think Tinderbox approaches lists the way I’d naïvley assumed/hoped it would.

When I try to manipulate items using the above-style lookups, the list doesn’t seem to change. Am I right in thinking this is wrong, and I need to use a different approach? (and if that’s the case, can I beg that timeline-band subbands be exposed as a property that lazy/hacky users can access to save themselves a world of pain?)

Hi Dave,

some remarks that may help:
lists store(d) strings only! But this seems to have changed :wink:
(acrobatfaq is wrong with “Values must be enclosed in double quotes.”)

so:

var:list justAList = [];
justAList = justAList + 23;

works fine.

Then: the first item in a list has the index 0 and not 1!

justAList[0] = 12;
justAList[0] = justAList[0] + 1;

both work.

The first element in a list is element 0:

$MyList=[];
$MyList[0]="able";     ➛     able

But more commonly, you add items to the end of a list:

$MyList=$MyList+"baker";

Just about everything in Tinderbox can be turned into a string, and back into itself as needed. So you can have lists of numbers, lists of dates, even lists of lists. Very occasionally, you must use quotation marks when you want to tell Tinderbox that something really is a string:

$MyList = $MyList+2+2;    //  Appends "4" to the list
$MyList = $MyList+"2+2"; //  Appends the string "2+2" to the list
$MyList = $MyList+MyNumber; //  Appends the value stored in the attribute $MyNumber to the list
$MyList = $MyList+"MyNumber"; //  Appends the string "MyNumber" to the list


I believe you’ll find that, whenever you find yourself needed to know this factoid, you’re probably off track. :slight_smile: There’s likely to be an easier way to do it.

Assuming, due to the lack of $-prefix that you are trying to make a new, List data type variable, the above does not work. The (optional) type declaration is colon-appended to the var operator, as documented:

var:list myList = [];

Your example omits the mandatory var operator so no variable is created. As you are applying a List data type type, the variable initialises as an empty list so the latter declaration is not needed (the default for untyped variables is an empty string. So, this is a shorter form of the above:

var:list myList;

Once declared a variable value can be assigned in either of 3 ways, here for variable myList:

myList(5);
myList = 5;

The first older form is not much used as people seem to find the latter more intuitive. If myList is already a List type, the above (re-)set the list’s contents to a single item list of value ‘5’. Within a list—i.e. List or Set type—individual single-value list items, so not nested list or dictionaries, are untyped so @webline’s observation about aTbRef is actually incorrect.

This is wrong in several ways. As has been stated above, Lists and Sets use zero-based referencding as documented:

The List[N] notation addresses any single List item using a zero-based address.

But using

myList[0]=0;

Doesn’t help as this sets item #0 to 0. So if the list value was 4;7 the above would change it to 0;7. For our original example using address #1, the same test would set the list to 4,0.

So, how to add a new first item? The approved syntax, uses the v9.5.2+ [] list declaration like so:

myList = [0] + myList;

Essentially we join two lists added the second not the first so for the example above the result is 0;4;7. The first list can ab a list of 1 or more items. The same mechanism is used to add items to to the end of a list:

myList = myList + [0];

As items inside lists have no data type, using the [] declaration for new list items avoids issues of type coercion mentioned in my footnote here.

But, what if you want to insert a new item (single item or list or dictionary) at a specific list address. AFAIK, there is no operator for that. You’ve need to read the parts of the existing list before/after the insertion point into variables and concatenate them with the new list values.

†. The description was correct as originally written though these days ‘must’ might be changed to ‘should’ . the issue surrounds the effects of type coercion when concatenating (+) differently typed inputs. Also the documentation is written for the general user so doesn’t default to a programmer’s PoV/assumptions (as most users aren’t programmers). Documenting ways coding-savvy users can see to work outside the guidelines (often just to save typing a few extra characters) would simple add confusion. IOW, there may be more than one way to do things but not all are documented—with deliberate good intent.

‡. In fact this is rendered moot by the new [] declarative form. I’ll update the docs.

1 Like

I’ve updated my article on List-Type Attributes to make more explicit use of the [] for declaring lists (List & Set types).

Thanks all - I picked a slightly terrible example that implied a number of misunderstandings on my part that aren’t the case.

var:list bandHeights; should be a list, stored in a local variable bandHeights.

If I want to find and increment, say, the 3rd element, (counting from 0, so index number 2) by 1,

bandHeights[2]=bandHeights[2]+1;

When I’m running this as part of a programme, I try to increment elements of the list:

notes = collect(descendants,$IDString);
notes.each(n){

		if(bandHeights[$TimelineBand(n)]=="") {
			bandHeights[$TimelineBand(n)]=1;
		} else (
			bandHeights[$TimelineBand(n)] = 				bandHeights[$TimelineBand(n)] + 1;
		}
		bandHeights[$TimelineBand(n)] = 			bandHeights[$TimelineBand(n)]+1;
		dolog("* bandHeights["+ $TimelineBand(n) + "]=" + 			bandHeights[$TimelineBand(n)];// + " for note "+$Name(n)
		);

but there seems to be a resistance to do what I mean. bandHeights[n] never increments.

To try to nail down a little further what may be going on - and with a constant reminder that lists are just strings with semicolon separators internally - I tried the following:

var:list tst;
dolog("tst="+tst);
tst[2]=5;
dolog("tst="+tst);
dolog("tst[0]="+tst[0]);
dolog("tst[1]="+tst[1]);
dolog("tst[2]="+tst[2]);
dolog("tst[3]="+tst[3]);

which I’d expect to create a list of items equivalent to [0;0;5].

Instead, it seems to create a list [5]: the debug output is

tst=
tst=5
tst[0]=5
tst[1]=
tst[2]=
tst[3]=

So there may be a few things going on here; one is that you can’t change a list elements unless items leading up to it already exist (eg, a list [1;2;3] could have item 2 changed, but item 3 just does not exist and cannot be changed, only appended).

I think using Action code to try to implement in-timeline staggering bands may be a little too much for it, me, or both.

I’ll nibble at the last bit first.

Tinderbox lists aren’t (multi-dimensional) array, but they do allow nested lists, if that makes sense. So TinderboxList != Array as used in things like JavaScript. But the visual similarity of code used can lead to innocent misassumptions.

The tst[2]=5 code doesn’t give the presumed output because you can’t write to a list item that doesn’t exist. You expected [0;0;5] but got [5]. The tstdidn't have an item #4 (indeed it had no items) so you gave an empty List a variable valid as value for list item and so the empty list added a new _first item_ of value5, i.e [5]. If you had started your test with the code tst=[0;0;0]`, your log would have looked like this

tst=0;0;0
tst=0;0;5
tst[0]=0
tst[1]=0
tst[2]=5
tst[3]=

The last being empty as the list holds only 3 items and tst[3] addresses a non-existent list item returns nothing and returns no error (Tinderbox action code gives silent failures in most cases - it’s not a deliberate coding languages but a much developed internal macro language). So assumed syntax failure rather that a bug (again not meant harshly).

Summary:

  • you can’t read set a list item that does not exist, i.e. you can’t auto size a list
  • code like var:list tst=[0;0;0] is as an effective way as any of defining a an ‘empty’ 3-item list. List values are not data-typed and are stored as strings. As it happens you can define and interrogate tst=["";"";""], but I think the above with zeroes seems better/safer and anyway your real use involves numbers.
    you can’t re-size a list by inserting a new item/value at an arbitrary point.
  • if you need to work with a 10 item list, define a list with 10 items before you work on it.

Splitting a long reply, on the the source topic next…

I think you are doing:

var:list bandHeights = [0;0;0;0];
dolog("bandHeights="+bandHeights);
dolog("bandHeights[2]="+bandHeights[2]+"\n");
bandHeights[2]=bandHeights[2]+1;
dolog("bandHeights="+bandHeights);
dolog("bandHeights="+bandHeights[0]);
dolog("bandHeights[1]="+bandHeights[1]);
dolog("bandHeights[2]="+bandHeights[2]);
dolog("bandHeights[3]="+bandHeights[3]);

result list 0;0;1;0 rather than:

dolog("bandHeights="+bandHeights);
dolog("bandHeights[2]="+bandHeights[2]+"\n");
bandHeights[2]=bandHeights[2]+1;
dolog("bandHeights="+bandHeights);
dolog("bandHeights="+bandHeights[0]);
dolog("bandHeights[1]="+bandHeights[1]);
dolog("bandHeights[2]="+bandHeights[2]);
dolog("bandHeights[3]="+bandHeights[3]);

which gives 0;1;3;3. But I think you’re confusing the (variable) equivalent of:

  • $TimelineBand - the number of the band in which the item is to be drawn.
  • $TimelineBands (for the view parent container) - the list of band screen labels
  • $TimelineBands.count (for the view parent container) - the number of overall bands.

As your code sample is incomplete (not snark) it’s not clear but this might be another disconnect:

Are you trying to set the size of bandHeights to the timeline’s $TimelineBands.count? If so, we’ve already established you can’t auto-size/define a list. But if $TimelineBands.count were 5, you could do:

var:list bandHeights = [];
$TimelineBands.each(aBand){
   bandHeights = bandHeights + [0];
}
// bandHeights is [0;0;0;0;0] and bandHeights.count is 5

Or , what is the purpose of bandHeights?

Thanks for a thorough analysis, @mwra - I think we’re both seeing the same thing, from slightly different perspectives. I hear ‘list’ and instantly start thinking arrays, with all the associated behaviours they generally come with (such as being able to access uninitialised elements), when they’re better thought of as a text string that is expanded and collapsed on demand. The silent error notifications from the parser throws me at times, but at least suggests making smaller and simpler examples to work through.

In my code excerpts, bandHeights is part of an attempt to replicate the timeline view into map view, and would be used to calculate how many items overlap in a single band. This is almost certainly now too much processing to run inside an action, so for the moment I’m going to put this on the eternal back-burner and get on with the writing I need to produce for this.

The timeline itself is very bursty - it runs over around a decade looking at service and API changes on platforms and their impact on research, so there will be a frenzy of events over the space of a couple of months, then six months of silence before it happens again. I’d been using Tinderbox’s timeline bands to cluster causes and impacts (including my own administrative processes and discussions with departments) in an effort to simplify this, but ultimately, as hinted at repeatedly in this forum, I just need a bigger screen!

This is the most common unintentional error seen when using action code—judging how it differs from [other thing] than working from how it is described as working. Also, if assuming Action code was written as a fully formed language (it wasn’t!) that leads to erroneous assumptions about features that ‘ought’ to be present. The fact that action code grew up a different way makes it no better or worse, but it pays to read the docs and not worry that it isn’t the methods your prefer because you know them better. :slight_smile:

Tinderbox does not have arrays. Lists are, in simplest form, stored as strings with semi-colons marking the item boundaries.

OK, so you are trying to figure the current max number of items horizontally overlapping within a band, as in the ‘hight’ of the band. I think that reflects the current view configuration rather than a finite. IOW, if you rescale the view, the packing may change. I’m not aware that this is information (deliberately) provided by the Timeline view as packing within a band reflects the degree of time overlap (x-axis) and the number of items due to be drawn in the band.

From experience, most real world temporal data is and most timeline libraries struggle with that. In part as the viewer of the output we want a layout that is pleasing on the eye and doesn’t involve too much unpacking. I think most timeline algos, whether using bands or not, start top left and follow a logic like:

  • sort items in date order
  • plot the first item
  • plot the next item after the first or below it if the duration (width) of the item overlaps
  • the next choice is if the current item starts after the finish of the first item or not. IOW, when to start a new ‘column’ within the layer and how many overlapping columns (but never column items) are allowed. In the secondary columns there is a further consideration of not only the position of the note above it but whether the ‘row’ to be used is impeded by a note stretching out into/through the column, in which case the next vertical/row position needs to be tested.

I’m not sure there is a standard approach for all that and as stated the packing my be recomputed every time the view window/scale is altered. If you don’t mind vertical extension of view/bands, just keep plotting right and down. Also, whilst action code know top left corner co-ords ($Xpos, $Ypos) and $Height, $Width, I don’t think there is a built-in ‘collision’ detection operator. IOW, a op that tells you “Am I going to collide with (overlap) an existing note”. Plus, the further you move right through time the more likely the current note is going to be placed some way from a position based only on sequence and start date. But then, that’s the point of a timeline view, where the view deicides the position vs a map where the user deicides.

Also when programmatically moving items on a map (i.e. without actually seeing them) beware creating unintentional composite notes, or, back stacking notes in the same position unintentionally causing encapsulation (one note moving the other to its child map).

As I think you’ve found, trying to drag a map based on a timeline view’s layout is a rather quixotic task.

If you have some sort of an example doc (real or simulated data) that shows what you need on map that you can’t get in timeline or vice versa it might present some opportunities for a different approach to the goal. TBH, if you haven’t already, just manually building the map you expect from the timeline you have might help better show the constraints/opportunities. Worthing from a considered yet conceptual plan gets difficult in this scenarios as the edge cases can be hard to imagine. Making an actual maps makes manifest those problems in a manner it is easier to explore.

HTH.