Tutorial for positions: comments please

53 views
Skip to first unread message

Edward K. Ream

unread,
May 22, 2013, 9:30:07 AM5/22/13
to leo-e...@googlegroups.com
Here is the first draft of a tutorial for positions.  It wasn't easy.  All comments welcome.

A **position** object represents a specific node in a Leo outline.

Equivalently, a position represents the state of a Leo generator, that is,
a specific node during a traversal of a Leo outline.

Because of clones, a node may appear arbitrarily many times in an outline,
so a node may appear at arbitrarily many positions in the outline.

For any position p, **p.v** is the vnode at position p.

A node's vnode never changes, regardless of where the node appears in the
outline, and regardless of whether the node is cloned or not.

**Positions become invalid when the outline changes.**

You may save and and use positions *provided* the outline remains unchanged.

Leo's generators deliver positions, one after each other, using a *single*
(ever-changing) position. As a result, scripts must use p.copy() to create
lists of positions. For example,

    aList = [p.copy() for p in c.rootPosition().subtree()]

Edward

P.S.  I considered using an example outline containing clones,
but I'm not sure that adds enough to be worth including in a precis.

EKR

Jacob Peck

unread,
May 22, 2013, 10:06:37 AM5/22/13
to leo-e...@googlegroups.com
That last bit about p.copy() just enlightened me all over the place...

This is a good draft. I like it as is.

-->Jake

Edward K. Ream

unread,
May 22, 2013, 11:30:41 AM5/22/13
to leo-editor
On Wed, May 22, 2013 at 9:06 AM, Jacob Peck <gates...@gmail.com> wrote:

That last bit about p.copy() just enlightened me all over the place...

This is a good draft.  I like it as is.

Thanks for this feedback.

What I learned most from the Camtasia tutorials is that it's really important to keep things as short as possible.  More than just "omit needless words", the principle "omit needless ideas" is crucial.

I think this is the essence of "the curse of knowledge".  We want to explain way too much, when what is important are the very basics.

Edward

Edward K. Ream

unread,
May 22, 2013, 11:49:50 AM5/22/13
to leo-e...@googlegroups.com
On Wednesday, May 22, 2013 9:06:37 AM UTC-5, Jacob Peck wrote:

> This is a good draft.  I like it as is.

The new words are on Leo's web site at:
http://leoeditor.com/scripting.html#positions

Edward

Terry Brown

unread,
May 22, 2013, 12:09:41 PM5/22/13
to leo-e...@googlegroups.com
Just a thought - while it's fresh in your mind you could add a bit on
building trees from scripts, p.insertAsLastChild etc. etc.

Cheers -Terry

Edward K. Ream

unread,
May 22, 2013, 7:23:04 PM5/22/13
to leo-editor
On Wed, May 22, 2013 at 11:09 AM, Terry Brown <terry_...@yahoo.com> wrote:

> Just a thought - while it's fresh in your mind you could add a bit on building trees from scripts, p.insertAsLastChild etc. etc.

It's on the list, but I don't think it has too high a priority.  Getting clear about p.deletePositionsInList is much more important, imo.

EKR

Fidel Pérez

unread,
May 23, 2013, 3:26:44 AM5/23/13
to leo-e...@googlegroups.com
I have spent some time trying to make a function to loop through nodes inserting childs, and after that I understand some of the reasons why looping through nodes is so difficult. My conclusions are:

- Since the position "p" of a node, right now, offers no more information than the position on a tree which will disappear if the tree changes, we might as well set the position value to be the actual numbered position that node occupies in the tree.
This way we don't loose any information or functionality from the nodes position reference, but instantly win a reference, since users will be able to call nodes by their position on the tree, even if the tree changes (When the tree changes we will obviously be calling the new node which occupies that position).
- What I'm asking for is just a way to reference and call actual position on the tree, by number,regardless of what has happened before or after or which node is there, just go to the "nth" node in the tree, but without having to loop through all the tree for that.

