Version 9 of nested list

Updated 2008-11-14 06:45:11 by FM

A nested list is simply a list that occurs as an element of another list (which may of course itself be an element of another list, etc.).

Common reasons nested lists arise are:

  1. They're matrices (a list of rows, where each row is itself a list, or a list of columns where each column is itself a list).
  2. Lists are being used for what in other languages is known as structs, records, or tuples -- collections of data with a fixed structure.
  3. A tree is encoded as a list where the subtrees occur as elements (hence lists are nested as deeply as the tree is high).

[Discuss when using a nested list is important or useful - what are cases where it is a good solution]

Before Tcl 8.4, nested lists were quite difficult to work with -- getting nested elements required nesting lindex commands, and the nightmare of setting elements in nested lists without the help of lset is better forgotten -- and as a result older code often uses arrays even in cases where the first-class data status (being possible to pass by value) of a list would have been very useful.

[Discuss which Tcl built in commands can be used to build, search, and maintain the list]

There's different kinds of nested list.

Here some procs to build purely nested list which have a constant length of 2, the last member is always {} :

Purely right nested list (constant length of 2):

proc ligne args {
    set script {list}
    foreach e [lreverse $args] {
	set script [subst -noc -nob {list "{$e}" [$script]}]
    }
    uplevel $script
}

set L [ligne A B C] ;#           {{A}} {{{B}} {{{C}} {}}}
llength [lindex $L] ;#           2
llength [lindex $ L 1] ; #       2
llength [lindex $ L 1 1]; #      2
llength [lindex $ L 1 1 1]; #    0 (so depth of the list = 3)

We can test if a list is of this kind

proc estLigne L {
    set res 1
    if {[llength $L]==2} {
	if {[llength [lindex $L 0]] == 1} {
	    set res [estLigne [lindex $L 1]]
	} else {
	    set res 0
	}
    } elseif {[llength $L] != 0} {
	if {[llength $L]==1 && $L eq ""} {
	    set res 1
	} else {
	    set res 0
	}
    }
    set res
}
estLigne $L;# 1

We can look at the depth

proc largeur L {
    if {[estLigne $L]} {
	set index [list]
	set l 0
	while {[llength [lindex $L $index]] == 2} {
	    set index [linsert $index 0 "1"]
	    incr l
	}
	set l
    } elseif {[estColonne $L]} {
	set res 1
    }
}
largeur $L; # 3

Purely left nested list (constant length of 2):

proc colonne args {
    set script {list}
    foreach e [lreverse $args] {
	set script [subst -noc -nob {list [$script] "{$e}"}]
    }
    uplevel $script
}
set C [colonne A B C]; #        {{{} {{C}}} {{B}}} {{A}}
llength [lindex $C]; #          2
llength [lindex $C 0]; #        2
llength [lindex $C 0 0]; #      2
llength [lindex $C 0 0 0]; #    0 (so depth of the list = 3)
=======
We can also test if a list is of this kind
=======
proc estColonne C {
    set res 1
    if {[llength $C] == 2} {
	if {[llength [lindex $C 1]] == 1} {
	    set res [estColonne [lindex $C 0]]
	} else {
	    set res 0
	}
    } elseif {[llength $C] != 0} {
	if {[llength $C]==1 && $C eq ""} {
	    set res 1
	} else {
	    set res 0
	}
    }
    set res
}
estColonne $L; # 0
estColonne $C; # 1
=======
We can look at the depth of the left nested list

proc hauteur L {

    if {[estColonne $L]} {
        set index ""
        set h 0
        while {[llength [lindex $L {*}$index]] == 2} {
            set index [linsert $index 0 "0"]
            incr h
        }
        set h
    } elseif {[estLigne $L]} {
        set res 1
    }

} hauteur $L ;# 1 hauteur $C ;# 3

Now we should be able to search in those two kind of list (indexation to improve)

proc nlindex {L args} {

    set Res [list]
    foreach i $args {
        if {![string is digit $i]} {
            puts stderr "index $i de format non valide dans nlindex $L {*}$args"
            return
        }
    }
    if {[estLigne $L]} {
        #ligne
        set index 0
        if {[llength $args] == 0} {
            # Tout afficher
            for {set i 0} {$i < [largeur $L]} {incr i} {
                lappend Res {*}[lindex $L {*}$index]
                set index [linsert $index 0 "1"]
            }
            set Res [list $Res]
        } else {
            # Afficher la coordonnée désirée
            for {set i 0} {$i < $args} {incr i} { 
                set index [linsert $index 0 "1"]
            }
            set Res [lindex $L {*}$index]
        }
    } elseif {[estColonne $L]} {
        #colonne
        set index 1
        if {[llength $args] == 0} {
            # Tout afficher
            for {set i 0} {$i < [hauteur $L]} {incr i} {
                lappend Res {*}[lindex $L {*}$index]
                set index [linsert $index 0 "0"]
            }
            set Res [list $Res]
        } else {
            # Afficher la coordonnée désirée
            for {set i 0} {$i < $args} {incr i} { 
                set index [linsert $index 0 "0"]
            }
            set Res [lindex $L {*}$index]
        }
    } else {
        puts stderr "Objet $L non reconnu"
        return
    }
    return [lindex $Res 0]

} puts "nlindex $L" ; # A B C puts "nlindex $L 0 nlindex $L 1 nlindex $L 2" ;# A B C puts "nlindex $C"; # A B C puts "nlindex $C 0; # A # ..etc

