Reputation: 1716
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
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
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