This would make possible:
    - Simple loops through the tree while editing it, since the user would be able to "calculate" the absolute position of the nodes he wants edited
    - Deletenodesinlist would instantly work through this approach (If our list to delete is position [3,4,8], the function will delete nodes by position [3,3,6], each time a node is deleted, all the numbers on the list must be substracted 1) 


Double loops are infernal right now, because you have to consider:
- When a node changes, all the positions will change
- If then there is a loop within the loop, after a node changed, there is no way to keep reference in both loops (there is, we can make a list, but then we would need another list in case of 3rd concatenated loop, another in 4rth, etc etc) Not practical.
But again, I think this approach would also make those loops simpler.

Fidel Pérez

unread,
May 23, 2013, 3:58:55 AM5/23/13
to leo-e...@googlegroups.com
- Cutting and pasting multiple selected nodes, moving multiple selected nodes, etc, becomes trivial too with this approach. Attached Leo file does that to demonstrate what I mean, but requires an inmense amount of loops since I canot refer the absolute position on a tree.

Say we have a tree:

A
 B
 C
 D
 E

Code required to cut/paste [D,E] just after [B] right now:

Get all items position

A-1,B-2,C-3,D-4,E-5

for each item in the list [D-4,E-5] we have to:
Loop through the tree. 
When we get to the position 4, we cut that node. Whenever the tree outcome changes, positions are updated, so we have to update also our position list. For every position bigger than 4, we substract one so we have now that [E-4]
After finding it and cutting, we now loop again through the tree. When the loop index is 2 we paste the node. This position reference now must be updated to 3,since we pasted already in 2.
Go to next position, repeat process.
Calculations have to be made in case the paste position is after the nodes being cut (position 2 wont be stable) so each time we cut/paste a node, that position must also be updated.

Code required If positions were to also refer absolute position on the tree:

cut 4
paste in 2
cut 4
paste in 3

Edward K. Ream

unread,
May 23, 2013, 5:47:42 AM5/23/13
to leo-editor
On Thu, May 23, 2013 at 2:26 AM, Fidel Pérez <fidel...@gmail.com> wrote:
I have spent some time trying to make a function to loop through nodes inserting childs, and after that I understand some of the reasons why looping through nodes is so difficult. My conclusions are:

- Since the position "p" of a node, right now, offers no more information than the position on a tree which will disappear if the tree changes, we might as well set the position value to be the actual numbered position that node occupies in the tree.

This is an interesting idea, but there is no way I'm going to change the position class as you suggest, for at least the following reasons:

1. The position class appears everywhere in the code.  It's way too late to change it significantly.

2. Rather than silently changing the meaning of a position index, we want to know whether p.exists().  Changing the meaning of a position silently is asking for chaos.  Changed positions would be time bombs.

3. The position class already has various ways of describing nodes using indices.  See p.archivedPosition and p.key.

Edward

Fidel Pérez

unread,
May 23, 2013, 5:50:50 AM5/23/13
to leo-e...@googlegroups.com
Hehe thanks for the detailed answer, I just study and give my thoughts on the code, but always understand there are many things Im not aware of :)

Edward K. Ream

unread,
May 23, 2013, 8:32:29 AM5/23/13
to leo-editor
On Thu, May 23, 2013 at 4:50 AM, Fidel Pérez <fidel...@gmail.com> wrote:
Hehe thanks for the detailed answer, I just study and give my thoughts on the code, but always understand there are many things Im not aware of :)

I should have mentioned that the problems you are wanting to solve are real, and important.  I'll take a look at your code to see whether I can suggest some alternatives.

EKR

Edward K. Ream

unread,
May 23, 2013, 10:42:38 AM5/23/13
to leo-editor
On Thu, May 23, 2013 at 2:58 AM, Fidel Pérez <fidel...@gmail.com> wrote:
- Cutting and pasting multiple selected nodes, moving multiple selected nodes, etc, becomes trivial too with this approach.

