How to properly use recursion in this (simple) python procedure? - python

guys, I wrote a function to test if two inputs (a and b) have the same data structure, this way:
print same_structure([1, 0, 1], [a, b, c])
#>>> True
#print same_structure([1, [0], 1], [2, 5, 3])
>>> False
#print same_structure([1, [2, [3, [4, 5]]]], ['a', ['b', ['c', ['d', 'e']]]])
>>> True
#print same_structure([1, [2, [3, [4, 5]]]], ['a', ['b', ['c', ['de']]]])
>>> False
This function has (in my implementation) to use recursion. I'm very beginner in recursion and I still have difficulty on thinking recursively. In addition, to avoid cheating the answer, I want to use my own (messy) code and through it learn to use the recursive call (using this code line: 'same_structure return (a [i], b [e])' properly). Could someone show how to properly use the recursion in code below?
Thanks in advance for any help!!!
def is_list(p):
return isinstance(p, list)
def same_structure(a,b):
if not is_list(a) and not is_list(b):
print '#1'
return True
else:
if is_list(a) and is_list(b):
print '#2'
if len(a) != len(b):
print '#3'
return False
if len(a) == len(b):
print '#4'
for e in range(len(a)):
print 'e = ', e, 'a[e]= ', a[e], 'b[e]=', b[e]
return same_structure(a[e], b[e])
else:
return False

The following works:
def same_structure(a, b):
if isinstance(a, list) and isinstance(b, list) and len(a) == len(b):
return all(same_structure(A, B) for A, B in zip(a, b))
return (not isinstance(a, list) and not isinstance(b, list))
When writing recursive functions, you first need to come up with the base case, where you just return a value instead of calling any recursion. The base case here is one of the following conditions:
a is a list and b isn't, or vice-versa: return False
a and b are both lists, but they have different lengths: return False
neither a or b are lists: return True
If a and b are both lists and they have the same length, we need to now check each element of these lists recursively. zip(a, b) provides a convenient way to iterate over the elements from each list together, and if the result of same_structure() is False for any two sub-elements, we want the entire function to return False. This is why all() is used, if you are unfamiliar with all() it is equivalent (but more efficient) to the following loop:
match = True
for A, B in zip(a, b):
if not same_structure(A, B):
match = False
break
return match
Here is how you could rewrite your function without changing too much, the logic is actually very similar to my solution, but just below the print '#4' you were returning from that loop too early:
def same_structure(a,b):
if not is_list(a) and not is_list(b):
print '#1'
return True
else:
if is_list(a) and is_list(b):
print '#2'
if len(a) != len(b):
print '#3'
return False
if len(a) == len(b):
print '#4'
for e in range(len(a)):
print 'e = ', e, 'a[e]= ', a[e], 'b[e]=', b[e]
if not same_structure(a[e], b[e]):
return False
return True
else:
return False

Try this solution, it works with all your examples and it's written in a recursive, functional-programming style but without using zip, all, etc. only slices to reduce the size of the list at each step:
def same_structure(a, b):
if a == [] or b == []:
return a == b
elif is_list(a[0]) != is_list(b[0]):
return False
elif not is_list(a[0]):
return same_structure(a[1:], b[1:])
else:
return same_structure(a[0], b[0]) and same_structure(a[1:], b[1:])

You're only doing the first recursive call, since you're returning right away.
If I understood what you want to do, you need to is call same_structure with the sub-element, and check it's return value (not Return it immediately). If the result of any of the calls is false, return false, otherwise, return true.

Related

Isomorphic Strings (the easy way?)

