• element-wise union of lists

    From Eduard Zozulya@21:1/5 to All on Fri Oct 13 05:00:23 2023
    Need union two list as
    set l1 "1 2 3"
    set l2 "a s d"

    Result list as "1 a 2 s 3 d"

    This procedure
    proc UnionList { l1 l2 } {
    foreach e1 $l1 e2 $l2 {
    lappend lret $e1 $e2
    }
    return $lret
    }
    working very slowly because lists l1 and l2 very big (100000 elements)

    How to optimize list merging?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Eduard Zozulya@21:1/5 to All on Fri Oct 13 06:10:08 2023
    пʼятниця, 13 жовтня 2023 р. о 15:52:12 UTC+3 Helmut Giese пише:
    Eduard Zozulya <[email protected]> schrieb:
    Need union two list as
    set l1 "1 2 3"
    set l2 "a s d"

    Result list as "1 a 2 s 3 d"

    This procedure
    proc UnionList { l1 l2 } {
    foreach e1 $l1 e2 $l2 {
    lappend lret $e1 $e2
    }
    return $lret
    }
    working very slowly because lists l1 and l2 very big (100000 elements)

    How to optimize list merging?
    Try a dict?
    set d [dict create]
    foreach e1 $l1 e2 $l2 {
    dict set d $e1 $e2
    }
    HTH
    Helmut
    A little faster.
    Thank you!

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Helmut Giese@21:1/5 to Eduard Zozulya on Fri Oct 13 14:52:07 2023
    Eduard Zozulya <[email protected]> schrieb:

    Need union two list as
    set l1 "1 2 3"
    set l2 "a s d"

    Result list as "1 a 2 s 3 d"

    This procedure
    proc UnionList { l1 l2 } {
    foreach e1 $l1 e2 $l2 {
    lappend lret $e1 $e2
    }
    return $lret
    }
    working very slowly because lists l1 and l2 very big (100000 elements)

    How to optimize list merging?
    Try a dict?
    set d [dict create]
    foreach e1 $l1 e2 $l2 {
    dict set d $e1 $e2
    }
    HTH
    Helmut

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rich@21:1/5 to Eduard Zozulya on Fri Oct 13 15:14:00 2023
    Eduard Zozulya <[email protected]> wrote:
    Need union two list as
    set l1 "1 2 3"
    set l2 "a s d"

    Result list as "1 a 2 s 3 d"

    This procedure
    proc UnionList { l1 l2 } {
    foreach e1 $l1 e2 $l2 {
    lappend lret $e1 $e2
    }
    return $lret
    }
    working very slowly because lists l1 and l2 very big (100000 elements)

    How to optimize list merging?

    The common name for this is "zipping" lists (see https://wiki.tcl-lang.org/page/lzip) and unfortunately, no matter how
    hard you try to 'hide' the fact, ultimately any abstraction will expand
    into iterating over both lists one element at a time and creating a
    result from each element one pair of elements at a time.

    There simply is not any other way to perform the operation.

    The only speedup possibility might be to code a C extension to actually
    perform the zip, which removes Tcl interpreter overhead, but it would
    still have to iterate one by one building up the result, so there is a
    lower bound below which the operation simply will not reach performace
    wise.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Andreas Leitgeb@21:1/5 to Rich on Fri Oct 13 16:27:28 2023
    Rich <[email protected]d> wrote:
    Eduard Zozulya <[email protected]> wrote:
    How to optimize list merging?
    The only speedup possibility might be to code a C extension to actually perform the zip, which removes Tcl interpreter overhead, but it would
    still have to iterate one by one building up the result, so there is a
    lower bound below which the operation simply will not reach performace
    wise.

    according to another followup in this thread, building a dict gave a
    slight improvement over building a list.

    There might be two reasons for that:

    - the list originally created was used as a dict afterwards,
    so building a dict in first place is of course better,
    than creating a list and having it converted to dict.

    - maybe the first list contains certain elements a multiple times,
    e.g. list1 is {a a a a}, then the dict created would only
    contain key "a" with last value of second list. That may
    or may not be OP's intention.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Schelte@21:1/5 to Eduard Zozulya on Fri Oct 13 18:26:28 2023
    On 13/10/2023 15:10, Eduard Zozulya wrote:
    пʼятниця, 13 жовтня 2023 р. о 15:52:12 UTC+3 Helmut Giese пише:
    Eduard Zozulya <[email protected]> schrieb:
    How to optimize list merging?
    Try a dict?
    set d [dict create]
    foreach e1 $l1 e2 $l2 {
    dict set d $e1 $e2
    }
    HTH
    Helmut
    A little faster.
    Thank you!

    In my testing, the dict version takes about twice as long as the
    original version. It also doesn't produce the same results if there are duplicates in l1. That may or may not be a problem.

    One factor that slows down the original is that lret needs to be
    reallocated every time it runs out of space when elements are added. A
    possible optimization is to allocate the needed number of elements in
    advance:

    proc UnionList {l1 l2} {
    set lret [lrepeat [expr {[llength $l1] * 2}] {}]
    set i -1
    foreach e1 $l1 e2 $l2 {
    lset lret [incr i] $e1
    lset lret [incr i] $e2
    }
    return $lret
    }

    I get a slight speed increase (about 5%) on two lists of 100000 elements
    that way.


    Schelte.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Eduard Zozulya@21:1/5 to All on Sat Oct 14 00:20:38 2023
    пʼятниця, 13 жовтня 2023 р. о 19:27:56 UTC+3 Andreas Leitgeb пише:
    Rich <[email protected]d> wrote:
    Eduard Zozulya <[email protected]> wrote:
    How to optimize list merging?
    The only speedup possibility might be to code a C extension to actually perform the zip, which removes Tcl interpreter overhead, but it would still have to iterate one by one building up the result, so there is a lower bound below which the operation simply will not reach performace wise.
    according to another followup in this thread, building a dict gave a
    slight improvement over building a list.

    There might be two reasons for that:

    - the list originally created was used as a dict afterwards,
    so building a dict in first place is of course better,
    than creating a list and having it converted to dict.

    - maybe the first list contains certain elements a multiple times,
    e.g. list1 is {a a a a}, then the dict created would only
    contain key "a" with last value of second list. That may
    or may not be OP's intention.

    Mergin based on lappend or dict almost equal.
    Sometimes, depending on the data in the lists, the version on the base lappend is ahead, and sometimes on the base dict.
    .
    I am tried another:
    proc UnionList { l1 l2 } {
    return [join [lmap e1 $l1 e2 $l2 { list $e1 $e2 }]]
    }

    it is 30 percent faster without "join", but with "join" - it is almost twice as slow.
    Therefore, if it is necessary to get the lists of the type
    {1 a} {2 s} {3 d}
    then the last variant based on lmap is the best solution.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mole Cool@21:1/5 to All on Sat Oct 14 02:42:19 2023
    It is interesting, I though call by name/ref would be faster, because you don't need to copy so much data in memory, but it was only faster for small strings. And lset is comparable slow.

    time0 {8733.72 microseconds per iteration}
    time1 {9645.48 microseconds per iteration}
    time2 {9336.58 microseconds per iteration}
    time3 {9219.38 microseconds per iteration}
    time4 {11611.38 microseconds per iteration}
    time5 {15875.34 microseconds per iteration}

    proc Task {} {
    set n 100000
    set t 50

    set time0 [time {Test0 $n} $t]
    set time1 [time {Test1 $n} $t]
    set time2 [time {Test2 $n} $t]
    set time3 [time {Test3 $n} $t]
    set time4 [time {Test4 $n} $t]
    set time5 [time {Test5 $n} $t]
    }
    # std call by value
    proc Test0 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [LMergeByVal0 $L1 $L2]
    }
    proc LMergeByVal0 {L1 L2} {
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }
    # in call by ref
    proc Test1 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [LMergeByRef1 L1 L2]
    }

    proc LMergeByRef1 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }

    # all by ref
    proc Test2 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set Lr [list]
    LMergeByRef2 L1 L2 LR

    }
    proc LMergeByRef2 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }

    # result by ref
    proc Test3 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [list]
    LMergeByRef3 $L1 $L2 LR
    }

    proc LMergeByRef3 {L1 L2 cbrRes} {
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }
    # by ref and index
    proc Test4 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [list]
    LMergeByRef4 L1 L2 LR
    }

    proc LMergeByRef4 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    set LR [list]
    set n [llength $L1]
    for {set i 0} {$i < $n} {incr i} {
    lappend LR [lindex $L1 $i] [lindex $L2 $i]
    }
    return $LR
    }
    # use lmap
    proc Test5 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [LMergeByRef5 L1 L2]
    }
    proc LMergeByRef5 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    return [lmap a $L1 b $L2 {list $a $b}]
    }

    proc Get_List1 {n} {
    return [lrepeat $n {3 2 5 1 9 4} ]
    }

    proc Get_List2 {n} {
    return [lrepeat $n {A B C d e F} ]
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Helmut Giese@21:1/5 to All on Sat Oct 14 17:48:40 2023
    Hi Mole,
    my 'dict'version doesn't seem too bad
    Taking your example and adding thisversion like so
    proc Test6 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set d [dict create]
    foreach e1 $L1 e2 $L2 {
    dict set d $e1 $e2
    }
    set d
    }
    (and of course adding it to 'Task') I got the following results on my
    machine:
    time0 15875.12 microseconds per iteration
    time1 15884.836000000001 microseconds per iteration
    time2 15795.996000000001 microseconds per iteration
    time3 15926.432000000003 microseconds per iteration
    time4 22394.828 microseconds per iteration
    time5 15459.424 microseconds per iteration
    time6 6233.11 microseconds per iteration

    Interesting result. Have a nice weekend
    Helmut

    It is interesting, I though call by name/ref would be faster, because you don't need to copy so much data in memory, but it was only faster for small strings. And lset is comparable slow.

    time0 {8733.72 microseconds per iteration}
    time1 {9645.48 microseconds per iteration}
    time2 {9336.58 microseconds per iteration}
    time3 {9219.38 microseconds per iteration}
    time4 {11611.38 microseconds per iteration}
    time5 {15875.34 microseconds per iteration}

    proc Task {} {
    set n 100000
    set t 50

    set time0 [time {Test0 $n} $t]
    set time1 [time {Test1 $n} $t]
    set time2 [time {Test2 $n} $t]
    set time3 [time {Test3 $n} $t]
    set time4 [time {Test4 $n} $t]
    set time5 [time {Test5 $n} $t]
    }
    # std call by value
    proc Test0 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [LMergeByVal0 $L1 $L2]
    }
    proc LMergeByVal0 {L1 L2} {
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }
    # in call by ref
    proc Test1 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [LMergeByRef1 L1 L2]
    }

    proc LMergeByRef1 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }

    # all by ref
    proc Test2 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set Lr [list]
    LMergeByRef2 L1 L2 LR

    }
    proc LMergeByRef2 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }

    # result by ref
    proc Test3 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [list]
    LMergeByRef3 $L1 $L2 LR
    }

    proc LMergeByRef3 {L1 L2 cbrRes} {
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }
    # by ref and index
    proc Test4 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [list]
    LMergeByRef4 L1 L2 LR
    }

    proc LMergeByRef4 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    set LR [list]
    set n [llength $L1]
    for {set i 0} {$i < $n} {incr i} {
    lappend LR [lindex $L1 $i] [lindex $L2 $i]
    }
    return $LR
    }
    # use lmap
    proc Test5 {n} {
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set LR [LMergeByRef5 L1 L2]
    }
    proc LMergeByRef5 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    return [lmap a $L1 b $L2 {list $a $b}]
    }

    proc Get_List1 {n} {
    return [lrepeat $n {3 2 5 1 9 4} ]
    }

    proc Get_List2 {n} {
    return [lrepeat $n {A B C d e F} ]
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From heinrichmartin@21:1/5 to Rich on Sat Oct 14 13:49:46 2023
    On Friday, October 13, 2023 at 5:14:04 PM UTC+2, Rich wrote:
    Eduard Zozulya wrote:
    Need union two list as
    set l1 "1 2 3"
    set l2 "a s d"

    Result list as "1 a 2 s 3 d"

    This procedure
    proc UnionList { l1 l2 } {
    foreach e1 $l1 e2 $l2 {
    lappend lret $e1 $e2
    }
    return $lret
    }
    working very slowly because lists l1 and l2 very big (100000 elements)

    How to optimize list merging?
    The common name for this is "zipping" lists (see https://wiki.tcl-lang.org/page/lzip) and unfortunately, no matter how
    hard you try to 'hide' the fact, ultimately any abstraction will expand
    into iterating over both lists one element at a time and creating a
    result from each element one pair of elements at a time.

    There simply is not any other way to perform the operation.

    The only speedup possibility might be to code a C extension to actually perform the zip, which removes Tcl interpreter overhead, but it would
    still have to iterate one by one building up the result, so there is a
    lower bound below which the operation simply will not reach performace
    wise.

    That might be fun. A few constraints should help, e.g. if the original lists won't be used after the merge, then one could keep the reference count of their members. (Imagine how Eduard might be incrementing all counters just to decrement them when
    abandoning the original lists afterwards.)

    https://core.tcl-lang.org/tips/doc/trunk/tip/625.md should be a good read on the internals of lists in general.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From briang@21:1/5 to heinrichmartin on Sat Oct 14 17:01:01 2023
    On Saturday, October 14, 2023 at 1:49:49 PM UTC-7, heinrichmartin wrote:
    On Friday, October 13, 2023 at 5:14:04 PM UTC+2, Rich wrote:
    Eduard Zozulya wrote:
    Need union two list as
    set l1 "1 2 3"
    set l2 "a s d"

    Result list as "1 a 2 s 3 d"

    This procedure
    proc UnionList { l1 l2 } {
    foreach e1 $l1 e2 $l2 {
    lappend lret $e1 $e2
    }
    return $lret
    }
    working very slowly because lists l1 and l2 very big (100000 elements)

    How to optimize list merging?
    The common name for this is "zipping" lists (see https://wiki.tcl-lang.org/page/lzip) and unfortunately, no matter how
    hard you try to 'hide' the fact, ultimately any abstraction will expand into iterating over both lists one element at a time and creating a
    result from each element one pair of elements at a time.

    There simply is not any other way to perform the operation.

    The only speedup possibility might be to code a C extension to actually perform the zip, which removes Tcl interpreter overhead, but it would still have to iterate one by one building up the result, so there is a lower bound below which the operation simply will not reach performace wise.
    That might be fun. A few constraints should help, e.g. if the original lists won't be used after the merge, then one could keep the reference count of their members. (Imagine how Eduard might be incrementing all counters just to decrement them when
    abandoning the original lists afterwards.)

    https://core.tcl-lang.org/tips/doc/trunk/tip/625.md should be a good read on the internals of lists in general.

    This would be an excellent candidate for a Tcl 9 Abstract List. The AL would simply hold a reference to the 2 original lists, and then index into the the even or odd list as needed. The initial construction would have a constant creation time (
    practically zero), regardless of list length. The string creation would be the same cost as with a traditional list. There are other optimizations possible for the various other list operations.

    -Brian

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Mole Cool@21:1/5 to All on Sun Oct 15 07:28:01 2023
    Sorry Helmut, your result was unfair :-) , because you may have worked with one index only. And if you have no idea what data you get, this may dangerous. List creation is now out, done in 'Task', only with call by values, and sorry dict is not the
    fastest :-), but it is very fast for few indicies. And you did it in one proc, this was not fair either :-)

    And as mentioned it seems to be 30% slower

    Now the start scenario is the same for each test, I hope :-)

    time1 {10133.08 microseconds per iteration}
    time2 {10808.5 microseconds per iteration}
    time3 {10865.56 microseconds per iteration}
    time4 {13078.14 microseconds per iteration}
    time5 {20947.82 microseconds per iteration}
    time6 {20225.42 microseconds per iteration}
    time7 {13771.64 microseconds per iteration}

    proc Task {} {
    set n 100000
    set t 50

    # L1 has now uniquid indicines
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]

    set time0 [time {ZIP_Main0 $L1 $L2} $t]
    set time1 [time {ZIP_Main1 $L1 $L2} $t]
    set time2 [time {ZIP_Main2 $L1 $L2} $t]
    set time3 [time {ZIP_Main3 $L1 $L2} $t]
    set time4 [time {ZIP_Main4 $L1 $L2} $t]
    set time5 [time {ZIP_Main5 $L1 $L2} $t]
    set time6 [time {ZIP_Main6 $L1 $L2} $t]
    set time7 [time {ZIP_Main7 $L1 $L2} $t]

    }

    # std call by value
    proc ZIP_Main0 {L1 L2} {
    set LR [ZIP_Action0 $L1 $L2]
    }

    proc ZIP_Action0 {L1 L2} {
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }

    # in args by ref
    proc ZIP_Main1 {L1 L2} {
    set LR [ZIP_Action1 L1 L2]
    }


    proc ZIP_Action1 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }

    # all by ref
    proc ZIP_Main2 {L1 L2} {
    set LR [list]
    ZIP_Action2 L1 L2 LR
    }

    proc ZIP_Action2 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # result by ref
    proc ZIP_Main3 {L1 L2} {
    set LR [list]
    ZIP_Action3 $L1 $L2 LR
    }


    proc ZIP_Action3 {L1 L2 cbrRes} {
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }

    # # by ref and index
    proc ZIP_Main4 {L1 L2} {
    set LR [list]
    ZIP_Action4 L1 L2 LR
    }

    proc ZIP_Action4 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    set LR [list]
    set n [llength $L1]
    for {set i 0} {$i < $n} {incr i} {
    lappend LR [lindex $L1 $i] [lindex $L2 $i]
    }
    return $LR
    }
    # use lmap by val
    proc ZIP_Main5 {L1 L2} {
    set LR [ZIP_Action5 $L1 $L2]
    }

    proc ZIP_Action5 {L1 L2} {
    return [lmap a $L1 b $L2 {list $a $b}]
    }

    # use lmap by ref
    proc ZIP_Main6 {L1 L2} {
    set LR [ZIP_Action6 L1 L2]
    }

    proc ZIP_Action6 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    return [lmap a $L1 b $L2 {list $a $b}]
    }


    # use dict with unique ID's in L1
    # use lmap by ref
    proc ZIP_Main7 {L1 L2} {
    set LR [ZIP_Action7 $L1 $L2]
    }

    proc ZIP_Action7 {L1 L2} {
    set d [dict create]
    foreach I1 $L1 I2 $L2 {
    dict set d $I1 $I2
    }
    return [set d]
    }

    proc Get_List1 {n} {
    set bl [list]
    for {set i 0} {$i < $n} {incr i} {
    lappend bl $i
    }
    return $bl
    }

    proc Get_List2 {n} {
    return [lrepeat $n {A B C d e F} ]
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From briang@21:1/5 to Mole Cool on Tue Oct 17 21:04:14 2023
    On Sunday, October 15, 2023 at 7:28:04 AM UTC-7, Mole Cool wrote:
    Sorry Helmut, your result was unfair :-) , because you may have worked with one index only. And if you have no idea what data you get, this may dangerous. List creation is now out, done in 'Task', only with call by values, and sorry dict is not the
    fastest :-), but it is very fast for few indicies. And you did it in one proc, this was not fair either :-)

    And as mentioned it seems to be 30% slower

    Now the start scenario is the same for each test, I hope :-)

    time1 {10133.08 microseconds per iteration}
    time2 {10808.5 microseconds per iteration}
    time3 {10865.56 microseconds per iteration}
    time4 {13078.14 microseconds per iteration}
    time5 {20947.82 microseconds per iteration}
    time6 {20225.42 microseconds per iteration}
    time7 {13771.64 microseconds per iteration}
    proc Task {} {
    set n 100000
    set t 50
    # L1 has now uniquid indicines
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set time0 [time {ZIP_Main0 $L1 $L2} $t]
    set time1 [time {ZIP_Main1 $L1 $L2} $t]
    set time2 [time {ZIP_Main2 $L1 $L2} $t]
    set time3 [time {ZIP_Main3 $L1 $L2} $t]
    set time4 [time {ZIP_Main4 $L1 $L2} $t]
    set time5 [time {ZIP_Main5 $L1 $L2} $t]
    set time6 [time {ZIP_Main6 $L1 $L2} $t]
    set time7 [time {ZIP_Main7 $L1 $L2} $t]
    }

    # std call by value
    proc ZIP_Main0 {L1 L2} {
    set LR [ZIP_Action0 $L1 $L2]
    }

    proc ZIP_Action0 {L1 L2} {
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }
    # in args by ref
    proc ZIP_Main1 {L1 L2} {
    set LR [ZIP_Action1 L1 L2]
    }


    proc ZIP_Action1 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }

    # all by ref
    proc ZIP_Main2 {L1 L2} {
    set LR [list]
    ZIP_Action2 L1 L2 LR
    }

    proc ZIP_Action2 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # result by ref
    proc ZIP_Main3 {L1 L2} {
    set LR [list]
    ZIP_Action3 $L1 $L2 LR
    }


    proc ZIP_Action3 {L1 L2 cbrRes} {
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # # by ref and index
    proc ZIP_Main4 {L1 L2} {
    set LR [list]
    ZIP_Action4 L1 L2 LR
    }

    proc ZIP_Action4 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    set LR [list]
    set n [llength $L1]
    for {set i 0} {$i < $n} {incr i} {
    lappend LR [lindex $L1 $i] [lindex $L2 $i]
    }
    return $LR
    }
    # use lmap by val
    proc ZIP_Main5 {L1 L2} {
    set LR [ZIP_Action5 $L1 $L2]
    }

    proc ZIP_Action5 {L1 L2} {
    return [lmap a $L1 b $L2 {list $a $b}]
    }
    # use lmap by ref
    proc ZIP_Main6 {L1 L2} {
    set LR [ZIP_Action6 L1 L2]
    }

    proc ZIP_Action6 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    return [lmap a $L1 b $L2 {list $a $b}]
    }
    # use dict with unique ID's in L1
    # use lmap by ref
    proc ZIP_Main7 {L1 L2} {
    set LR [ZIP_Action7 $L1 $L2]
    }

    proc ZIP_Action7 {L1 L2} {
    set d [dict create]
    foreach I1 $L1 I2 $L2 {
    dict set d $I1 $I2
    }
    return [set d]
    }

    proc Get_List1 {n} {
    set bl [list]
    for {set i 0} {$i < $n} {incr i} {
    lappend bl $i
    }
    return $bl
    }

    proc Get_List2 {n} {
    return [lrepeat $n {A B C d e F} ]
    }

    I've implemented "ZIP" as an abstract list. It is implemented as the command [lweave] which takes 1 or more lists as arguments and returns the zippered (or weaved) list.

    Added the following test procs:

    package require lweave
    # use lweave
    proc ZIP_Main8 {L1 L2} {
    set LR [ZIP_Action8 $L1 $L2]
    }

    proc ZIP_Action8 {L1 L2} {
    lweave $L1 $L2
    }

    proc ZipCheck {0 1 zip} {
    set i 0
    set errors 0
    foreach a $0 b $1 {
    if {$a ne [lindex $zip $i]} {
    puts "Mismatch($i) $a ne [lindex $zip $i]"
    incr errors
    }
    incr i
    if {$b ne [lindex $zip $i]} {
    puts "Mismatch($i) $b ne [lindex $zip $i]"
    incr errors
    }
    incr i
    }
    puts "Errors=$errors"
    }

    And here are the results. (Note: I'm using a virtual machine running on an older laptop, so the times are about double+ those reported above.)

    $ tclsh9.0 weave_test.tcl
    Start Task
    Errors=0
    Errors=0
    timeL1->19785 microseconds per iteration
    timeL2->1034 microseconds per iteration
    time0->31293.82 microseconds per iteration
    time1->32768.96 microseconds per iteration
    time2->31919.48 microseconds per iteration
    time3->31927.18 microseconds per iteration
    time4->40238.34 microseconds per iteration
    time5->34435.24 microseconds per iteration
    time6->34532.44 microseconds per iteration
    time7->52551.18 microseconds per iteration
    time8->2.88 microseconds per iteration

    I will check in the implementation to github in the next couple days, then post a link here.
    -Brian

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From briang@21:1/5 to briang on Wed Oct 18 17:58:30 2023
    On Tuesday, October 17, 2023 at 9:04:18 PM UTC-7, briang wrote:
    On Sunday, October 15, 2023 at 7:28:04 AM UTC-7, Mole Cool wrote:
    Sorry Helmut, your result was unfair :-) , because you may have worked with one index only. And if you have no idea what data you get, this may dangerous. List creation is now out, done in 'Task', only with call by values, and sorry dict is not the
    fastest :-), but it is very fast for few indicies. And you did it in one proc, this was not fair either :-)

    And as mentioned it seems to be 30% slower

    Now the start scenario is the same for each test, I hope :-)

    time1 {10133.08 microseconds per iteration}
    time2 {10808.5 microseconds per iteration}
    time3 {10865.56 microseconds per iteration}
    time4 {13078.14 microseconds per iteration}
    time5 {20947.82 microseconds per iteration}
    time6 {20225.42 microseconds per iteration}
    time7 {13771.64 microseconds per iteration}
    proc Task {} {
    set n 100000
    set t 50
    # L1 has now uniquid indicines
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set time0 [time {ZIP_Main0 $L1 $L2} $t]
    set time1 [time {ZIP_Main1 $L1 $L2} $t]
    set time2 [time {ZIP_Main2 $L1 $L2} $t]
    set time3 [time {ZIP_Main3 $L1 $L2} $t]
    set time4 [time {ZIP_Main4 $L1 $L2} $t]
    set time5 [time {ZIP_Main5 $L1 $L2} $t]
    set time6 [time {ZIP_Main6 $L1 $L2} $t]
    set time7 [time {ZIP_Main7 $L1 $L2} $t]
    }

    # std call by value
    proc ZIP_Main0 {L1 L2} {
    set LR [ZIP_Action0 $L1 $L2]
    }

    proc ZIP_Action0 {L1 L2} {
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }
    # in args by ref
    proc ZIP_Main1 {L1 L2} {
    set LR [ZIP_Action1 L1 L2]
    }


    proc ZIP_Action1 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }

    # all by ref
    proc ZIP_Main2 {L1 L2} {
    set LR [list]
    ZIP_Action2 L1 L2 LR
    }

    proc ZIP_Action2 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # result by ref
    proc ZIP_Main3 {L1 L2} {
    set LR [list]
    ZIP_Action3 $L1 $L2 LR
    }


    proc ZIP_Action3 {L1 L2 cbrRes} {
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # # by ref and index
    proc ZIP_Main4 {L1 L2} {
    set LR [list]
    ZIP_Action4 L1 L2 LR
    }

    proc ZIP_Action4 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    set LR [list]
    set n [llength $L1]
    for {set i 0} {$i < $n} {incr i} {
    lappend LR [lindex $L1 $i] [lindex $L2 $i]
    }
    return $LR
    }
    # use lmap by val
    proc ZIP_Main5 {L1 L2} {
    set LR [ZIP_Action5 $L1 $L2]
    }

    proc ZIP_Action5 {L1 L2} {
    return [lmap a $L1 b $L2 {list $a $b}]
    }
    # use lmap by ref
    proc ZIP_Main6 {L1 L2} {
    set LR [ZIP_Action6 L1 L2]
    }

    proc ZIP_Action6 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    return [lmap a $L1 b $L2 {list $a $b}]
    }
    # use dict with unique ID's in L1
    # use lmap by ref
    proc ZIP_Main7 {L1 L2} {
    set LR [ZIP_Action7 $L1 $L2]
    }

    proc ZIP_Action7 {L1 L2} {
    set d [dict create]
    foreach I1 $L1 I2 $L2 {
    dict set d $I1 $I2
    }
    return [set d]
    }

    proc Get_List1 {n} {
    set bl [list]
    for {set i 0} {$i < $n} {incr i} {
    lappend bl $i
    }
    return $bl
    }

    proc Get_List2 {n} {
    return [lrepeat $n {A B C d e F} ]
    }
    I've implemented "ZIP" as an abstract list. It is implemented as the command [lweave] which takes 1 or more lists as arguments and returns the zippered (or weaved) list.

    Added the following test procs:

    package require lweave
    # use lweave
    proc ZIP_Main8 {L1 L2} {
    set LR [ZIP_Action8 $L1 $L2]
    }

    proc ZIP_Action8 {L1 L2} {
    lweave $L1 $L2
    }

    proc ZipCheck {0 1 zip} {
    set i 0
    set errors 0
    foreach a $0 b $1 {
    if {$a ne [lindex $zip $i]} {
    puts "Mismatch($i) $a ne [lindex $zip $i]"
    incr errors
    }
    incr i
    if {$b ne [lindex $zip $i]} {
    puts "Mismatch($i) $b ne [lindex $zip $i]"
    incr errors
    }
    incr i
    }
    puts "Errors=$errors"
    }

    And here are the results. (Note: I'm using a virtual machine running on an older laptop, so the times are about double+ those reported above.)

    $ tclsh9.0 weave_test.tcl
    Start Task
    Errors=0
    Errors=0
    timeL1->19785 microseconds per iteration
    timeL2->1034 microseconds per iteration
    time0->31293.82 microseconds per iteration
    time1->32768.96 microseconds per iteration
    time2->31919.48 microseconds per iteration
    time3->31927.18 microseconds per iteration
    time4->40238.34 microseconds per iteration
    time5->34435.24 microseconds per iteration
    time6->34532.44 microseconds per iteration
    time7->52551.18 microseconds per iteration
    time8->2.88 microseconds per iteration

    I will check in the implementation to github in the next couple days, then post a link here.
    -Brian

    The implementation of "lweave" can be found here: https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca

    Here is the comparative results of lweave. Note that although the create time for the weaved (Zipped) list is significantly faster, iterating over the list is about 4x slower. The memory footprint should be smaller though. Also note that string creation
    is expensive for such large lists.

    tclsh9.0 weave_test.tcl
    Start Task
    timeL1->21252 microseconds per iteration
    timeL2->961 microseconds per iteration
    time8->2.96 microseconds per iteration
    Z8-representation->value is a lweave with a refcount of 2, object pointer at 0x55ad8d4ee960, internal representation 0x55ad8cfbc200:0x55ad8d4ee7b0, no string representation
    dict-keys->169024 microseconds per iteration
    After-dict->value is a dict with a refcount of 2, object pointer at 0x55ad8d4ee960, internal representation 0x55ad8d02e720:0x0, string representation "0 {A B C d e ..."
    time0->32575.44 microseconds per iteration
    time1->32297.28 microseconds per iteration
    time2->32364.22 microseconds per iteration
    time3->32841.46 microseconds per iteration
    time4->41252.14 microseconds per iteration
    time5->35948.38 microseconds per iteration
    time6->35133.78 microseconds per iteration
    time7->55273.42 microseconds per iteration
    Errors=0
    Z0-check->34604 microseconds per iteration
    Errors=0
    Z8-check->142120 microseconds per iteration
    21075 microseconds per iteration

    See weave_test.tcl in the abstractlist-toys project to replay the above results.

    This implementation will only work in Tcl 9.0.

    -Brian

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From heinrichmartin@21:1/5 to briang on Thu Oct 19 04:35:59 2023
    On Thursday, October 19, 2023 at 2:58:34 AM UTC+2, briang wrote:
    On Tuesday, October 17, 2023 at 9:04:18 PM UTC-7, briang wrote:
    On Sunday, October 15, 2023 at 7:28:04 AM UTC-7, Mole Cool wrote:
    Sorry Helmut, your result was unfair :-) , because you may have worked with one index only. And if you have no idea what data you get, this may dangerous. List creation is now out, done in 'Task', only with call by values, and sorry dict is not the
    fastest :-), but it is very fast for few indicies. And you did it in one proc, this was not fair either :-)

    And as mentioned it seems to be 30% slower

    Now the start scenario is the same for each test, I hope :-)

    time1 {10133.08 microseconds per iteration}
    time2 {10808.5 microseconds per iteration}
    time3 {10865.56 microseconds per iteration}
    time4 {13078.14 microseconds per iteration}
    time5 {20947.82 microseconds per iteration}
    time6 {20225.42 microseconds per iteration}
    time7 {13771.64 microseconds per iteration}
    proc Task {} {
    set n 100000
    set t 50
    # L1 has now uniquid indicines
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set time0 [time {ZIP_Main0 $L1 $L2} $t]
    set time1 [time {ZIP_Main1 $L1 $L2} $t]
    set time2 [time {ZIP_Main2 $L1 $L2} $t]
    set time3 [time {ZIP_Main3 $L1 $L2} $t]
    set time4 [time {ZIP_Main4 $L1 $L2} $t]
    set time5 [time {ZIP_Main5 $L1 $L2} $t]
    set time6 [time {ZIP_Main6 $L1 $L2} $t]
    set time7 [time {ZIP_Main7 $L1 $L2} $t]
    }

    # std call by value
    proc ZIP_Main0 {L1 L2} {
    set LR [ZIP_Action0 $L1 $L2]
    }

    proc ZIP_Action0 {L1 L2} {
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }
    # in args by ref
    proc ZIP_Main1 {L1 L2} {
    set LR [ZIP_Action1 L1 L2]
    }


    proc ZIP_Action1 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }

    # all by ref
    proc ZIP_Main2 {L1 L2} {
    set LR [list]
    ZIP_Action2 L1 L2 LR
    }

    proc ZIP_Action2 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # result by ref
    proc ZIP_Main3 {L1 L2} {
    set LR [list]
    ZIP_Action3 $L1 $L2 LR
    }


    proc ZIP_Action3 {L1 L2 cbrRes} {
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # # by ref and index
    proc ZIP_Main4 {L1 L2} {
    set LR [list]
    ZIP_Action4 L1 L2 LR
    }

    proc ZIP_Action4 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    set LR [list]
    set n [llength $L1]
    for {set i 0} {$i < $n} {incr i} {
    lappend LR [lindex $L1 $i] [lindex $L2 $i]
    }
    return $LR
    }
    # use lmap by val
    proc ZIP_Main5 {L1 L2} {
    set LR [ZIP_Action5 $L1 $L2]
    }

    proc ZIP_Action5 {L1 L2} {
    return [lmap a $L1 b $L2 {list $a $b}]
    }
    # use lmap by ref
    proc ZIP_Main6 {L1 L2} {
    set LR [ZIP_Action6 L1 L2]
    }

    proc ZIP_Action6 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    return [lmap a $L1 b $L2 {list $a $b}]
    }
    # use dict with unique ID's in L1
    # use lmap by ref
    proc ZIP_Main7 {L1 L2} {
    set LR [ZIP_Action7 $L1 $L2]
    }

    proc ZIP_Action7 {L1 L2} {
    set d [dict create]
    foreach I1 $L1 I2 $L2 {
    dict set d $I1 $I2
    }
    return [set d]
    }

    proc Get_List1 {n} {
    set bl [list]
    for {set i 0} {$i < $n} {incr i} {
    lappend bl $i
    }
    return $bl
    }

    proc Get_List2 {n} {
    return [lrepeat $n {A B C d e F} ]
    }
    I've implemented "ZIP" as an abstract list. It is implemented as the command [lweave] which takes 1 or more lists as arguments and returns the zippered (or weaved) list.

    Added the following test procs:

    package require lweave
    # use lweave
    proc ZIP_Main8 {L1 L2} {
    set LR [ZIP_Action8 $L1 $L2]
    }

    proc ZIP_Action8 {L1 L2} {
    lweave $L1 $L2
    }

    proc ZipCheck {0 1 zip} {
    set i 0
    set errors 0
    foreach a $0 b $1 {
    if {$a ne [lindex $zip $i]} {
    puts "Mismatch($i) $a ne [lindex $zip $i]"
    incr errors
    }
    incr i
    if {$b ne [lindex $zip $i]} {
    puts "Mismatch($i) $b ne [lindex $zip $i]"
    incr errors
    }
    incr i
    }
    puts "Errors=$errors"
    }

    And here are the results. (Note: I'm using a virtual machine running on an older laptop, so the times are about double+ those reported above.)

    $ tclsh9.0 weave_test.tcl
    Start Task
    Errors=0
    Errors=0
    timeL1->19785 microseconds per iteration
    timeL2->1034 microseconds per iteration
    time0->31293.82 microseconds per iteration
    time1->32768.96 microseconds per iteration
    time2->31919.48 microseconds per iteration
    time3->31927.18 microseconds per iteration
    time4->40238.34 microseconds per iteration
    time5->34435.24 microseconds per iteration
    time6->34532.44 microseconds per iteration
    time7->52551.18 microseconds per iteration
    time8->2.88 microseconds per iteration

    I will check in the implementation to github in the next couple days, then post a link here.
    -Brian
    The implementation of "lweave" can be found here: https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca

    Here is the comparative results of lweave. Note that although the create time for the weaved (Zipped) list is significantly faster, iterating over the list is about 4x slower. The memory footprint should be smaller though. Also note that string
    creation is expensive for such large lists.

    tclsh9.0 weave_test.tcl
    Start Task
    timeL1->21252 microseconds per iteration
    timeL2->961 microseconds per iteration
    time8->2.96 microseconds per iteration
    Z8-representation->value is a lweave with a refcount of 2, object pointer at 0x55ad8d4ee960, internal representation 0x55ad8cfbc200:0x55ad8d4ee7b0, no string representation
    dict-keys->169024 microseconds per iteration
    After-dict->value is a dict with a refcount of 2, object pointer at 0x55ad8d4ee960, internal representation 0x55ad8d02e720:0x0, string representation "0 {A B C d e ..."
    time0->32575.44 microseconds per iteration
    time1->32297.28 microseconds per iteration
    time2->32364.22 microseconds per iteration
    time3->32841.46 microseconds per iteration
    time4->41252.14 microseconds per iteration
    time5->35948.38 microseconds per iteration
    time6->35133.78 microseconds per iteration
    time7->55273.42 microseconds per iteration
    Errors=0
    Z0-check->34604 microseconds per iteration
    Errors=0
    Z8-check->142120 microseconds per iteration
    21075 microseconds per iteration

    See weave_test.tcl in the abstractlist-toys project to replay the above results.

    This implementation will only work in Tcl 9.0.

    -Brian

    Cool things happen in Tcl 9.0. Looking at the code without any experience with abstract lists, I wonder:

    * If "NULL) /* in operation */"[1] is the hook for the "in" operator, then implementing it might be useful (true if "in any list" instead of iterating over the woven list).

    * Is there no "iterator" for abstract lists? I.e. would foreach call ObjIndex up to ObjLength times? But I guess taking this further would bloat the interface quite a bit: thinking of the Fibonacci example, the coroutine/iterator approach seems much more
    efficient than repeatedly calculating items.

    * Nested/multiple indices are not supported for the time being[2]. I guess that was a design decision to get work done faster - the semantics and implementation would otherwise look quite clear, aren't they?

    * I guess I know why lreverse is not implemented (the simple implementation does not account for ribbons of different sizes -> pad with empty strings before reversing?), but the same "error" seems present in lset[3]. Without possibility to test, I assume
    from the code: set w [lweave [list 0 2 4] [list 1]]; lset w 5 5; puts $w; # 0 1 2 5 4 "", but expected 0 1 2 "" 4 5

    [1] https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca#diff-49da01101cfbbe312c2e416d26cf05d2e8cf8168af8c8f98e31079a2801d4160R67
    [2] https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca#diff-49da01101cfbbe312c2e416d26cf05d2e8cf8168af8c8f98e31079a2801d4160R267
    [3] https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca#diff-49da01101cfbbe312c2e416d26cf05d2e8cf8168af8c8f98e31079a2801d4160R306

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From briang@21:1/5 to heinrichmartin on Thu Oct 19 08:40:04 2023
    On Thursday, October 19, 2023 at 4:36:02 AM UTC-7, heinrichmartin wrote:
    On Thursday, October 19, 2023 at 2:58:34 AM UTC+2, briang wrote:
    On Tuesday, October 17, 2023 at 9:04:18 PM UTC-7, briang wrote:
    On Sunday, October 15, 2023 at 7:28:04 AM UTC-7, Mole Cool wrote:
    Sorry Helmut, your result was unfair :-) , because you may have worked with one index only. And if you have no idea what data you get, this may dangerous. List creation is now out, done in 'Task', only with call by values, and sorry dict is not
    the fastest :-), but it is very fast for few indicies. And you did it in one proc, this was not fair either :-)

    And as mentioned it seems to be 30% slower

    Now the start scenario is the same for each test, I hope :-)

    time1 {10133.08 microseconds per iteration}
    time2 {10808.5 microseconds per iteration}
    time3 {10865.56 microseconds per iteration}
    time4 {13078.14 microseconds per iteration}
    time5 {20947.82 microseconds per iteration}
    time6 {20225.42 microseconds per iteration}
    time7 {13771.64 microseconds per iteration}
    proc Task {} {
    set n 100000
    set t 50
    # L1 has now uniquid indicines
    set L1 [Get_List1 $n ]
    set L2 [Get_List2 $n ]
    set time0 [time {ZIP_Main0 $L1 $L2} $t]
    set time1 [time {ZIP_Main1 $L1 $L2} $t]
    set time2 [time {ZIP_Main2 $L1 $L2} $t]
    set time3 [time {ZIP_Main3 $L1 $L2} $t]
    set time4 [time {ZIP_Main4 $L1 $L2} $t]
    set time5 [time {ZIP_Main5 $L1 $L2} $t]
    set time6 [time {ZIP_Main6 $L1 $L2} $t]
    set time7 [time {ZIP_Main7 $L1 $L2} $t]
    }

    # std call by value
    proc ZIP_Main0 {L1 L2} {
    set LR [ZIP_Action0 $L1 $L2]
    }

    proc ZIP_Action0 {L1 L2} {
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }
    # in args by ref
    proc ZIP_Main1 {L1 L2} {
    set LR [ZIP_Action1 L1 L2]
    }


    proc ZIP_Action1 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    return $LR
    }

    # all by ref
    proc ZIP_Main2 {L1 L2} {
    set LR [list]
    ZIP_Action2 L1 L2 LR
    }

    proc ZIP_Action2 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # result by ref
    proc ZIP_Main3 {L1 L2} {
    set LR [list]
    ZIP_Action3 $L1 $L2 LR
    }


    proc ZIP_Action3 {L1 L2 cbrRes} {
    upvar $cbrRes LR
    foreach I1 $L1 I2 $L2 {
    lappend LR $I1 $I2
    }
    }
    # # by ref and index
    proc ZIP_Main4 {L1 L2} {
    set LR [list]
    ZIP_Action4 L1 L2 LR
    }

    proc ZIP_Action4 {cbrL1 cbrL2 cbrRes} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    upvar $cbrRes LR
    set LR [list]
    set n [llength $L1]
    for {set i 0} {$i < $n} {incr i} {
    lappend LR [lindex $L1 $i] [lindex $L2 $i]
    }
    return $LR
    }
    # use lmap by val
    proc ZIP_Main5 {L1 L2} {
    set LR [ZIP_Action5 $L1 $L2]
    }

    proc ZIP_Action5 {L1 L2} {
    return [lmap a $L1 b $L2 {list $a $b}]
    }
    # use lmap by ref
    proc ZIP_Main6 {L1 L2} {
    set LR [ZIP_Action6 L1 L2]
    }

    proc ZIP_Action6 {cbrL1 cbrL2} {
    upvar $cbrL1 L1
    upvar $cbrL2 L2
    return [lmap a $L1 b $L2 {list $a $b}]
    }
    # use dict with unique ID's in L1
    # use lmap by ref
    proc ZIP_Main7 {L1 L2} {
    set LR [ZIP_Action7 $L1 $L2]
    }

    proc ZIP_Action7 {L1 L2} {
    set d [dict create]
    foreach I1 $L1 I2 $L2 {
    dict set d $I1 $I2
    }
    return [set d]
    }

    proc Get_List1 {n} {
    set bl [list]
    for {set i 0} {$i < $n} {incr i} {
    lappend bl $i
    }
    return $bl
    }

    proc Get_List2 {n} {
    return [lrepeat $n {A B C d e F} ]
    }
    I've implemented "ZIP" as an abstract list. It is implemented as the command [lweave] which takes 1 or more lists as arguments and returns the zippered (or weaved) list.

    Added the following test procs:

    package require lweave
    # use lweave
    proc ZIP_Main8 {L1 L2} {
    set LR [ZIP_Action8 $L1 $L2]
    }

    proc ZIP_Action8 {L1 L2} {
    lweave $L1 $L2
    }

    proc ZipCheck {0 1 zip} {
    set i 0
    set errors 0
    foreach a $0 b $1 {
    if {$a ne [lindex $zip $i]} {
    puts "Mismatch($i) $a ne [lindex $zip $i]"
    incr errors
    }
    incr i
    if {$b ne [lindex $zip $i]} {
    puts "Mismatch($i) $b ne [lindex $zip $i]"
    incr errors
    }
    incr i
    }
    puts "Errors=$errors"
    }

    And here are the results. (Note: I'm using a virtual machine running on an older laptop, so the times are about double+ those reported above.)

    $ tclsh9.0 weave_test.tcl
    Start Task
    Errors=0
    Errors=0
    timeL1->19785 microseconds per iteration
    timeL2->1034 microseconds per iteration
    time0->31293.82 microseconds per iteration
    time1->32768.96 microseconds per iteration
    time2->31919.48 microseconds per iteration
    time3->31927.18 microseconds per iteration
    time4->40238.34 microseconds per iteration
    time5->34435.24 microseconds per iteration
    time6->34532.44 microseconds per iteration
    time7->52551.18 microseconds per iteration
    time8->2.88 microseconds per iteration

    I will check in the implementation to github in the next couple days, then post a link here.
    -Brian
    The implementation of "lweave" can be found here: https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca

    Here is the comparative results of lweave. Note that although the create time for the weaved (Zipped) list is significantly faster, iterating over the list is about 4x slower. The memory footprint should be smaller though. Also note that string
    creation is expensive for such large lists.

    tclsh9.0 weave_test.tcl
    Start Task
    timeL1->21252 microseconds per iteration
    timeL2->961 microseconds per iteration
    time8->2.96 microseconds per iteration
    Z8-representation->value is a lweave with a refcount of 2, object pointer at 0x55ad8d4ee960, internal representation 0x55ad8cfbc200:0x55ad8d4ee7b0, no string representation
    dict-keys->169024 microseconds per iteration
    After-dict->value is a dict with a refcount of 2, object pointer at 0x55ad8d4ee960, internal representation 0x55ad8d02e720:0x0, string representation "0 {A B C d e ..."
    time0->32575.44 microseconds per iteration
    time1->32297.28 microseconds per iteration
    time2->32364.22 microseconds per iteration
    time3->32841.46 microseconds per iteration
    time4->41252.14 microseconds per iteration
    time5->35948.38 microseconds per iteration
    time6->35133.78 microseconds per iteration
    time7->55273.42 microseconds per iteration
    Errors=0
    Z0-check->34604 microseconds per iteration
    Errors=0
    Z8-check->142120 microseconds per iteration
    21075 microseconds per iteration

    See weave_test.tcl in the abstractlist-toys project to replay the above results.

    This implementation will only work in Tcl 9.0.

    -Brian
    Cool things happen in Tcl 9.0. Looking at the code without any experience with abstract lists, I wonder:

    Thanks for the review and questions!


    * If "NULL) /* in operation */"[1] is the hook for the "in" operator, then implementing it might be useful (true if "in any list" instead of iterating over the woven list).

    I rushed the implementation, and the "in" operator was not needed for performance testing. Yes, an implementation can be more efficient than the fallback.


    * Is there no "iterator" for abstract lists? I.e. would foreach call ObjIndex up to ObjLength times? But I guess taking this further would bloat the interface quite a bit: thinking of the Fibonacci example, the coroutine/iterator approach seems much
    more efficient than repeatedly calculating items.

    The [foreach] iterator will use GetElements method when present, otherwise, the fallback is to call ObjIndex. It is a space/time tradeoff.
    Note that the fib.c abstract list implementation uses math to compute the i'th Fibonacci number, so no iterating is needed.


    * Nested/multiple indices are not supported for the time being[2]. I guess that was a design decision to get work done faster - the semantics and implementation would otherwise look quite clear, aren't they?

    Yes, you are correct. I was rushing to get some basic performance numbers, so multiple indices was not needed for my purposes.


    * I guess I know why lreverse is not implemented (the simple implementation does not account for ribbons of different sizes -> pad with empty strings before reversing?), but the same "error" seems present in lset[3]. Without possibility to test, I
    assume from the code: set w [lweave [list 0 2 4] [list 1]]; lset w 5 5; puts $w; # 0 1 2 5 4 "", but expected 0 1 2 "" 4 5

    Yep, I did rush this part, then did not need/use lset when testing.
    As for lreverse, I believe it can be done with index math rather than having to physically reorder the elements, even when the lists are non-uniform in length.

    On the other hand, lweave needs to work with other abstract list types as well (e.g. [lweave [lseq 1 3] [lweave {a b d} {10 20 30}]]). So, using the ObjIndex by default will result in correct behavior regardless of list construction details.

    -Brian


    [1] https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca#diff-49da01101cfbbe312c2e416d26cf05d2e8cf8168af8c8f98e31079a2801d4160R67
    [2] https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca#diff-49da01101cfbbe312c2e416d26cf05d2e8cf8168af8c8f98e31079a2801d4160R267
    [3] https://github.com/bgriffinfortytwo/abstractlist-toys/commit/94b0f4addc05e8f777833629847e5dcb685375ca#diff-49da01101cfbbe312c2e416d26cf05d2e8cf8168af8c8f98e31079a2801d4160R306

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)