Of course we can mix up this two kinds of nested list :
=======
set M [colonne [ligne a00 a01 a02] \
               [ligne a10 a11 a12] \
               [ligne a20 a21 a22] ]

=======
We can test if a list is of one mixed type
=======
proc estMatrice M {
    set res 1
    set dim -1
    if {[estColonne $M]} {
	for {set i 0} {$i < [hauteur $M]} {incr i} {
	    if {[estLigne [set Ligne [nlindex $M $i]]]} {
		if {$dim == -1} {
		    set dim [largeur $Ligne]
		} elseif {$dim != [largeur $Ligne]} {
#		    puts stderr "Objet non homogène en dimension : `$Ligne` est de dimension [largeur $Ligne] alors que $dim était attendu"
		    return 0
		}
	    } else {
#		puts stderr "Ligne $Ligne non valide"
		return 0
	    }
	}
    } elseif {[estLigne $M]} {
	for {set i 0} {$i < [largeur $M]} {incr i} {
	    if {[estColonne [set Colonne [nlindex $M $i]]]} {
		if {$dim == -1} {
		    set dim [hauteur $Colonne]
		} elseif {$dim != [hauteur $Colonne]} {
#		    puts stderr "Objet non homogène en dimension : `$Colonne` est de dimension [hauteur $Colonne] alors que $dim était attendu"
		    return 0
		}
	    } else {
#		 puts stderr "Colonne $Colonne non valide"
		return 0
	    }
	}
    } else {
	return 0
    }
    return "$res"
}
estMatrice $M ;# 1

We can look at the dimension of this mixed type (not complete)

proc dimension M {
    if {[estMatrice $M]} {
	if {[estColonne $M]} {
	    set dim [hauteur $M]
	    set Ligne [nlindex $M 0]
	    lappend dim [largeur $Ligne]
	} elseif {[estLigne $M]} {
	    set dim [largeur $M]
	    set Colonne [nlindex $M 0]
	    set dim [linsert $dim 0 [hauteur $Colonne]]
	} 
    } elseif {[estColonne $M]} {
	set dim [list [hauteur $M] 1]
    } elseif {[estLigne $M]} {
	set dim [list 1 [largeur $M]]
    } else {
	puts stderr "$M n'est pas une matrice"
	return 0
    }
    return $dim
}
dimension $M ; # 3 3

Searching in it this mixed type (to be completed)

proc mlindex {M {l {}} {c {}}} { 
    if {![estMatrice $M]} {return}
    lassign [dimension $M] height length
    if {$c eq {} && $l ne {}} {
	# une ligne est recherchée : Should-it be return in the nested form ?
	if {$l >= $height} {return}
	if {[estLigne $M]} {
	    for {set i 0} {$i < [largeur $M]} {incr i} {
		lappend Res [nlindex [nlindex $M $i] $l]
	    }
	    return $Res
	} elseif {[estColonne $M]} {
	    return [nlindex [nlindex $M $l]]
	}
    } elseif {$c ne {} && $l ne {}} {
	# une cellule est recherchée
	if {! ($l < $height && $c < $length) } {return}
	if {[estLigne $M]} {
	    return [nlindex [nlindex $M $c] $l]
	} elseif {[estColonne $M]} {
	    return [nlindex [nlindex $M $l] $c]
	}
    } elseif {$c ne {} && $l eq {}} {
	# une colonne est recherchée : Should-it be return in the nested form ?
	if {$c >= $length} {return}
	if {[estColonne $M]} {
	    for {set i 0} {$i < [hauteur $M]} {incr i} {
		lappend Res [nlindex [nlindex $M $i] $c]
	    }
	    return $Res
	} elseif {[estLigne $M]} {
	    return [nlindex [nlindex $M $c]]
	}
    } elseif {$c eq {} && $l eq {}} {
        # Maybe the list should flatten ?
	lindex $M
    }
}

mlindex $M 0; # a00 a01 a02
mlindex $M {} 0; # a00 a10 a20
mlindex $M 0 0; # a00

We can transpose one kind in another

proc transpose {L} {
    if {[estMatrice $L]} {
	lassign [dimension $L] h l
	lappend script ligne
	for {set i 0} {$i < $h} {incr i} {
	    lappend script "\[colonne {*}\[mlindex \$L $i\]\]"
	}
	eval $script
    } elseif {[estLigne $L]} {
	set index [list 0]
	for {set i 0} {"1"} {incr i} {	
	    if {[set e [lindex $L {*}$index]] ne {}} {
		lappend plat [join $e]
		set index [linsert $index 0 "1"]
	    } else {
		break
	    }
	    if {$i> 2000} break
	}
	colonne {*}$plat	
    } elseif {[estColonne $L]} {
	set index [list 1]
	for {set i 0} {"1"} {incr i} {	
	    if {[set e [lindex $L {*}$index]] ne {}} {
		lappend plat [join $e]
		set index [linsert $index 0 "0"]
	    } else {
		break
	    }
	    if {$i> 2000} break
	}
	ligne {*}$plat
    }
}

string eq [mlindex $M 0] [mlindex [transpose $M] {} 0]; # 1
string eq $L [transpose $C]; # 1

It's seems like if we can encode in the structure a distinction between vectors, covectors and matrice, no ?