So I have been trying to solve the Easy questions on Leetcode and so far I dont understand most of the answers I find on the internet. I tried working on the Isomorphic strings problem (here:https://leetcode.com/problems/isomorphic-strings/description/)
and I came up with the following code
def isIso(a,b):
if(len(a) != len(b)):
return false
x=[a.count(char1) for char1 in a]
y=[b.count(char1) for char1 in b]
return x==y
string1 = input("Input string1..")
string2 = input("Input string2..")
print(isIso(string1,string2))
Now I understand that this may be the most stupid code you have seen all day but that is kinda my point. I'd like to know why this would be wrong(and where) and how I should further develop on this.
If I understand the problem correctly, because a character can map to itself, it's just a case of seeing if the character counts for the two words are the same.
So egg and add are isomorphic as they have character counts of (1,2). Similarly paper and title have counts of (1,1,1,2).
foo and bar aren't isomorphic as the counts are (1,2) and (1,1,1) respectively.
To see if the character counts are the same we'll need to sort them.
So:
from collections import Counter
def is_isomorphic(a,b):
a_counts = list(Counter(a).values())
a_counts.sort()
b_counts = list(Counter(b).values())
b_counts.sort()
if a_counts == b_counts:
return True
return False
Your code is failing because here:
x=[a.count(char1) for char1 in a]
You count the occurrence of each character in the string for each character in the string. So a word like 'odd' won't have counts of (1,2), it'll have (1,2,2) as you count d twice!
You can use two dicts to keep track of the mapping of each character in a to b, and the mapping of each character in b to a while you iterate through a, and if there's any violation in a corresponding character, return False; otherwise return True in the end.
def isIso(a, b):
m = {} # mapping of each character in a to b
r = {} # mapping of each character in b to a
for i, c in enumerate(a):
if c in m:
if b[i] != m[c]:
return False
else:
m[c] = b[i]
if b[i] in r:
if c != r[b[i]]:
return False
else:
r[b[i]] = c
return True
So that:
print(isIso('egg', 'add'))
print(isIso('foo', 'bar'))
print(isIso('paper', 'title'))
print(isIso('paper', 'tttle')) # to test reverse mapping
would output:
True
False
True
False
I tried by creating a dictionary, and it resulted in 72ms runtime.
here's my code -
def isIsomorphic(s: str, t: str) -> bool:
my_dict = {}
if len(s) != len(t):
return False
else:
for i in range(len(s)):
if s[i] in my_dict.keys():
if my_dict[s[i]] == t[i]:
pass
else:
return False
else:
if t[i] in my_dict.values():
return False
else:
my_dict[s[i]] = t[i]
return True
There are many different ways on how to do it. Below I provided three different ways by using a dictionary, set, and string.translate.
Here I provided three different ways how to solve Isomorphic String solution in Python.
from itertools import zip_longest
def isomorph(a, b):
return len(set(a)) == len(set(b)) == len(set(zip_longest(a, b)))
here is the second way to do it:
def isomorph(a, b):
return [a.index(x) for x in a] == [b.index(y) for y in b]

Python: Check if a nested list is in a nested list

Sorry for for the potentially silly question. But this seems to be a stumping problem I just can't find the answer to.
Say I have the following mixed nested list in python:
a = [((1,1),(0,0)), (3,4)]
I'd like to check if the following tuples b, c and d appear in a:
b = (1,1)
c = (0,0)
d = (3,4)
print(b in a) # <- False ?
print(c in a) # <- False ?
print(d in a) # <- True
I'd like to replace the code in each print statement in such away that the search finds the tuple in the list and as such returns True
Any help at all would be much appreciated. Apologies if this question has already been asked before.
We need a recursive function that takes a list or tuple and an element to find.
Each function should check whether the element is in the current iterable using the in operator. If it is in then return True, otherwise check if it is in any of the lower dimensions by recursively calling itself with each of the lower iterables and return if any of those succeeded (this can be done with a for-loop, but we may as well use the any() function).
So a one liner would be roughly:
def inc(it, e):
return True if e in it else any(inc(iit, e) for iit in it if type(iit) in (list, tuple))
And it works as intended:
>>> inc(a, (1,1))
True
>>> inc(a, (0,0))
True
>>> inc(a, (3,4))
True
The list has two elements, a tuple which contains other tuples, and a tuple of ints. If you want to check the nested structures, you are going to have to do that yourself. A recursive solution (this one assumes the nested containers can only be either tuple's or lists) would be one option if nesting can be arbitrarily deep:
>>> a = [((1,1),(0,0)), (3,4)]
>>> def is_in(x, nested):
... result = False
... if not isinstance(nested, (tuple, list)):
... return result
... for item in nested:
... if x == item:
... result = True
... else:
... result = result or is_in(x, item)
... if result:
... return True
... return result
...
>>> is_in((1,1), a)
True
>>> is_in((0,0), a)
True
>>> is_in((3,4), a)
True
>>> is_in((8, 8), a)
False
>
This should stop traversing once the first match is found.
Note, if recursion isn't your thing, you can replace the call stack with your own stack!
def is_in_iterative(x, nested):
stack = [nested]
while stack:
item = stack.pop()
print(item)
if item == x:
return True
elif isinstance(item, (list, tuple)):
stack.extend(item)
return False
However, note that this will check in the reverse order...
i tried to solve your problem by recursive function method and global variable defining , i wish this solution can give you idea:
a = [((1,1),(0,0)), (3,4)]
global bb
bb=False
def checker(tuple1, tuple2):
b=str(type(('a','b')))
for i in range(len(tuple1)):
if(str(type(tuple1[i]))!=b):
if (tuple1==tuple2):
global bb
bb=True
else:
checker(tuple1[i],tuple2)
checker(a,(1,1))
print(bb)
#-->True
bb=False
checker(a,(3,4))
print(bb)
#-->True
bb=False
checker(a,(0,0))
print(bb)
#--->True
bb=False
checker(a,(10,1))
print(bb)
#--->False

How to check if a sequence in a tuple is part of a tuple? python [duplicate]

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
Best Way To Determine if a Sequence is in another sequence in Python
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))
Don't know if this is very pythonic, but I would do it in this way:
def is_sublist(a, b):
if not a: return True
if not 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.
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
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
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
This should work with whatever couple of lists, preserving the order.
Is checking if b is a sub list of a
def is_sublist(b,a):
if len(b) > len(a):
return False
if a == b:
return True
i = 0
while i <= len(a) - len(b):
if a[i] == b[0]:
flag = True
j = 1
while i+j < len(a) and j < len(b):
if a[i+j] != b[j]:
flag = False
j += 1
if flag:
return True
i += 1
return False
>>>''.join(b) in ''.join(a)
True
Not sure how complex your application is, but for pattern matching in lists, pyparsing is very smart and easy to use.
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. :)

