2

I need to find if a string has all characters of a substring. So for a 7 char string i want to know if the chars of a 5 char substings are in the string, without repeating letters (if substring has 2 letters a, then the string needs at max 2 letters a).

For exemple :

Substring = 'aabfy'
String1 = 'aabcfgy'
String2 = 'abcfgmy' 
String3 = 'aaabcfy'

Then String1 and String3 are True, but String2 is False. Because sub string is in 1 and 3 but not in 2 (double a, but it can also be any other letter).

I hope i explained myself, any questions i can and will answer.

U13-Forward
  • 69,221
  • 14
  • 89
  • 114
Gundro
  • 139
  • 9

2 Answers2

1

You can use collections.Counter in the following way:

from collections import Counter

def contains_all(string, substring):
    c1, c2 = Counter(string), Counter(substring)
    return all(c1[x] >= c2[x] for x in c2)

This will ensure every character in the substring is at least as many times in the containing string.

>>> contains_all('aaabcfy', 'aabfy')
True
>>> contains_all('abcfgmy', 'aabfy')
False
>>> contains_all('aabcfgy', 'aabfy')
True

Update: A better version (thx to @HenadziRabkin for the hint), using Counter difference:

def contains_all(string, substring):
    return not (Counter(substring) - Counter(string))

self.cs = Counter(source)
# more efficient version
def countains_all(target):
    ct = Counter(target)
    for k,v in ct.items(): # Compare only target symbols
       if k not in self.cs or self.cs[k]- v < 0: # Break comparison if a source has number of expected symbol types less than a target 
         return False
    return True
Henadzi Rabkin
  • 6,834
  • 3
  • 32
  • 39
user2390182
  • 72,016
  • 6
  • 67
  • 89
  • It works! Thanks – Gundro Jan 13 '19 at 08:11
  • you can substract Counters and check length difference of a result as an option – Henadzi Rabkin Nov 26 '22 at 15:46
  • @HenadziRabkin Difference is a good approach, too. However, length is not required, you can simply check for any remainder when subtracting the string from the substring. I added that version. – user2390182 Nov 29 '22 at 07:09
  • @user2390182 that's true, but be aware that this method is not optimal, it takes O(n) and checking every symbol for value difference equality to zero and abrupt circle if not is much more efficient. You have to check only symbols which has target substring you look for instead of ALL symbols which might have source string – Henadzi Rabkin Nov 29 '22 at 14:40
  • @user2390182 in the efficient version you will have O(m) instead of O(n) where M - is a target length and n is a source length – Henadzi Rabkin Nov 29 '22 at 14:53
  • @HenadziRabkin nah it is O(m+n) in both cases. The `Counter(source), Counter(target)` calls already make that obvious. – user2390182 Dec 01 '22 at 15:05
  • @HenadziRabkin Actually, your "more efficient" version is quite equivalent to my original, just way more verbose and convoluted. – user2390182 Dec 01 '22 at 15:23
  • @user2390182 true, the only difference is that your first version doesn't stop comparisons at non-match. In the worst case they are the same. I tried all three versions in leetcode and "more efficient" one wins with pre-created source Counter outside the function – Henadzi Rabkin Dec 01 '22 at 15:33
  • @user2390182 I have added an update and moved Counter(source) out of the function – Henadzi Rabkin Dec 01 '22 at 15:36
  • @HenadziRabkin My first DOES stop at a non-match. `all` is short-circuiting. Creating the source counter once make sense if you test it against multiple targets. And you if part is still way to wordy as `if k not in self.cs or self.cs[k]- v < 0:` is the same as just `if self.cs[k] < v` – user2390182 Dec 01 '22 at 22:52
  • @user2390182 nice, I now know that 'all' is short-circuiting. But you anyway has to move creation of source-str-Counter out of the function. – Henadzi Rabkin Dec 02 '22 at 16:05
1

Or use str.count in list comprehension, with all:

def f(s):
    return all(s.count(i)>=Substring.count(i) for i in Substring)
print(f(String1))
print(f(String2))
print(f(String3))

Output:

True
False
True
U13-Forward
  • 69,221
  • 14
  • 89
  • 114