views:

338

answers:

8

If I have this:

a='abcdefghij'
b='de'

Then this finds b in a:

b in a => True

Is there a way of doing an similar thing with lists? Like this:

a=list('abcdefghij')
b=list('de')

b in a => False 

The 'False' result is understandable - because its rightly looking for an element 'de', rather than (what I happen to want it to do) 'd' followed by 'e'

This is works, I know:

a=['a', 'b', 'c', ['d', 'e'], 'f', 'g', 'h']
b=list('de')
b in a => True

I can crunch the data to get what I want - but is there a short Pythonic way of doing this?

To clarify: I need to preserve ordering here (b=['e','d'], should return False).

And if it helps, what I have is a list of lists: these lists represents all possible paths (a list of visited nodes) from node-1 to node-x in a directed graph: I want to 'factor' out common paths in any longer paths. (So looking for all irreducible 'atomic' paths which constituent all the longer paths).

Related

+1  A: 

So, if you aren't concerned about the order the subset appears, you can do:

a=list('abcdefghij')
b=list('de')
set(b).issubset(set(a))

True

Edit after you clarify: If you need to preserve order, and the list is indeed characters as in your question, you can use:

''.join(a).find(''.join(b)) > 0
Ramashalanka
Thanks - but actually I do need to preserve order I'm afraid - I'll update the question to clarify that.
monojohnny
Thanks to this and another post I learnt 'join()' today !Cheers
monojohnny
+1  A: 

Not sure how complex your application is, but for pattern matching in lists, pyparsing is very smart and easy to use.

Thanks for the link - might be a little OTT for what I want, but will check it out.Cheers
monojohnny
+3  A: 

Don't know if this is very pythonic, but I would do it in this way:

def is_sublist(a, b):
    if a == []: return True
    if b == []: return False
    return b[:len(a)] == a or is_sublist(a, b[1:])

Shorter solution is offered in this discussion, but it suffers from the same problems as solutions with set - it doesn't consider order of elements.

UPDATE:
Inspired by MAK I introduced more concise and clear version of my code.

UPDATE: There are performance concerns about this method, due to list copying in slices. Also, as it is recursive, you can encounter recursion limit for long lists. To eliminate copying, you can use Numpy slices which creates views, not copies. If you encounter performance or recursion limit issues you should use solution without recursion.

Rorick
Looks good - will give this a go! Cheers
monojohnny
This works well for me - and nicely written to make sense. Thanks ![I'm too new to Python to know what 'Pythonic' really means, but if I was a betting man, I would bet that this is Pythonic]
monojohnny
Oh, I have just shortened a code a bit. Try this new version. If it doesn't fit you (or I made a bug =)), you can stick to the old one.
Rorick
You're building a lot of copies of b due to recursion, that's a definite drawback of your solution. Imagine you have a list with one million entries, and your searched sublist appears at the very end.
jellybean
Hope your lists aren't too big as it creates approx. len(b) lists. (len(b)/2 + len(b)/2).
Till Backhaus
Its ok for my lists - never more than ~5-10 items.I'm gonna stick to the old imp for now, but I might take a look later.Cheers!
monojohnny
Your updated version doesn't work.
jellybean
And ditto my original one) List is too long, so we get - yes - stack overflow!
Rorick
There's no real reason to write this recursively when an iterative method is just as obvious.
Mike Graham
I've "tail-optimized" your code by hand. http://paste.pocoo.org/show/177416/ -- it is still slow but now it can handle any size list.
nosklo
+7  A: 

I suspect there are more pythonic ways of doing it, but at least it gets the job done:

l=list('abcdefgh')
pat=list('de')

print pat in l # Returns False
print any(l[i:i+len(pat)]==pat for i in xrange(len(l)-len(pat)+1))
MAK
Thanks - will give this a go.
monojohnny
Correct and compact. Cool!
Rorick
if you did it as a for loop, you could return early with True.
telliott99
`any` is short-circuit so it anyway returns early with True. Look at http://docs.python.org/library/functions.html
Rorick
This isn't especially pretty, but it is to the point.
Mike Graham
+1  A: 
>>>''.join(b) in ''.join(a)

