Updated 2015-08-14 16:54:04 by AMG

WJG (16/Feb/06) A simple proc to compare the contents of two lists. It returns a list equal to the differences.
 #----------------
 # compare list a with b
 #----------------
 proc listcomp {a b} {
  set diff {}
  foreach i $a {
    if {[lsearch -exact $b $i]==-1} {
      lappend diff $i
    }
  }
  return $diff
 }

 catch {console show}
 set a {1 2 3 4 5 6}
 set b {2 4 6 8}

 puts "list a = \{$a\}\nlist b = \{$b\}\nThe difference between a and b is \{[listcomp $a $b]\}."

male - 2006-02-16: reading the title I thought about something like "string compare".

So what's about this approach of a kind of list compare lcompare:
 proc lcompare {list1 list2} {
     set length1 [llength $list1];
     set length2 [llength $list2];

     if {$length1 < $length2} {
         return [expr {double(-1)}];
     } elseif {$length1 > $length2} {
         return [expr {double(1)}];
     }

     if {[lindex $list1 0] eq $list1} {
         return [expr {double([string compare $list1 $list2])}];
     }

     set result [expr {double(0)}];

     foreach element1 $list1 element2 $list2 {
         set result [expr {$result + [lcompare $element1 $element2]}];
     }

     return [expr {$result / $length1}];
 }

WJG (16/Feb/06) male, what is this proc supposed to do?

male - 2006-02-16: sorry I didn't explain it.

lcompare returns a number between -1 and 1 to tell if the list1 is "lower" or "greater" than list2 - like in string compare.

It is about comparing each atom of a list or sub lists with string compare. The results are summed and diveded by the count of compared atoms. I assumed this as one way to do this. Perhaps this is the wrong way!

WJG (16/Feb/06) I fancied that the idea was to provide a simple decision. Running my clip above gives away the purpose. Its simply to compare one list with the other and then to return the differences.

jcw - The way the original was written, it really is sort of a "minus" or "except" set operator.

(Exercise for the reader: fix the error when this is given two identical lists)

AK Feb 16, 2006: Tcllib's struct module has a package struct::set with all kinds of set operators. The original listcomp is simply struct::set difference.

KBK 2007-11-20 The "difference between two lists" is a trifle ambiguous. If the lists do indeed represent sets of items, then struct::set difference is what you want. If the lists are, say, lines from two versions of a file, and you want to find the difference the way the diff command does, then you're probably after struct::list longestCommonSubsequence (The struct::list lcsInvert call repackages the results of longestCommonSubsequence in a more convenient form.) See also diff in Tcl.

See also: Additional list functions

applemcg - 2012-02-11 17:00:52

listcomp should be named something like lAlessB. what is the math term, "commutative"? this function removes "b" from "a".

HaO 2014-09-16: I often have the issue to quickly check two lists if they are equaln in the sense that:

  • Same list length
  • All elements equal

As a list may not be in canonical form, a string compare is not suitable. In addition, I fear about shimmering.

Up to now, I have solved this by a loop like on the page Additional list functions.

Aspect has proposed the following on the chat:
[list {*}$l1] eq [list {*}$l2]

RS proposes this solution, if lists are representing sets (order does not matter):
proc leq {a b} {expr {[lsort $a] eq [lsort $b]}}

% leq {1 2 3} {1 3 2}
1
% leq {1 2 3} {1 3 2 4}
0

If order does matter (i.e. lists represent sequences), lrange makes a list canonical:
proc leq_s {a b} {expr {[lrange $a 0 end] eq [lrange $b 0 end]}}

% leq_s {1 2 3} {1      2     3}
1
% leq_s {1 2 3} {1      3    2}
0

[tcl_hack] - 2015-08-14 13:52:07

It seems to me that the listcomp proc will only show items in list "a" that are not in "b", but if there are items in "b" that are not in "a", they'd be missed. To see all the differences between both lists, I modified it like this:
proc ListComp { List1 List2 } {
   set DiffList {}
   foreach Item $List1 {
      if { [ lsearch -exact $List2 $Item ] == -1 } {
         lappend DiffList $Item
      }
   }
   foreach Item $List2 {
      if { [ lsearch -exact $List1 $Item ] == -1 } {
         if { [ lsearch -exact $DiffList $Item ] == -1 } {
            lappend DiffList $Item
         }
      }
   }
   return $DiffList
}