sortingtcltclsh

order two list with the same indices


I am new to tcl. I need to sort a list of values and save indices. I have 2 lists and I want to sort listA, but then I want to order listB preserving the indices form listA.

For example:

set listA {5 6 7 3 4 7 8 9}
set listB {0 1 2 3 4 5 6 7}

set sort_listA [lsort $listA]

Now sort_listA will be 3 4 5 6 7 7 8 9.

I want to sort listB list keeping the same indices of sort_listA like:

3 4 0 1 2 5 6 7

In other words, I need to sort both lists with the sorting of listA. Can anyone help me?


Solution

  • This is exactly the sort of task for which lsort has the -indices option (requires 8.5 or later). Instead of the list of sorted values, it returns the list of indices into the original list in the order that that you would retrieve them to get a sorted list, and that's perfect for your task. This interactive session (in Tcl 8.6 so I have lmap) is indicative:

    % set listA {5 6 7 3 4 7 8 9}
    5 6 7 3 4 7 8 9
    % set listB {0 1 2 3 4 5 6 7}
    0 1 2 3 4 5 6 7
    % set idxs [lsort -indices $listA]
    3 4 0 1 2 5 6 7
    % lmap i $idxs {lindex $listA $i}
    3 4 5 6 7 7 8 9
    % lmap i $idxs {lindex $listB $i}
    3 4 0 1 2 5 6 7
    

    If you're still on 8.5, you can use foreach to do the remapping; it's easier with a procedure:

    proc mapindices {indexlist valuelist} {
        set result {}
        foreach i $indexlist {
            lappend result [lindex $valuelist $i]
        }
        return $result
    }
    set listA {5 6 7 3 4 7 8 9}
    set listB {0 1 2 3 4 5 6 7}
    puts [mapindices [lsort -indices $listA] $listB]
    

    Now, in 8.4 (NO LONGER SUPPORTED!) there's no indices option so you need to do more work.

    proc lsortIndices {list} {
        # Pair each value up with its index
        set zipped {}
        set idx -1
        foreach val $list {
            lappend zipped [list [incr idx] $val]
        }
    
        # Do the sorting by the value of the pair
        set sorted [lsort -index 1 $zipped]
    
        # Unpack the indices from the pairs to get the result
        set result {}
        foreach pair $sorted {
            lappend result [lindex $pair 0]
        }
        return $result
    }
    

    However, at that point you'd probably just zip the two lists together and work more directly:

    set zipped {}
    foreach valA $listA valB $listB {
        lappend zipped [list $valA $valB]
    }
    
    set sorted [lsort -index 0 $zipped]
    
    set listAsorted {}
    set listBsorted {}
    foreach pair $sorted {
        lappend listAsorted [lindex $pair 0]
        lappend listBsorted [lindex $pair 1]
    }
    

    Working with older versions of Tcl than 8.4 would require you to use the -command option and that's really slow.