True
mshsayem
That is quite promising (I see it 'flattens' the list to a string) - but in my case, the list memembers themselves are not always single characters [ in fact they are numbers ] - and I might lose information if I collapse them like this: my fault for over-simplifying the question!I might look at zero-padding the numbers so they are all fixed-sized elements though.Thanks.
monojohnny
what if u first convert the numbers into delimited str, say: '*'.join([str(i) for i in b]) in '*'.join([str(i) for i in a])
mshsayem
Another good idea, cheers!
monojohnny
This would raise for `[2, 4] in [1, 7, 8]` and would return an untrue answer for `['qqhe', 'lloqq'] in ['why', 'hello']`.
Mike Graham
See the comments above... I had not changed the answer since OP has accepted one...
mshsayem
A: 

Use the lists' string representation and remove the square braces. :)

def is_sublist(a, b):
    return str(a)[1:-1] in str(b)

EDIT: Right, there are false positives ... e.g. is_sublist([1], [11]). Crappy answer. :)

jellybean
Nice - although in my case I would need to pre-process my items to ensure they are all the same length - otherwise going back from string->list (I actually need an 'index' method returned as well as True|False for my imp).Cheers
monojohnny
This has false positives in general.
Mike Graham
+3  A: 

I think this will be faster - It uses C implementation list.index to search for the first element, and goes from there on.

def find_sublist(sub, bigger):
    if not bigger:
        return -1
    if not sub:
        return 0
    first, rest = sub[0], sub[1:]
    pos = 0
    try:
        while True:
            pos = bigger.index(first, pos) + 1
            if not rest or bigger[pos:pos+len(rest)] == rest:
                return pos
    except ValueError:
        return -1

data = list('abcdfghdesdkflksdkeeddefaksda')
print find_sublist(list('def'), data)

Note that this returns the position of the sublist in the list, not just True or False. If you want just a bool you could use this:

def is_sublist(sub, bigger): 
    return find_sublist(sub, bigger) >= 0
nosklo
And I think you were right - jellybean timed it... :)
monojohnny
I timed this one, too ... you beat me, nosklo. :)
jellybean
+2  A: 

I timed the accepted solution, my earlier solution and a new one with an index. The one with the index is clearly best.

EDIT: I timed nosklo's solution, it's even much better than what I came up with. :)

def is_sublist_index(a, b):
    if not a:
        return True

    index = 0
    for elem in b:
        if elem == a[index]:
            index += 1
            if index == len(a):
                return True
        elif elem == a[0]:
            index = 1
        else:
            index = 0

    return False

def is_sublist(a, b):
    return str(a)[1:-1] in str(b)[1:-1]

def is_sublist_copylist(a, b):
    if a == []: return True
    if b == []: return False
    return b[:len(a)] == a or is_sublist_copylist(a, b[1:])

from timeit import Timer
print Timer('is_sublist([99999], range(100000))', setup='from __main__ import is_sublist').timeit(number=100)
print Timer('is_sublist_copylist([99999], range(100000))', setup='from __main__ import is_sublist_copylist').timeit(number=100)
print Timer('is_sublist_index([99999], range(100000))', setup='from __main__ import is_sublist_index').timeit(number=100)
print Timer('sublist_nosklo([99999], range(100000))', setup='from __main__ import sublist_nosklo').timeit(number=100)

Output in seconds:

4.51677298546

4.5824368

1.87861895561

0.357429027557

jellybean
Nice: quite a difference actually!
monojohnny
+1 for timing. This solution is best form performance view, but I prefer mine as it looks much clearer to me. This one is rather C-way then Pythonic. But I'm glad that Python allows both ways)
Rorick
I don't know how about you, but I slept a little this night) My solution doesn't work for such long lists because of recursion limit. I even started to imagine that there's tail recursion optimization in Python!! Of course, there is no. Fix `is_sublist_copylist` to call itself, but not another function %)
Rorick
ah, fixed that. :)
jellybean
@jellybean: Nice. Making the smaller `list` more than just a single element will probably reveal more about the relative speed of the solutions. Try something like `range(19000,99999)` instead of `[99999]`.
MAK