Basic Anagram VS more advanced one

I'm currently working my way through a "List" unit and in one of the exercises we need to create an anagram (for those who don't know; two words are an anagram if you can rearrange the letters from one to spell the other).
The easier solution that comes to mind is:
def is_anagram(a, b):
return sorted(a) == sorted(b)
print is_anagram('god', 'dog')
It works, but it doesn't really satisfy me. If we were in this situation for example:
def is_anagram(a, b):
return sorted(a) == sorted(b)
print is_anagram('god', 'dyog') #extra letter 'd' in second argument
>>> False
Return is False, although we should be able to to build the word 'god' out of 'dyog'. Perhaps this game/problem isn't called an anagram, but I've been trying to figure it out anyway.
Technically my solution is to:
1- Iterate through every element of b.
2- As I do, I check if that element exists in a.
3- If all of them do; then we can create a out of b.
4- Otherwise, we can't.
I just can't seem to get it to work. For the record, I do not know how to use lambda
Some tests:
print is_anagram('god', 'ddog') #should return True
print is_anagram('god', '9d8s0og') #should return True
print is_anagram('god', '_#10d_o g') #should return True
Thanks :)
Since the other answers do not, at time of writing, support reversing the order:
Contains type hints for convenience, because why not?
# Just strip hints out if you're in Python < 3.5.
def is_anagram(a: str, b: str) -> bool:
long, short = (a, b) if len(a) > len(b) else (b, a)
cut = [x for x in long if x in short]
return sorted(cut) == sorted(short)
If, in future, you do learn to use lambda, an equivalent is:
# Again, strip hints out if you're in Python < 3.5.
def is_anagram(a: str, b: str) -> bool:
long, short = (a, b) if len(a) > len(b) else (b, a)
# Parentheses around lambda are not necessary, but may help readability
cut = filter((lambda x: x in short), long)
return sorted(cut) == sorted(short)
If you need to check if a word could be created from b you can do this
def is_anagram(a,b):
b_list = list(b)
for i_a in a:
if i_a in b_list:
b_list.remove(i_a)
else:
return False
return True
UPDATE(Explanation)
b_list = list(b) makes a list of str objects(characters).
>>> b = 'asdfqwer'
>>> b_list = list(b)
>>> b_list
['a', 's', 'd', 'f', 'q', 'w', 'e', 'r']
What basically is happening in the answer: We check if every character in a listed in b_list, when occurrence happens we remove that character from b_list(we do that to eliminate possibility of returning True with input 'good', 'god'). So if there is no occurrence of another character of a in rest of b_list then it's not advanced anagram.
def is_anagram(a, b):
test = sorted(a) == sorted(b)
testset = b in a
testset1 = a in b
if testset == True:
return True
if testset1 == True:
return True
else:
return False
No better than the other solution. But if you like verbose code, here's mine.
Try that:
def is_anagram(a, b):
word = [filter(lambda x: x in a, sub) for sub in b]
return ''.join(word)[0:len(a)]
Example:
>>> is_anagram('some','somexcv')
'some'
Note: This code returns the common word if there is any or '' otherwise. (it doesn't return True/False as the OP asked - realized that after, but anyway this code can easily change to adapt the True/False type of result - I won't fix it now such there are great answers in this post that already do :) )
Brief explanation:
This code takes(filter) every letter(sub) on b word that exists in a word also. Finally returns the word which must have the same length as a([0:len(a)]). The last part with len needs for cases like this:
is_anagram('some','somenothing')
I think all this sorting is a red herring; the letters should be counted instead.
def is_anagram(a, b):
return all(a.count(c) <= b.count(c) for c in set(a))
If you want efficiency, you can construct a dictionary that counts all the letters in b and decrements these counts for each letter in a. If any count is below 0, there are not enough instances of the character in b to create a. This is an O(a + b) algorithm.
from collections import defaultdict
def is_anagram(a, b):
b_dict = defaultdict(int)
for char in b:
b_dict[char] += 1
for char in a:
b_dict[char] -= 1
if b_dict[char] < 0:
return False
return True
Iterating over b is inevitable since you need to count all the letters there. This will exit as soon as a character in a is not found, so I don't think you can improve this algorithm much.

