Gert Gottschalk
Gert Gottschalk

Reputation: 1716

tcl lsort for last n characters in the string

Looking for a way to lsort a list of strings by the n last characters.

Desired outcome:

lsort -lastchars 3 {123xyz 456uvw 789abc}
789abc 456uvw 123xyz 

My fall back position would be to use -command option and write my proc discarding all but the last 3 characters.

Thanks, Gert

Upvotes: 2

Views: 1016

Answers (2)

Donal Fellows
Donal Fellows

Reputation: 137587

A fast way to do this is to compute a collation key and to sort on that. A collation key is just a string that sorts in the order that you want; you package them up with the real values to sort and sort together.

set yourList {123xyz 456uvw 789abc}
set withCKs {}
foreach value $yourList {
    lappend withCKs [list $value [string range $value end-2 end]]
}
set sorted {}
foreach pair [lsort -index 1 $withCKs] {
    lappend sorted [lindex $pair 0]
}

This can be made more elegant in Tcl 8.6:

set sorted [lmap pair [lsort -index 1 [lmap val $yourList {list $val [string range $val end-2 end]}]] {lindex $pair 0}]

Splitting up the one-liner for clarity:

# Add in the collation keys
set withCKs [lmap val $yourList {list $val [string range $val end-2 end]}]
# Sort by the collation keys and then strip them from the result list
set sorted [lmap pair [lsort -index 1 $withCKs] {lindex $pair 0}]

A different approach is to produce the collation keys in a separate list and then to get lsort to spit out the indices it produces when sorting.

set CKs [lmap val $yourList {string range $val end-2 end}]
set sorted [lmap idx [lsort -indices $CKs] {lindex $yourList $idx}]

As a one-liner:

set sorted [lmap idx [lsort -indices [lmap val $yourList {string range $val end-2 end}]] {lindex $yourList $idx}]

For Tcl 8.5 (there's no -indices option in 8.4 or before):

set CKs [set sorted {}]
foreach val $yourList {
    lappend CKs [string range $val end-2 end]
}
foreach idx [lsort -indices $CKs] {
    lappend sorted [lindex $yourList $idx]
}

(The foreach/lappend pattern is precisely what lmap improves on in 8.6.)

Upvotes: 3

patthoyts
patthoyts

Reputation: 33203

Your fallback idea is the way to achieve this.

proc f {lhs rhs} {
    return [string compare [string range $lhs end-2 end] \
        [string range $rhs end-2 end]]
}
lsort -command f {123xyz 456uvw 789abc}

returns

789abc 456uvw 123xyz

Upvotes: 2

Related Questions