LowerMoon
LowerMoon

Reputation: 61

Compare 'n' number of lists in Tcl

I have the following lists:

set w {1 2 3 4 5 6 7}
set x {1 2 3 4 5 8 9}
set y {1 2 3 4 0 9 1}
set z {1 2 3 4 5 6 7}

I want to compare all the lists - w.r.t corresponding indices - and find out the common elements and append those common elements to a new list. If I compare the above lists, I see that 1 2 3 4 are common across all the lists and have the same index, so my output should be:

{1 2 3 4}

If there are no common elements(even at 0'th index) my new list will be empty.

I first start off by creating a list of lists:

set l1 [list $w $x $y $z]

Then I create a nested loop to compare the lists and extract my common elements, I will be using list 'x' as my reference list:

for {set j 0} {$j < [llength $x]} {incr j} {
    for {set i 1} {$i < [llength $l1]} {incr i} {

         set a [lindex [lindex $l1 $i] $j]

         if {$a == [lindex $x $j] && [lindex $l2 $j] == {}} {
                lappend l2 $a
         } else {
           break
         }
    }

}

What I'm getting is:

1 2 3 4 5

Upvotes: 1

Views: 430

Answers (2)

glenn jackman
glenn jackman

Reputation: 247260

Similar implementation, but using an array to store the unique elements of the slice

set lists [list $w $x $y $z]
set common [list]

for {set i 0} {$i < [llength $w]} {incr i} {
    array unset elements
    foreach list $lists {
        set elements([lindex $list $i]) dummyvalue
    }
    set unique [array names elements]
    if {[llength $unique] == 1} {
        lappend common $unique
    }
}

puts $common  ;# => 1 2 3 4

Upvotes: 2

Jerry
Jerry

Reputation: 71598

You are effectively only comparing list x against list x, and the actual output from your code above (assuming list l2 is initially empty) is actually:

1 2 3 4 5 8 9

You might ask:

Why is it comparing list x against list x?

Your inner loop starts at index 1 (set i 1), which is list x in l1.

You might further ask:

Why are the other lists not being compared?

Once you have appended something to l2, lindex $l2 $j for the next list will never be empty, so the inner loop will break.


So, how to do it?

I would probably use something like this:

set w {1 2 3 4 5 6 7}
set x {1 2 3 4 5 8 9}
set y {1 2 3 4 0 9 1}
set z {1 2 3 4 5 6 7}

set l1 [list $w $x $y $z]
set l2 [list]

set num [llength $x]

for {set i 0} {$i < $num} {incr i} {
  # This variable will tell us how many matched. 0 indicating none.
  set same 0

  for {set j 0} {$j < [llength $l1]} {incr j} {
    # lindex accepts multiple indices, see the manual
    # using x as reference, if the current list's ith element is the same as x's ith element...
    if {[lindex $l1 $j $i] == [lindex $x $i]} {
      incr same
    }
  }

  # if same reached 4, means 4 matched
  if {$same == 4} {
    lappend l2 [lindex $x $i]
  }
}

Result:

1 2 3 4

You could make the inner loop break if the elements do not match, so it won't loop unnecessarily.

Or instead of counting the number of matches, you could check if the inner loop broke with something like:

for {set i 0} {$i < $num} {incr i} {
  set broke 0

  for {set j 0} {$j < [llength $l1]} {incr j} {
    if {[lindex $l1 $j $i] != [lindex $x $i]} {
      set broke 1
      break
    }
  }

  # if it did not break, then we know all matched
  if {$broke == 0} {
    lappend l2 [lindex $x $i]
  }
}

Upvotes: 1

Related Questions