There are several ways to deal with multiple cuts/pastes/inserts/whatever.

1. Make the changes in a place so that positions do not change.  This is what the clone-find-all commands do.

2. Do all the real work using vnodes.  You might say that this is cleaner conceptually.

The important distinction to understand is that Leo stores all data in vnodes, including structure data.  Provided that you don't create loops from child to any ancestor, the v.children array may contain any node any number of times.  Of course, the other data structures must be updated (carefully!).  This is what the low-level vnode methods do.

Unfortunately, the higher-level methods that insert and move nodes are *position* methods.  These do the tricky work of calling the low-level *vnode* methods.  There aren't any higher-level vnode methods that insert/delete/clone/move vnodes.  Perhaps there should be.

The best advice I can give is first, to use method 1 if you can.  If not, then you have to do some real thinking in order to understand what vnodes you want where.  Once you have that figured out, you can either use positions as helpers, or do all the work at the vnode level, using the corresponding position methods as a rough guide.

Finally, you might look at Leo's cut/paste code in leoCommands.py.  See the node::

    leoPy.leo#Code-->Core classes-->@file leoCommands.py-->
    class commands-->Command handlers...-->Outline menu...-->
    Top Level... (Commands)-->c.Cut & Paste Outlines

These are solving a simpler problem, but some of this code might be useful.

HTH.  Please feel free to ask more questions.

Edward

Terry Brown

unread,
May 23, 2013, 10:53:51 AM5/23/13
to leo-e...@googlegroups.com
On Thu, 23 May 2013 09:42:38 -0500
"Edward K. Ream" <edre...@gmail.com> wrote:

> Unfortunately, the higher-level methods that insert and move nodes are
> *position* methods. These do the tricky work of calling the low-level
> *vnode* methods. There aren't any higher-level vnode methods that
> insert/delete/clone/move vnodes. Perhaps there should be.

Since you brought vnodes into the topic :-)

Building large data visualization (i.e. throw away) trees is slow with
positions, but fast with vnodes. I use this monkey patch:

from leo.core.leoNodes import vnode
if not hasattr(vnode, 'insertAsLastChild'):
def ialc(self):
vnode(self.context)._linkAsNthChild(self, len(self.children))
return self.children[-1]
vnode.insertAsLastChild = ialc

Cheers -Terry

Edward K. Ream

unread,
May 23, 2013, 11:15:49 AM5/23/13
to leo-editor
On Thu, May 23, 2013 at 9:53 AM, Terry Brown <terry_...@yahoo.com> wrote:

Building large data visualization (i.e. throw away) trees is slow with
positions, but fast with vnodes.  I use this monkey patch:

from leo.core.leoNodes import vnode
if not hasattr(vnode, 'insertAsLastChild'):
    def ialc(self):
        vnode(self.context)._linkAsNthChild(self, len(self.children))
        return self.children[-1]
    vnode.insertAsLastChild = ialc

Cool.  As I look at p.deletePositionsInList, I can think of no reason why it should work.  It looks thoroughly broken.  Who wrote this trash?  According to qblame, I did.  Hahaha.

I don't know how you are approaching the problem, Terry, but it seems to me that it's more natural to look at things from the data (vnode) point of view rather than the traversal (position) point of view.  If that is so, then having higher-level vnode methods, such as v.insertAsLastChild, makes a lot of sense.

Edward

Terry Brown

unread,
May 23, 2013, 11:57:29 AM5/23/13
to leo-e...@googlegroups.com
On Thu, 23 May 2013 10:15:49 -0500
"Edward K. Ream" <edre...@gmail.com> wrote:

I'm just building trees from scratch under an existing position, so
only insertAsLastChild is needed. To delete it I just use
p.deletePositionsInList([top_node]). I think deletePositionsInList
came into being to support the multiple selected node deletion case in
contextmenu.py - you can select multiple nodes using shift or ctrl and
the mouse and delete all of them at once with contextmenu's right-click
Delete.

