Using a list comprehension to label data that is common to two lists
<p>I have two lists, A and B.  I want to generate a third list that is 1 if the corresponding entry in A has an entry in the list B at the end of the string and 0 otherwise.</p>

<pre><code>A = ['Mary Sue', 'John Doe', 'Alice Stella', 'James May', 'Susie May']
B = ['Smith', 'Stirling', 'Doe']
</code></pre>

<p>I want a list comprehension that will give the result</p>

<pre><code>[0, 1, 0, 0, 0]
</code></pre>

<p>Keep in mind that this is a specific case of a more general problem.  Elements in A can have arbitrary white space and contain an arbitrary number of words in them.  Likewise elements in B can have an arbitrary number of words.  For example</p>

<pre><code>A = ['  Tom Barry Stirling Adam', 'Maddox Smith', 'George Washington Howard Smith']
B = ['Washington Howard Smith', 'Stirling Adam']
</code></pre>

<p>should return</p>

<pre><code>[1, 0, 1]
</code></pre>

<p>So far I have the following</p>

<pre><code>[1 if y.endswith(x) else 0 for x in B for y in A]
</code></pre>

<p>However the length of the returned list is not the dimension that I want because it gives a 0 or 1 for every combination of A[i], B[j] elements.  I am not interested in solutions using for loops, I need a list comprehension for speed.</p>

<p>A much faster way is to pass a tuple to <em>endswith</em>:</p>

<pre><code>In [8]: A = ['Mary Sue', 'John Doe', 'Alice Stella', 'James May', 'Susie May']

In [9]: B = ['Smith', 'Stirling', 'Doe']            

In [10]: A *= 1000

In [11]: %%timeit                                                          
t = tuple(B)
[int(s.endswith(t)) for s in A]
   ....: 
100 loops, best of 3: 5.02 ms per loop

In [12]: timeit [int(any(full.endswith(last) for last in B)) for full in A]
100 loops, best of 3: 21.3 ms per loop
</code></pre>

<p>You make <em>one function call</em> per element in <code>A</code> as opposed to one function call for potentially every element in B for each in <code>A</code> and without the overhead of the generator used with <em>any</em>. </p>

<p>You can see using a larger set of words just how much faster it is especially if the matches are sparse:</p>

<pre><code>In [2]: from random import sample

In [6]: A = [s.strip() for s in open("/usr/share/dict/american-english")][:20000]

In [7]: B = sample([s.strip() for s in open("/usr/share/dict/british-english")], 2000)

In [8]: %%timeit                                                                      
t = tuple(B)
[int(s.endswith(t)) for s in A]
   ...: 

1 loop, best of 3: 2.16 s per loop
In [9]: timeit [int(any(full.endswith(last) for last in B)) for full in A]              
1 loop, best of 3: 26.6 s per loop
</code></pre>

<p>You said you don't want loops but as the lists grow sorting might be a better option, then bisect to find any matched strings with a log n search reversing the logic:</p>

<pre><code>from  bisect import bisect_left


def compress(l1, l2):
    srt1 = sorted(s[::-1] for s in l2)
    hi = len(l2)
    for ele in l1:
        rev = ele[::-1]
        ind = bisect_left(srt1, rev, hi=hi)
print(list(compress(A, B)))
</code></pre>

<p>The runtime is O(N log N) as opposed to the quadratic approach checking every substring.</p>

python python-3.x list-comprehension