Think Python Chapter 10 Exercise 6 Stuck

Exercise:
Write a function called is_sorted that takes a list as a parameter and returns True if the list is sorted in ascending order and False otherwise. You can assume (as a precondition) that the elements of the list can be compared with the relational operators <, >, etc.
For example, is_sorted([1,2,2]) should return True and is_sorted(['b', 'a']) should return False.
So far I have:
def is_sorted(stuff):
for i in stuff:
if stuff[i+1] > stuff[i]:
return True
else:
return False
numbers = [1, 0, 5, 2, 8]
print is_sorted(numbers)
But it seems to return True every time I change the numbers list around. How do I change this so it works?
The line
for i in stuff:
Iterates through items, not indices. If you put 5 or 8 first, you will get an IndexError. Also, you return if the check passes for the first item, much too early! You can return False if any are out of order, but all must be in order to return True.
Instead, try using enumerate to get both item and index:
def is_sorted(stuff):
for index, item in enumerate(stuff):
try:
if item > stuff[index + 1]:
return False
except IndexError:
return True
Alternatively, use zip to compare pair wise:
def is_sorted(stuff):
return all(b >= a for a, b in
zip(stuff, stuff[1:]))
for i in stuff will enumerate each of the elements. What you'd like to do is enumerate the indices of the elements, so change the loop to
for i in range(len(stuff))
Next, you dont want to return True if you come across a subsequent element greater than the current. just return True after testing each pair of adjacent elements, so something like:
def is_sorted(stuff):
for i in range(1,len(stuff)):
if stuff[i - 1] > stuff[i]:
return False
return True
Building off #jonrsharpe's answer,
This is a great application of the itertools pairwise recipe:
from itertools import tee, izip
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None) #or b.next() on Python 2.5 and below
return izip(a, b)
def is_sorted(iterable):
return all(a <= b for a,b in pairwise(iterable))
This avoids indexed access or slicing, which allows you to operate on generators and greatly reduces memory footprint.
is_sorted(xrange(5000000))
Unfortunately, it won't help you for infinite generators like is_sorted(count(0))
It looks like you're conflating for item in stuff with for i in range(len(stuff)).
Say stuff = ['a', 'b', 'c', 'd'].
With for item in stuff, you'd be iterating over each item in stuff: 'a', 'b', 'c', and 'd'.
With for i in range(len(stuff)), you'd be iterating over each index of stuff: 0, 1, 2, 3.
Note that the variable names item and i are common conventions for these statements but are not mandatory--you can replace item and i with anything you want. Therefore your 2nd line of code (for i in stuff) is executing #1 above, not #2 as you were expecting.
To have your code execute #2, you'd have to use range(len(stuff) in your 2nd line of code instead of just stuff.
I know this is an old thread but I am trying to refine my solution to this problem.
I want to make sure no body sneaks in an integer or a string in a predominantly string or integer list resp. For instance, ['a', 'b', 2]
I wrote a python program for this but it seems to me it is getting a bit unwieldy. Is there any better solution to this?
def is_sorted(stuff):
is_int = 1
not_int = 0
if type(stuff[0]) == int:
of_type = is_int
else:
of_type = not_int
for i in range(1,len(stuff)):
if (of_type == is_int and (type(stuff[i - 1]) == int and type(stuff[i]) == int)) or \
(of_type == not_int and (type(stuff[i - 1]) == str and type(stuff[i]) == str)):
if stuff[i - 1] > stuff[i]:
return False
else:
print "I saw what you did there!"
return False
return True
print is_sorted(['b', 'a']) //False
print is_sorted(['a', 'b']) //True
print is_sorted([1, 2]) //True
print is_sorted(['a', 'b', 2]) //False; "I saw what you did there!"
def isSorted(stuff):
if len(stuff) == 0:
output = 'True'
if len(stuff) == 1:
output = 'True'
for i in range(0,len(stuff)-1):
if stuff[i+1] > stuff[i]:
output = 'True'
else:
return False
return output

Categories