tags:

views:

61

answers:

3

Say I have the following string lists:

string[] list1 = { "one", "two", "three", "four"};
string[] list2 = { "one", "two", "three" };
string[] list3 = { "three", "two", "one" };

I need a query that allows me to compare list2 to list1 and return true if all the strings in list2 exist in list1, in the same order as list2.

So, such a query would return true if I compare list2 to list1 because all the strings in list2 are in list1, in the same order as list2.

The query would return false if I compare list3 to list1 because even though the strings in list3 exist in list1, they are not in the same order.

Is such a query possible?

+3  A: 

If I understand what you're describing, this should do it:

list1.Intersect(list2).SequenceEquals(list2);

We first get the intersection of list1 and list2, which is { "one", "two", "three" }

Then use SequenceEquals to determine if that is the same as list1.

Rex M
Although, I don't think it quite meets the case in the question... "true if I compare list2 to list1"
pst
@Rex - This does not work. This returns false, when it should return true. Comparing list2 to list1 should return true because list2 has the same items as list1 IN THE SAME ORDER. Comparing list3 to list1 should return false because even though list3 contains all the items in list1, they are not in the same order.
Randy Minder
@Rex - Actually you were very close. The following seems to work:list2.Intersect(list1).SequenceEqual(list1.Take(list2.Count()))
Randy Minder
@Randy: I suspect this will fail if you've got duplicate elements - `Intersect` only returns distinct elements. It also fails in the sample case if you move the "four" to the first element... which I assume should still match, because list1 *does* contain the elements of list2 in order, just with an extra element to start with?
Jon Skeet
@Randy my newly modified code does work for the specific example you've cited, but it likely will not work in some real-world scenarios. @Jon's answer solves the underlying problem, not just the sample case.
Rex M
@Rex - Your solution does work. I tried against six different cases and all worked successfully. I would post my results in this comment, but I don't know how, and get the formatting set correctly.
Randy Minder
+2  A: 

You basically have to iterate over both lists simultaneously. Try this:

public static bool IsOrderedSubsequenceOf<T>(
    this IEnumerable<T> smallerList,
    IEnumerable<T> largerList)
{
    IEqualityComparer<T> comparer = Comparer<T>.Default;

    using (var smallerIterator = smallerList.GetEnumerator())
    using (var largerIterator = largerList.GetEnumerator())
    {
        while (smallerIterator.MoveNext())
        {
            T currentTarget = smallerIterator.Current;
            bool found = false;
            while (largerIterator.MoveNext())
            {
                T candidate = largerIterator.Current;
                if (comparer.Equals(currentTarget, candidate))
                {
                    found = true;
                    break;
                }
            }
            if (!found)
            {
                // Exhausted iterator without finding target.
                return false;
            }
        }
    }
    // Found everything in the smaller sequence. Done.
    return true;
}

I haven't tested this or even compiled it, but I think it might work...

You'd call it with

if (list2.IsOrderedSubsequenceOf(list1))

If you can think of a better name (possibly putting the arguments the other way round) that would be good :)

Jon Skeet
@Jon - I would like to kidnap you some day and have our brains exchanged. I'll try this, thank you!!
Randy Minder
A: 

How about this:

int pos = 0;
bool result = list1.Count(p => list2.Contains(p) && pos < list2.Length && list2[pos++] == p) == list2.Length;

This works in all cases I believe. Event for the following:

string[] list1 = { "one", "two", "four", "three" };
string[] list2 = { "one", "two", "three" };

The accepted answer will return false, even though all elements in list2 are in list1, on the same order.

EDIT: Will not work if list1 has repeated values contained in list2. As per comments below.

Klinger
Nope, this fails for `string[] list1 = { "three", "one", "two", "four", "three" }; string[] list2 = { "one", "two", "three" };`. It gets confused because "three" is in list2, but not until the end...
Jon Skeet
Yes it fails, but it's because "three" is repeated in list1.
Klinger
@Klinger: Yes, but I can't see anything in the question to suggest that's not a valid case.
Jon Skeet
@Jon: Just for the sake of the argument. :-) "three", "one", "two", "four", "three" may be a valid input, but, the string "three" is out of order: "...return true if "all" ..., in "the same order" as list2. ".I guess the requirement would need to be clarified as it gives room for more than one interpretation.Three questions come to mind: 1) How duplicates entries in list1 should be treated. 2) How duplicates entries in list2 should be treated. 3) Can the values in list1 have values in between?
Klinger
@Klinger: But all the values *do* occur in the same order... they occur in the wrong order as well, but they do occur in the right order. As you say, some extra clarity would be helpful here.
Jon Skeet
@Jon: Yes, I see your point. Well, just one more case where what appear to be a perfect good and clear requirement is actually not.
Klinger