But I think contextmenu.py may do its own thing without using
deletePositionsInList, you could have a look.

Cheers -Terry

Edward K. Ream

unread,
May 24, 2013, 8:23:45 AM5/24/13
to leo-editor
On Thu, May 23, 2013 at 9:53 AM, Terry Brown <terry_...@yahoo.com> wrote:

Since you brought vnodes into the topic :-)

Building large data visualization (i.e. throw away) trees is slow with
positions, but fast with vnodes.  I use this monkey patch [to define v.insertAsLastChild]:

Just a few methods need to be added to the vnode class to fill the holes in the vnode API. That is, most of the position methods in tree::

    p.Moving, Inserting, Deleting, Cloning, Sorting

make no sense in the vnode class.  For example, p.moveAfter, p.moveToNthChildOf, etc leave data unchanged.  Furthermore, v.insertAfter would not be well defined: because of clones, vnode v may have many parents. Ditto for v.clone, but see below.

It seems worthwhile to add v.insertAsNthChild, with v.insertAsFirstChild and v.insertAsLastChild defined in using v.insertAsNthChild.  As I write this, I see that it would be possible to define::

    v.cloneAsNthChild(parent_vnode,n)

These four methods seem to me to be the only holes in the vnode API.  I'll add these *experimental* definitions today.

Edward

Edward K. Ream

unread,
May 24, 2013, 9:05:09 AM5/24/13
to leo-editor
On Thu, May 23, 2013 at 10:15 AM, Edward K. Ream <edre...@gmail.com> wrote:

As I look at p.deletePositionsInList, I can think of no reason why it should work.  It looks thoroughly broken.  Who wrote this trash?  According to qblame, I did.  Hahaha.

Well, the read logic uses p.deletePositionsInList, as I found out when a unit test of the consistency of LeoPyRef.leo failed!  So this is no laughing matter.  Something has to be done asap.

EKR

Edward K. Ream

unread,
May 24, 2013, 12:40:49 PM5/24/13
to leo-editor
On Fri, May 24, 2013 at 7:23 AM, Edward K. Ream <edre...@gmail.com> wrote:

> These four methods seem to me to be the only holes in the vnode API.  I'll add these *experimental* definitions today.

Rev 5793 adds the following new vnode methods, with a *minimal* unit test::

    def cloneAsNthChild(self,parent_v,n):
        # Does not check for illegal clones!
        v = self
        v._linkAsNthChild(parent_v,n)
        return v
   
    def insertAsFirstChild(self):
        v = self
        return v.insertAsNthChild(0)
       
    def insertAsLastChild(self):
        v = self
        return v.insertAsNthChild(len(v.children))
   
    def insertAsNthChild(self,n):
        v = self
        assert 0 <= n <= len(v.children)
        v2 = vnode(v.context)
        v2._linkAsNthChild(v,n)
        assert v.children[n] == v2
        return v2

Terry, please test the insert methods.  When I have time, later today, I'll figure out what to do about bad args to v.cloneAsNthChild.

Edward

Edward K. Ream

unread,
May 24, 2013, 5:18:02 PM5/24/13
to leo-editor
On Fri, May 24, 2013 at 11:40 AM, Edward K. Ream <edre...@gmail.com> wrote:
 
When I have time, later today, I'll figure out what to do about bad args to v.cloneAsNthChild.

Well, it seems that the caller should take responsibility for ensuring that the arguments to v.cloneAsNthChild are correct.  It *is* possible to check the args as follows (not tested), following the pattern in c.clone::

    def cloneAsNthChild(self,parent_v,n):
        v = self
        c = v.context
        v._linkAsNthChild(parent_v,n)
        if c.validateOutline():
            return v
        else:
            v._cutLink(n,parent_v)
            return None

But this would be extremely slow, contrary to the intention of having the vnode methods be fast.  So for now, the watchword will be, **user beware**.

Edward
Reply all
Reply to author
Forward
0 new messages