Recent

Author Topic: "Flatten" linked-lists or trees in Watches of debugger  (Read 3503 times)

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
"Flatten" linked-lists or trees in Watches of debugger
« on: July 22, 2024, 04:10:20 pm »
Lazarus 3.99 (FpDebug only)
For review: https://wiki.freepascal.org/FpDebug-Watches-Intrinsic-Functions#flatten

See description in link. Displays linked lists, and trees, etc as array in the Watches Window

Comments on naming, options, etc welcome.
« Last Edit: July 22, 2024, 06:03:26 pm by Martin_fr »

440bx

  • Hero Member
  • *****
  • Posts: 4731
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #1 on: July 22, 2024, 04:49:55 pm »
call me lazy but, it would be great if you posted the code of the little example too.  That would enable the "reviewer" to initially be on the same page as you are and not run the risk of changing the code due to a typo or mental lapse.

question: my version of trunk is two days old, (downloaded on the 20th), is that recent enough to test/review this feature or should I update to today's version ?

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #2 on: July 22, 2024, 04:57:04 pm »
Attached has some demo data and watches

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #3 on: July 22, 2024, 04:59:35 pm »
question: my version of trunk is two days old, (downloaded on the 20th), is that recent enough to test/review this feature or should I update to today's version ?

Todays.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #4 on: July 22, 2024, 05:01:37 pm »
And, one possible obvious suggestion. When walking a tree (e.g. with left/right fields), it may be useful to add info to each result item, if it was a left or right key. But that is for another day.

440bx

  • Hero Member
  • *****
  • Posts: 4731
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #5 on: July 22, 2024, 10:25:16 pm »
Hi Martin,

I've got a comment/request/suggestion about the :flatten feature.

I think it is a very nice feature.  It provides an array view of what may be a multilevel tree.  That can sometimes be very useful.

For reference, I used the code you first posted (the example's simplicity makes visualizing the operations easier.)
Code: Pascal  [Select][+][-]
  1. {$APPTYPE CONSOLE}
  2.  
  3. program _Flatten;
  4.  
  5. type
  6.   TFoo = class
  7.     Value : integer;
  8.     Next  : TFoo;
  9.   end;
  10.  
  11. var
  12.   f, f1 : TFoo;
  13.   i     : integer;
  14.  
  15. begin
  16.   f  := TFoo.Create;
  17.   f1 := f;
  18.  
  19.   for i := 1 to 5 do
  20.   begin
  21.     f1.Value := i;
  22.     f1.Next  := TFoo.Create;
  23.     f1       := f1.Next
  24.   end;
  25.  
  26.   f.Value := -1;
  27. end.                                

Running the code to the "end." produces the attached Watches screenshot.

While the "array"/"flat" view is very nice to have, the dependency tree is lost and that can sometimes be a problem.  What I'd like to see is, instead of having the node be numbered "1", I'd like to see it labeled as "0.0", node "2" labeled "0.0.0" (instead of "2") and so on, that way the label/(array index) gives an indication of where in the tree the element belongs without having to navigate the actual tree.  That way if a branch has multiple children then they would be labeled "0.0.0", "0.1.0", "0.2.0", etc presuming the branch has 2 children which in turn have one child each.    That would give the best of both worlds, the flat array view and an indicator of where the node belongs in the tree structure.

I realize that what I'm suggesting may not be possible if the tree uses numbers instead of text labels but, I thought it wouldn't hurt to mention it.

Lastly, I think displaying the _vptr$TOBJECT (or whatever object is inherited from) could be made optional.  The idea is that the flattening operation is done to see the link tree not the objects (but, I could be wrong about that, since as most everyone knows, I don't OOP ;) )

Hopefully some of this is useful to you.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #6 on: July 22, 2024, 11:05:29 pm »
What I'd like to see is, instead of having the node be numbered "1", I'd like to see it labeled as "0.0", node "2" labeled "0.0.0" (instead of "2") and so on, that way the label/(array index) gives an indication of where in the tree the element belongs without having to navigate the actual tree.  That way if a branch has multiple children then they would be labeled "0.0.0", "0.1.0", "0.2.0", etc presuming the branch has 2 children which in turn have one child each. 

It all depends on what is stored and why you traverse it. One use case I would have had in the past, was an AVL tree with words, and I wanted to know if a certain word is in it. It would not have mattered where in the tree it was. Searching a tree with maybe 300 nodes really not a pleasure, browsing an array that size would have been better. [1]

As for the numbering. The result is an array. The index is not part of the data. So there can't be custom values in the index. But I have a similar idea, and the solution would be that each array entry is an artificial record, with the fields "k" for the key-info, and "d" for the data (current returned data).

My idea was actually to have a "depth"(d) and the "keyname"(k) (or "key index") and the data(o)
So it would be like
Code: Text  [Select][+][-]
  1. d: 1, k: Left, o: ....
  2. d: 2, k: Left, o: ....
  3. d: 3, k: Left, o: ....
  4. d: 2, k: Right, o: ....
  5. d: 3, k: Left, o: ....
  6. d: 1, k: Right, o: ....
  7. d: 2, k: Left, o: ....

If the keys were expressions, then their index might be preferred (chosen via option), to avoid a long expression show in the result

But both ways can be offered. Yours has merits too.

Alas, as indicated: Work for another day.


Quote
Lastly, I think displaying the _vptr$TOBJECT (or whatever object is inherited from) could be made optional.  The idea is that the flattening operation is done to see the link tree not the objects (but, I could be wrong about that, since as most everyone knows, I don't OOP ;) )

That is more a general issue of how TObject is displayed.

Also, it may not be as much of an issue as though in first. Usually the interest is not on the List/Tree node/container, but the data.

If the Data is stored an a field of its own then it can be displayed standalone. In the example the Data is an integer on the field "Value" (But it could be an object on a field "Item").
Code: Pascal  [Select][+][-]
  1. :flatten(f, Next)[0..999].Value

And what that will do, is just extract the values. Though if you have info like nil, or errors in the list, they will be replaced with a new, meaningless error.....

And of course, if the above idea (numeration) is implemented, then you can't use this mapping as you loose the fields with the "location". (Unless there is a way for a custom mapping, which there may one day be)



[1] And that brings me to the idea of creating an IndexOf intrinsic

dbannon

  • Hero Member
  • *****
  • Posts: 3156
    • tomboy-ng, a rewrite of the classic Tomboy
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #7 on: July 23, 2024, 02:43:07 am »

Another excellent addition to the debugger. I am amazed how much it has improved in the last couple of years !

Code: Pascal  [Select][+][-]
  1. flatten(f, Next)

In your example, you offer two parameters to flatten(), is the "Next" there to tell the function to show the value of next or to tell it how to get to the the next item in the list ?

In most cases (not all) I'm not really interested in the value of Next, all I want to see is the data in the item. Having the pointer value there is certainly correct but does make it all just a bit harder to read sometimes.

Davo
Lazarus 3, Linux (and reluctantly Win10/11, OSX Monterey)
My Project - https://github.com/tomboy-notes/tomboy-ng and my github - https://github.com/davidbannon

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #8 on: July 23, 2024, 09:51:28 am »
In your example, you offer two parameters to flatten(), is the "Next" there to tell the function to show the value of next or to tell it how to get to the the next item in the list ?

In most cases (not all) I'm not really interested in the value of Next, all I want to see is the data in the item. Having the pointer value there is certainly correct but does make it all just a bit harder to read sometimes.

You can have any amount of parameters (up to 999 different fields);
Code: Pascal  [Select][+][-]
  1. :flatten( MyTree, Left, Right )

:flatten retrieves all the objects/records. It needs "Next" as the name of the field that it needs to follow. It is coincidence that in the example the link to the next object is in a field called "Next".

To just display the data in each node, it can be combined with [0..9999].

So if you have a tree, with
Code: Pascal  [Select][+][-]
  1. type TTreeNode = class
  2.   Left, Right: TTreeNode;
  3.   Data: String; // or another class, or...
  4. end
Then you can do
Code: Pascal  [Select][+][-]
  1. :flatten(node, Left, Right)[0..9999].Data

And you get only the Data of each node.

The [..] array slice is also described on the linked page about intrinsics. It adds the magic to allow mapping each entry in the array.

In my image, in the first post, I watch the same linked list twice, the 2nd time with mapping I only get the numbers.
« Last Edit: July 23, 2024, 10:16:06 am by Martin_fr »

dbannon

  • Hero Member
  • *****
  • Posts: 3156
    • tomboy-ng, a rewrite of the classic Tomboy
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #9 on: July 23, 2024, 01:14:50 pm »
yep, that is seriously useful !

Presently, I would use a debug function added to the unit that dumps the data to stdout.  Will be so much easier you way. Thanks !

Davo
Lazarus 3, Linux (and reluctantly Win10/11, OSX Monterey)
My Project - https://github.com/tomboy-notes/tomboy-ng and my github - https://github.com/davidbannon

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #10 on: July 24, 2024, 03:05:18 pm »
Arrays and Array-Slices...


So I am adding expansion of arrays. Optional.

Arrays:

Code: Pascal  [Select][+][-]
  1. type
  2.   PBar = ^TBar;
  3.   TBar = record
  4.     Data: array[1..4] of integer;
  5.     Others: array[1..4] of PBar;
  6.     //MorePtr: PBar; // pointer to list of PBar, so MorePtr[0..9] will work
  7.     //More: array[1..2, 1..4] of PBar;
  8.     Next: PBar;
  9.   end;
  10. var
  11.   MyBar: PBar;
  12.  
  13. begin
  14.   MyBar := new(PBar);             MyBar^             := Default(TBar); with MyBar^             do begin Data[1] := 101; Data[2] := 102; end;
  15.   MyBar^.Next := new(PBar);       MyBar^.Next^       := Default(TBar); with MyBar^.Next^       do begin Data[1] := 201; Data[2] := 202; end;
  16.   MyBar^.Next^.Next := new(PBar); MyBar^.Next^.Next^ := Default(TBar); with MyBar^.Next^.Next^ do begin Data[1] := 301; Data[2] := 302; end;
  17.  
  18.   MyBar^.Others[1] := new(PBar);  MyBar^.Others[1]^ := Default(TBar);  with MyBar^.Others[1]^ do begin Data[1] := 111; Data[2] := 112; end;
  19.   MyBar^.Others[2] := new(PBar);  MyBar^.Others[2]^ := Default(TBar);  with MyBar^.Others[2]^ do begin Data[1] := 121; Data[2] := 122; end;
  20.   MyBar^.Others[3] := new(PBar);  MyBar^.Others[3]^ := Default(TBar);  with MyBar^.Others[3]^ do begin Data[1] := 131; Data[2] := 132; end;
  21.   MyBar^.Others[4] := new(PBar);  MyBar^.Others[4]^ := Default(TBar);  with MyBar^.Others[4]^ do begin Data[1] := 141; Data[2] := 142; end;
  22.  
  23.   MyBar^.Next^.Others[1] := new(PBar); MyBar^.Next^.Others[1]^ := Default(TBar); with MyBar^.Next^.Others[1]^ do begin Data[1] := 211; Data[2] := 212; end;
  24.   MyBar^.Next^.Others[2] := new(PBar); MyBar^.Next^.Others[2]^ := Default(TBar); with MyBar^.Next^.Others[2]^ do begin Data[1] := 221; Data[2] := 222; end;
  25.   MyBar^.Next^.Others[3] := new(PBar); MyBar^.Next^.Others[3]^ := Default(TBar); with MyBar^.Next^.Others[3]^ do begin Data[1] := 231; Data[2] := 232; end;
  26.   MyBar^.Next^.Others[4] := new(PBar); MyBar^.Next^.Others[4]^ := Default(TBar); with MyBar^.Next^.Others[4]^ do begin Data[1] := 241; Data[2] := 242; end;
  27.  
  28.   //MyBar^.Others[2]^.Others[1] := new(PBar); MyBar^.Others[2]^.Others[1]^ := Default(TBar); with MyBar^.Others[2]^.Others[1]^ do begin Data[1] := 211; Data[2] := 212; end;
  29.   //MyBar^.Others[2]^.Others[2] := new(PBar); MyBar^.Others[2]^.Others[2]^ := Default(TBar); with MyBar^.Others[2]^.Others[2]^ do begin Data[1] := 221; Data[2] := 222; end;
  30.  

Code: Text  [Select][+][-]
  1. :f_(MyBar^, Next, Others)
Would normally return
Code: Text  [Select][+][-]
  1. Len=7: ((Data: (101, 102, 0, 0);Others: ($0000000000117570^: (), $00000000001175B0^: (), $00000000001175F0^: (), $0000000000117630^: ());Next: $00000000001174F0^: ();),
  2. (Data: (201, 202, 0, 0);Others: ($0000000000117670^: (), $00000000001176B0^: (), $00000000001176F0^: (), $0000000000117730^: ());Next: $0000000000117530^: ();),
  3. (Data: (301, 302, 0, 0);Others: (nil, nil, nil, nil);Next: nil;),
  4. nil,
  5. (nil, nil, nil, nil),
  6. ($0000000000117670^: (), $00000000001176B0^: (), $00000000001176F0^: (), $0000000000117730^: ()),
  7. ($0000000000117570^: (), $00000000001175B0^: (), $00000000001175F0^: (), $0000000000117630^: ()))

Or for "Data"
Code: Text  [Select][+][-]
  1. :f_(MyBar^, Next, Data)
Code: Text  [Select][+][-]
  1. Len=7: ((Data: (101, 102, 0, 0);Others: ($0000000000117570^: (), $00000000001175B0^: (), $00000000001175F0^: (), $0000000000117630^: ());Next: $00000000001174F0^: ();),
  2. (Data: (201, 202, 0, 0);Others: ($0000000000117670^: (), $00000000001176B0^: (), $00000000001176F0^: (), $0000000000117730^: ());Next: $0000000000117530^: ();),
  3. (Data: (301, 302, 0, 0);Others: (nil, nil, nil, nil);Next: nil;),
  4. nil,
  5. (301, 302, 0, 0),
  6. (201, 202, 0, 0),
  7. (101, 102, 0, 0))

That is, in each case, when the key returned an array, that array is include "as is".

That is certainly good for "Data" (where you likely don't want each of the numbers to become a row of its own in the flatten-result).

For "Others" it depends on the use case.
Like, if you had the keys "Next" and "Previous", then you would only list "Next", because "Previous" should already be in the list.
So you may or may not want to specify the array as <sort of key>.

You can select individual elements of an array (no ranges/slices => see below)
Code: Text  [Select][+][-]
  1. :f_(MyBar^, Next, Others[2], Others[4])

But if you want all keys, that can be a lot of work, and if the length is dynamic then it wont work that way at all.

So you will be able to do
Code: Text  [Select][+][-]
  1. :f_(MyBar^, Next, Others, [array])
  2. :f_(MyBar^, Next, Others, [array=1])
  3. :f_(MyBar^, Next, Others, [array=2])

The first do are the same. (default is "1").

Expand arrays down to the given nesting depth.
 - Depth applies to directly nested arrays: MyBar^.More[1][2] (requires array=2 or higher)
 - Depth starts over, if there are objects in between MyBar^.Others[1]^.Others[2] (fine with array=1 because Others[1] did return an object, not a nested array)


Array-slices:
Array slices may be handled different...

Internally Array slices need different handling anyway. So it comes at no cost to give separate control to the user. That way you could allow to expand slices, but not arrays, and then you could do (if implemented)
Code: Text  [Select][+][-]
  1. :f_(MyBar^, Next, Others[1..4], Data, [slice])
Which would expand "Others", but not "Data".

But then again, there is the Question: Maybe slices should always be expanded?

For that lets have a look how they work, if not expanded. (i.e. currently):
A slice produces an array result, which can be mapped. That is any operation on the array-result is performed on each entry in it.
- foo[1..4].bar => return an array containing only the field bar of each of the 4 (object/record) value from the array foo.
- :lenght(foo[1..4]) => returns an array containing the :length of each of the 4 (hopfully string/array) values from the array foo.

And then: :_flatten(foo[1..4], Next)
returns an array with 4 entries, each entry being one flattened array for each of the 4 value in foo.

But also: :_flatten(MyBar, Others[1..4])
returns an array with 4 entries, each entry being one flattened array. The first flattened array is flattened using "Others[1]". The 2nd flattened array is flattened using "Others[2]". The 3rd ...

Question: Does that last example ever really make sense? (And if it does, does it so much that it should be default?)

IHMO, the first example (slice in the first param) could be sometimes useful, but probably in most cases wants to be flattened into ONE flattened result?
But the 2nd example (slice in the key) almost never (if ever) should return 4 results. It should flatten into one list.

Does that make sense? Or am I overlooking use cases?

Based on that, my current idea would be to have all slices inside a ":flatten" being flattened (rather than resulting in multiple separate flattened results).

If such behaviour should be override-able in future (not sure if it will), then there would be 2 ways.
- :flaten(a, b[1..2], c, [noslice])
- :flaten(a, :slice(b[1..2]), c)  // wrap the key into an intrinsic that will make the slice not being flattened

Of course, if slices by default will not be flattened, then ":slice()" could mean, for this param include slices into ONE flatten result.

=> In any case, slices will not have a "depth", but be either all one way , or all the other way.
=> They will likely use something like ":slice()" and not "[noslice]". Because the latter has some complications for the implementation.



So some thought, and some fixes later...
It will be a syntax, not an option. It will come down to

1) The current Syntax defaults to multiple flatten, or integrated into one flatten.
2) What syntax?  wrapper?  :f_( a, :x(next[1..3]) )

The difference in result is, that
** multiple flatten for :f_( a, next[1..3])
returns  a NESTED array with the 3 outer entries from the slice,
each holding ONE of the flattens
   :f_( a, next[1]),
   :f_( a, next[2]),
   :f_( a, next[3])

** integrated one flatten for :f_( a, :x(next[1..3])
return on non-nested array
   a.next[1],
   a.next[1].next[1],
   a.next[1].next[1].next[1],
  ...
   a.next[1].next[2],
   ...
   a.next[1].next[3],
   ...
   a.next[2],
   a.next[2].next[1],
   ...
   a.next[3],
  ...


As for amending the syntax of the slice, I don't want to touch the  [n..m] part itself.

That part is know by the FpDebug, but also by the IDE. If you have a watch "a[1..3]" in the watches window, and "unfold" it, then the IDE must be able to create the sub-watches; "a[1]", "a[2]", "a[3]"
« Last Edit: July 24, 2024, 10:26:34 pm by Martin_fr »

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #11 on: July 24, 2024, 11:02:19 pm »
A similar issue may arise with other future intrinsics.

E.g. there may be a   :IndexOf(array,  match)  that will return if (and where) a matching element was first found.
Using   :IndexOf(data[0..99],  match)    can do either
- search entries 0 to 99
- if data is "array of array" for each of the first 100 entries perform a separate IndexOf, and return 99 indexes.

It may even be that outside such functions, one might want to stop the "mapping" of a slice.
   data[0..99].foo[3]
- retrieves an array in the field foo for each of the entries, and get the 3rd entry from that foo array.
- to only get the 3rd entry of the map, one has to change the [0..99] to [3]. But :stop(data[0..99].foo)[3]  could mean the entry of the slice.



Such a "slice map stopper" would also work inside :flatten. Though maybe :flatten itself should be a stopper, and there could be a marker(wrapper) for slices that should not be stopped?  :flatten(a[5..9], :extslice(next[1..3]))  => would create 3 flattens, for each "next[1]", "next[2]" , "next[3]", but the slice an "a" would stay inside the flatten.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #12 on: July 26, 2024, 09:14:26 pm »
While the "array"/"flat" view is very nice to have, the dependency tree is lost and that can sometimes be a problem.  What I'd like to see is, instead of having the node be numbered "1", I'd like to see it labeled as "0.0", node "2" labeled "0.0.0" (instead of "2") and so on, that way the label/(array index) gives an indication of where in the tree the element belongs without having to navigate the actual tree.  That way if a branch has multiple children then they would be labeled "0.0.0", "0.1.0", "0.2.0", etc presuming the branch has 2 children which in turn have one child each.   

Added something...

Code: Text  [Select][+][-]
  1. :flatten(a,b, [obj1])
There are obj1, obj2, obj3 and obj4.

The default is
- if only one key is followed, then: OFF
- if more than one key is given to follow, then: obj1

Code: Pascal  [Select][+][-]
  1. program test_flatten;
  2. type
  3.   TFoo = class
  4.     Value: integer;
  5.     Next: TFoo;
  6.     More: array[5..7] of TFoo;
  7.   end;
  8. var
  9.   foo: TFoo;
  10.   m, n: Integer;
  11. begin
  12.   foo                := TFoo.Create; foo.Value := 1;
  13.   foo.Next           := TFoo.Create; foo.Next.Value := 2;
  14.   foo.Next.Next      := TFoo.Create; foo.Next.Next.Value := 3;
  15.   foo.Next.Next.Next := TFoo.Create; foo.Next.Next.Next.Value := 4;
  16.  
  17.   for m := 5 to 7 do for n := 6 to 7 do begin
  18.     foo.More[m] := TFoo.Create;              foo.More[m].Value              := m*11000000 + 1;
  19.     foo.More[m].More[n] := TFoo.Create;      foo.More[m].More[n].Value      := m*11000000 + 11;
  20.     foo.More[m].Next := TFoo.Create;         foo.More[m].Next.Value         := m*11000000 + 9901;
  21.     foo.More[m].Next.More[n] := TFoo.Create; foo.More[m].Next.More[n].Value := m*11000000 +9911;
  22.     foo.Next.More[m] := TFoo.Create; foo.Next.More[m].Value := m*10100000 + 2;
  23.   end;
  24.   m := 1; // breakpoint
  25. end.

OBJ1 will print the DEPTH and the KEY (or expression)
Code: Text  [Select][+][-]
  1. :flatten(foo, next, more, [array, obj1])
  2. Len=19: ((d: 0;k: '';v: TFoo(Value: 1;Next: $00000000015774F0;More: ($00000000015776F0, $0000000001577970, $0000000001577BF0);_vptr$TOBJECT: $000000010000F000;);),
  3. (d: 0;k: 'next';v: TFoo(Value: 2;Next: $0000000001577530;More: ($00000000015777F0, $0000000001577A70, $0000000001577CF0);_vptr$TOBJECT: $000000010000F000;);),
  4. (d: 1;k: 'next';v: TFoo(Value: 3;Next: $0000000001577570;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  5. (d: 2;k: 'next';v: TFoo(Value: 4;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  6. (d: 2;k: 'more[0]';v: TFoo(Value: 50500002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  7. (d: 2;k: 'more[1]';v: TFoo(Value: 60600002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  8. (d: 2;k: 'more[2]';v: TFoo(Value: 70700002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  9. (d: 1;k: 'more[0]';v: TFoo(Value: 55000001;Next: $0000000001577770;More: (nil, nil, $0000000001577730);_vptr$TOBJECT: $000000010000F000;);),
  10. (d: 2;k: 'next';v: TFoo(Value: 55009901;Next: nil;More: (nil, nil, $00000000015777B0);_vptr$TOBJECT: $000000010000F000;);),

OBJ2 will print the DEPTH and the index of the KEY (or expression)
Code: Text  [Select][+][-]
  1. :flatten(foo, next, more, [array, obj2])
  2. Len=19: ((d: 0;k: -1;v: TFoo(Value: 1;Next: $00000000015774F0;More: ($00000000015776F0, $0000000001577970, $0000000001577BF0);_vptr$TOBJECT: $000000010000F000;);),
  3. (d: 0;k: 0;v: TFoo(Value: 2;Next: $0000000001577530;More: ($00000000015777F0, $0000000001577A70, $0000000001577CF0);_vptr$TOBJECT: $000000010000F000;);),
  4. (d: 1;k: 0;v: TFoo(Value: 3;Next: $0000000001577570;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  5. (d: 2;k: 0;v: TFoo(Value: 4;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  6. (d: 2;k: 1;v: TFoo(Value: 50500002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  7. (d: 2;k: 1;v: TFoo(Value: 60600002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  8. (d: 2;k: 1;v: TFoo(Value: 70700002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  9. (d: 1;k: 1;v: TFoo(Value: 55000001;Next: $0000000001577770;More: (nil, nil, $0000000001577730);_vptr$TOBJECT: $00000

OBJ3/4 follow your idea.
That works for tree structures, but has the disadvantage that the 0.0.0.0.0.0.0.0. will become very long an a pure linked list. Such a linked list with 1000 entries gets 1000 * "0.". It will be cut if at a string-length of 1000.

OBJ3 uses names
Code: Text  [Select][+][-]
  1. :flatten(foo, next, more, [array, obj3])
  2. Len=19: ((k: '';v: TFoo(Value: 1;Next: $00000000015774F0;More: ($00000000015776F0, $0000000001577970, $0000000001577BF0);_vptr$TOBJECT: $000000010000F000;);),
  3. (k: 'next';v: TFoo(Value: 2;Next: $0000000001577530;More: ($00000000015777F0, $0000000001577A70, $0000000001577CF0);_vptr$TOBJECT: $000000010000F000;);),
  4. (k: 'next.next';v: TFoo(Value: 3;Next: $0000000001577570;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  5. (k: 'next.next.next';v: TFoo(Value: 4;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  6. (k: 'next.more[0]';v: TFoo(Value: 50500002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  7. (k: 'next.more[1]';v: TFoo(Value: 60600002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  8. (k: 'next.more[2]';v: TFoo(Value: 70700002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  9. (k: 'more[0]';v: TFoo(Value: 55000001;Next: $0000000001577770;More: (nil, nil, $0000000001577730);_vptr$TOBJECT: $000000010000F000;);),
  10. (k: 'more[0].next';v: TFoo(Value: 55009901;Next: nil;More: (nil, nil, $00000000015777B0);_vptr$TOBJECT: $000000010000F000;);),
  11. (k: 'more[0].next.more[2]';v: TFoo(Value: 55009911;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  12.  

OBJ4 uses indexes (your idea) / in this example mixed with array expansion, which adds the indexes in "[]"
Code: Text  [Select][+][-]
  1. :flatten(foo, next, more, [array, obj4])
  2. Len=19: ((k: '';v: TFoo(Value: 1;Next: $00000000015774F0;More: ($00000000015776F0, $0000000001577970, $0000000001577BF0);_vptr$TOBJECT: $000000010000F000;);),
  3. (k: '0';v: TFoo(Value: 2;Next: $0000000001577530;More: ($00000000015777F0, $0000000001577A70, $0000000001577CF0);_vptr$TOBJECT: $000000010000F000;);),
  4. (k: '0.0';v: TFoo(Value: 3;Next: $0000000001577570;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  5. (k: '0.0.0';v: TFoo(Value: 4;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  6. (k: '0.1[0]';v: TFoo(Value: 50500002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  7. (k: '0.1[1]';v: TFoo(Value: 60600002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  8. (k: '0.1[2]';v: TFoo(Value: 70700002;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  9. (k: '1[0]';v: TFoo(Value: 55000001;Next: $0000000001577770;More: (nil, nil, $0000000001577730);_vptr$TOBJECT: $000000010000F000;);),
  10. (k: '1[0].0';v: TFoo(Value: 55009901;Next: nil;More: (nil, nil, $00000000015777B0);_vptr$TOBJECT: $000000010000F000;);),
  11. (k: '1[0].0.1[2]';v: TFoo(Value: 55009911;Next: nil;More: (nil, nil, nil);_vptr$TOBJECT: $000000010000F000;);),
  12.  


Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 10552
  • Debugger - SynEdit - and more
    • wiki
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #13 on: July 26, 2024, 09:27:57 pm »
About specifying the options array,err,obj1...

If non are specified, then default is used (all, expect obj or including obj if multi key). But the "default set" could be assumed anything.

Options that take a number (array=1, max = 1000) are exempt from below.

Then options can be specified as
Code: Text  [Select][+][-]
  1. flatten(a,b, [nil, deref=true, seen=false])
This starts with an empty set of options.
- an option without modifier is included (true)
- an option with =true is included
- an option with =false  is NOT included
  (could be used, if true/false is computed from a variable in the debugged app)

Code: Text  [Select][+][-]
  1. flatten(a,b, +[nil, deref=true, seen=false])
Note the "+"
This starts with the default set of options and adds options.
The same true/false rules apply. (So using =false this can also remove options)

Code: Text  [Select][+][-]
  1. flatten(a,b, -[nil, deref=true, seen=false])
Note the "-"
This starts with the default set of options and removes options.
- an option without modifier is removed
- an option with =true is removed
- an option with =false  is ADDED

The last one is because the "-" reverses all operations.
Comments?

There would be an alternative to interpret the "-":
Only modify the meaning of an identifier without =true/false.
I.e., normally just "nil" translates to "nil=true", but with a "-" it translates to "nil=false".

And then explicit true/false would not be inverted by the "-".

Thoughts?


440bx

  • Hero Member
  • *****
  • Posts: 4731
Re: "Flatten" linked-lists or trees in Watches of debugger
« Reply #14 on: July 26, 2024, 11:46:10 pm »
I presume these new capabilities require today's trunk version.

You're right that the way I suggested works best when the number of branches may be numerous but the tree is shallow.  If the tree isn't shallow (reasonably, about a practical max depth of 16) then the output is somewhat unwieldy.  It's nice for data indexed using B-Trees (depth kept low by using a larger - greater than 2 - number of branches.)

Going to download it and give it a try.

Thank you for the work you put into it.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

 

TinyPortal © 2005-2018