Related
Recently, I was experimenting with writing a function to find a primitive value anywhere within an arbitrarily deeply nested sequence, and return the path taken to get there (as a list of indices inside each successive nested sequence, in order). I encountered a very unexpected obstacle: the function was finding the result, but not returning it! Instead of the correct output, the function kept returning the output which should only have been produced when attempting to find an item not in the sequence.
By placing print statements at various points in the function, I found that the problem was that after the recursive call which actually found the item returned, others which did not find the item were also returning, and evidently later in time than the one that found it. This meant that the final result was getting reset to the 'fail' value from the 'success' value unless the 'success' value was the last thing to be encountered.
I tried fixing this by putting an extra conditional inside the function to return early in the success case, trying to preempt the additional, unnecessary recursive calls which were causing the incorrect final result. Now, this is where I ran into the root cause of the problem:
There is no way of knowing which recursive call (if any) will find the item beforehand, and once one of them does find it, it has no way of 'communicating' with the others!
The only way I could come up with of avoiding this deeper issue was to completely refactor the function to 'set' a variable outside itself with the 'success' output if and only if the 'success' condition is encountered. The external, global variable starts out set to the 'failed to find item in sequence' value, and is not reset except in the 'success' case. All the other recursive calls just return without doing anything. This seems very ugly and inefficient, but it does work.
FIRST ATTEMPT
# ITERATIVE/RECURSIVE SEQUENCE TRAVERSER (First Attempt)
# Works on 'result1' but not on 'result2'
# Searches for 'item' in sequence (list or tuple) S, and returns a tuple
# containing the indices (in order of increasing depth) at which the item
# can be found, plus the depth in S at which 'item' was found.
# If the item is *not* found, returns a tuple containing an empty list and -1
def traverse(S, item, indices=[], atDepth=0):
# If the sequence is empty, return the 'item not found' result
if not S:
return ([], -1)
else:
# For each element in the sequence (breadth-first)
for i in range(len(S)):
# Success condition base case: found the item!
if S[i] == item:
return (indices + [i], atDepth)
# Recursive step (depth-first): enter nested sequence
# and repeat procedure from beginning
elif type(S[i]) in (list, tuple):
return traverse(S[i], item, indices + [i], atDepth + 1)
# Fail condition base case: searched the entire length
# and depth of the sequence and didn't find the item, so
# return the 'item not found' result
else:
print("We looked everywhere but didn't find " + str(item) + " in " + str(S) + ".")
return ([], -1)
L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7])], [[8, ()]], (([9], ), 10)]
result1 = traverse(L, 7)
result2 = traverse(L, 9)
print("-------------------------------------------")
print(result1)
print("-------------------------------------------")
print(result2)
SECOND ATTEMPT
# ITERATIVE/RECURSIVE SEQUENCE TRAVERSER (Second Attempt)
# Does not work on either test case
# Searches for 'item' in sequence (list or tuple) S, and returns a tuple
# containing the indices (in order of increasing depth) at which the item
# can be found, plus the depth in S at which 'item' was found.
# If the item is *not* found, returns a tuple containing an empty list and -1
def traverse(S, item, indices=[], atDepth=0, returnValue=None):
# If the sequence is empty, return the 'item not found' result
if not S:
print("Sequence S is empty.")
return ([], -1)
# --- ATTEMPTED FIX:
# If the item is found before the end of S is reached,
# do not perform additional searches. In addition to being
# inefficient, doing extra steps would cause incorrect false
# negatives for the item being in S.
# --- DOES NOT WORK: the underlying issue is that the multiple recursive
# calls generated at the same time can't communicate with each other,
# so the others don't 'know' if one of them already found the item.
elif returnValue:
print("Inside 'elif' statement!")
return returnValue
else:
# For each element in the sequence (breadth-first)
for i in range(len(S)):
# Success condition base case: found the item!
if S[i] == item:
# Return the depth and index at that depth of the item
print("--- Found item " + str(item) + " at index path " + str(indices) + " in current sequence")
returnValue2 = (indices + [i], atDepth)
print("--- Item " + str(item) + " is at index path " + str(returnValue2) + " in S, SHOULD RETURN")
#return returnValue2 # THIS DIDN'T FIX THE PROBLEM
#break # NEITHER DID THIS
# Recursive step (depth-first): enter nested sequence
# and repeat procedure from beginning
elif type(S[i]) in (list, tuple):
# CANNOT USE 'return' BEFORE RECURSIVE CALL, as it would cause any items
# in the outer sequence which come after the first occurrence of a nested
# sequence to be missed (i.e. the item could exist in S, but if it is
# after the first nested sequence, it won't be found)
traverse(S[i], item, indices + [i], atDepth + 1, returnValue) # CAN'T USE 'returnValue2' HERE (out of scope);
# so parameter can't be updated in 'if' condition
# Fail condition base case: searched the entire length
# and depth of the sequence and didn't find the item, so
# return the 'item not found' result
else:
print("We looked everywhere but didn't find " + str(item) + " in " + str(S) + ".")
return ([], -1)
L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7])], [[8, ()]], (([9], ), 10)]
result1 = traverse(L, 7)
result2 = traverse(L, 9)
print("-------------------------------------------")
print(result1)
print("-------------------------------------------")
print(result2)
THIRD AND FINAL ATTEMPT -- Working, but not ideal!
# ITERATIVE/RECURSIVE SEQUENCE TRAVERSER (Third Attempt)
# This 'kludge' is ** HIDEOUSLY UGLY **, but it works!
# Searches for 'item' in sequence (list or tuple) S, and generates a tuple
# containing the indices (in order of increasing depth) at which the item
# can be found, plus the depth in S at which 'item' was found.
# If the item is *not* found, returns nothing (implicitly None)
# The results of calling the function are obtained via external global variables.
# This 3rd version of 'traverse' is thus actually a void function,
# and relies on altering the global state instead of producing an output.
# ----- WORKAROUND: If the result is found, have the recursive call that found it
# send it to global scope and use this global variable as the final result of calling
# the 'traverse' function.
# Initialize the global variables to the "didn't find the item" result,
# so the result will still be correct if the item actually isn't in the sequence.
globalVars = {'result1': ([], -1), 'result2': ([], -1)}
def traverse(S, item, send_output_to_var, indices=[], atDepth=0):
# If the sequence is empty, return *without* doing anything to the global variable.
# It is already initialized to the "didn't find item" result.
if not S:
return
else:
# For each element in the sequence (breadth-first)
for i in range(len(S)):
# Success condition base case: found the item!
if S[i] == item:
# Set the global variable to the index path of 'item' in 'S'.
globalVars[send_output_to_var] = (indices + [i], atDepth)
# No need to keep on doing unnecessary work!
return
# Recursive step (depth-first): enter nested sequence
# and repeat procedure from beginning
elif type(S[i]) in (list, tuple):
# Don't use 'return' before the recursive call, or it will miss items
# in the outer sequence after a nested sequence is encountered.
traverse(S[i], item, send_output_to_var, indices + [i], atDepth + 1)
# Fail condition base case: searched the entire length
# and depth of the sequence and didn't find the item.
else:
# Return *without* setting the global variable, as it is
# already initialized to the "didn't find item" result.
return
L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7])], [[8, ()]], (([9], ), 10)]
traverse(L, 7, 'result1')
traverse(L, 9, 'result2')
print("-------------------------------------------")
print(globalVars['result1'])
print("-------------------------------------------")
print(globalVars['result2'])
I was wondering if I'm missing something and there is in fact a way of making this work without the use of external variables. The best possible solution would be somehow 'shutting down' all the other recursive calls as soon as one of them returns the success result, but I don't believe this is possible (I'd love to be wrong about this!). Or maybe some kind of 'priority queue' which delays the return of the 'success' case recursive call (if it exists) until after all the 'fail' case recursive calls have returned?
I looked at this similar question: Recursively locate nested dictionary containing a target key and value
but although the accepted answer here https://stackoverflow.com/a/59538362/18248018 by ggorlen solved OP's problem and even mentions what seems to be this exact issue ("matched result isn't being passed up the call stack correctly"), it is tailored towards performing a specific task, and doesn't offer the insight I'm looking for into the more general case.
Your first attempt is almost perfect, the only mistake is that you return the result of searching through the first list/tuple at the current depth, regardless of whether the item was found or not. Instead, you need to check for a positive result, and only return if it is one. That way you keep iterating through the current depth until you either find the item or it is not found at all.
So you need to change:
return traverse(S[i], item, indices + [i], atDepth + 1)
to something like:
t = traverse(S[i], item, indices + [i], atDepth + 1)
if t != ([], -1):
return t
Full code:
def traverse(S, item, indices=[], atDepth=0):
# If the sequence is empty, return the 'item not found' result
if not S:
return ([], -1)
else:
# For each element in the sequence (breadth-first)
for i in range(len(S)):
# Success condition base case: found the item!
if S[i] == item:
return (indices + [i], atDepth)
# Recursive step (depth-first): enter nested sequence
# and repeat procedure from beginning
elif type(S[i]) in (list, tuple):
t = traverse(S[i], item, indices + [i], atDepth + 1)
if t != ([], -1):
return t
# Fail condition base case: searched the entire length
# and depth of the sequence and didn't find the item, so
# return the 'item not found' result
else:
print("We looked everywhere but didn't find " + str(item) + " in " + str(S) + ".")
return ([], -1)
Output for your two test cases:
>>> traverse(L, 7)
([3, 1, 2, 4], 3)
>>> traverse(L, 9)
We looked everywhere but didn't find 9 in [6, 6.25, 6.5, 6.75, 7].
We looked everywhere but didn't find 9 in (4, 5, [6, 6.25, 6.5, 6.75, 7]).
We looked everywhere but didn't find 9 in [3, (4, 5, [6, 6.25, 6.5, 6.75, 7])].
We looked everywhere but didn't find 9 in [8, ()].
We looked everywhere but didn't find 9 in [[8, ()]].
([5, 0, 0, 0], 3)
Note as pointed out by #FreddyMcloughlan, atDepth is simply the length of the returned list minus 1. So you can remove that parameter from the function call and just use:
def traverse(S, item, indices=[]):
# If the sequence is empty, return the 'item not found' result
if not S:
return ([], -1)
else:
# For each element in the sequence (breadth-first)
for i in range(len(S)):
# Success condition base case: found the item!
if S[i] == item:
return (indices + [i], len(indices))
# Recursive step (depth-first): enter nested sequence
# and repeat procedure from beginning
elif type(S[i]) in (list, tuple):
t = traverse(S[i], item, indices + [i])
if t != ([], -1):
return t
# Fail condition base case: searched the entire length
# and depth of the sequence and didn't find the item, so
# return the 'item not found' result
else:
print("We looked everywhere but didn't find " + str(item) + " in " + str(S) + ".")
return ([], -1)
I'd write it differently, using the result of the recursive calls to decide whether to return immediately or continue searching. My version returns a non-empty list of indices if the item was found, or None otherwise.
def traverse(S, item):
for i, element in enumerate(S):
if element == item:
return [i]
if type(element) in (list, tuple):
if indices := traverse(element, item):
indices.insert(0, i)
return indices
Results for your two tests (Try it online!):
[3, 1, 2, 4]
[5, 0, 0, 0]
I don't pass in a list, instead build the list backwards only from the location of the item (or if it's nowhere, then I don't build any list at all). This is much less work, although inserting at 0 makes it quadratic, although much faster than copying slices. If you want it linear, you could wrap this recursive function in another function and have the recursive function append the indices and have the wrapper function reverse the list before returning it to the original outside caller. The wrapper function would also let you turn the "non-empty list or None" result into something else (like a pair with depth), if you want.
Since refactoring the traverse function to be iterative instead of recursive was discussed in the comments, here is that version too for completeness' sake:
# Iterative-only version of the same function from the question
def traverse(S, item):
indices = []
sequence = S
depth = 0
# By definition, if the sequence is empty, you won't find any items in it.
# Return the 'item not found' result.
if not S:
return ([], -1)
else:
# You have to start somewhere :D
indices.append(0)
while depth > -1:
# Go back up one level once the end of a nested sequence is reached.
while indices[depth] == len(sequence):
depth -= 1
# If the depth ever gets below 0, that means that we've scanned
# the entire length of the outermost sequence and not found the
# item. Return the 'failed to find item' result.
if depth == -1:
return ([], -1)
# Remove the entry corresponding to index in the innermost
# nested sequence we just exited
indices.pop()
# Reset 'sequence' using the outermost sequence S and the indices
# computed so far, to get back to the sequence which contained
# the previous one
sequence = S
for i in range(len(indices) - 1):
sequence = sequence[indices[i]]
indices[depth] += 1
# And return to the top of the outer 'while' loop to re-check
# whether to go down another level
continue
# Success: found the item at depth 'depth', in sequence 'sequence',
# at index 'indices[depth]` inside that sequence. Return 'indices'
# and 'depth'.
if sequence[indices[depth]] == item:
return (indices, depth)
# Recursion replacement: enter the nested subsequence and increase the depth
# as long as the nested subsequence is not empty. If it is empty, treat it
# as if it were a non-sequence type.
elif (type(sequence[indices[depth]]) in (list, tuple)) and (sequence[indices[depth]]):
sequence = sequence[indices[depth]]
depth += 1
indices.append(0)
# The item being compared is not a sequence type and isn't equal to 'item', so increment
# the index without increasing the depth
else:
indices[depth] += 1
# If all of S has been searched and item was not found,
# return the 'failed to find item' result
return ([], -1)
L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7]), 7.5], [[8, ()]], (([9], ), 10)]
# Demonstrating the function's use: search for numbers from 0 to 10 in list L,
# in increments of 0.5
for i in range(21):
outputString = "Looking for " + (str(i/2) if (i/2 % 1) else str(int(i/2))) + " in L: "
indices, depth = traverse(L, i/2)
if indices:
print(outputString + "found at depth " + str(depth) + " by index path " + str(indices))
else:
print(outputString + "Not found.")
I've heard that iterative algorithms can be faster than recursive ones, so I tried benchmarking all 3 working versions using time.time(). For all 3, I used the same 'busy work' test shown below:
L = [0, 1, 2, [3, (4, 5, [6, 6.25, 6.5, 6.75, 7]), 7.5], [[8, ()]], (([9], ), 10)]
startTime = time.time()
for i in range(1000):
for j in range(21):
traverse(L, j/2)
finishedAt = time.time()
print("[WHICH VERSION] took " + str(finishedAt - startTime) + " seconds")
and got these results:
For the original recursive function with Nick's fix applied:
Original recursive version took 0.14863014221191406 seconds
For Kelly's recursive version:
Quadratic recursive version took 0.11344289779663086 seconds
For the iterative version:
Iterative version took 0.1890261173248291 seconds
As I expected, Kelly's version, which has optimizations that my original function didn't, is faster than the original. I haven't implemented the linear version with the outer wrapping function that reverses the list yet, but I expect that that will be even faster.
Unfortunately, the iterative version seems to actually be slower than the original version of the recursive function with no optimizations, so I guess I'll take it over to the code review site to see where it could be improved...
References:
Original with bug fix: See Nick's answer here:
https://stackoverflow.com/a/72180834/18248018
Kelly's recursive version:
https://stackoverflow.com/a/72189848/18248018
Iterative version: see above
You need to stack the indexes as you go, and assign the recursive function a success flag. When you perform a recursive call, you push the new index. When the function returns, in case of failure you pop and continue the search, and in case of success you do nothing but return success. In the end, the stack will be either empty or filled with the solution.
The stack can be a global variable or be passed as an argument.
As part of a dynamical programming assignment, I find myself having to do the following.
I have two sorted lists of length 2 tuples (ordered pairs, representing scores on two criteria). One pair of values can only be considered strictly greater than another if it is greater on one criterion and not lower on the other. So, (1,8) and (2,7) are incomparable, while (1,7) is lower than (2,8).
Each input list contains only values that are incomparable with each other. My method merges the two lists, omitting duplicates as well as any values that are strictly inferior to another value in the new, bigger list. Then it sorts the new list.
For example, the following input produces this result:
combine([(1,8), (2, 6), (3, 4)], [(2, 7), (3, 3)])
[(1, 8), (2, 7), (3, 4)]
Here's the code I have currently produced:
def combine(left, right):
# start with lists sorted from biggest to smallest
newlist = left+right
leftlen = len(left)
for i in range(leftlen - 1, -1, -1):
l = newlist[i] # candidate value to be inserted in
for j in range(len(newlist) - 1, leftlen - 1, -1):
r = newlist[j]
if r[0] >= l[0]: # cell with >= food encountered without having previously encountered cell with less water
if r[1] >= l[1]: # this cell also has more water - del candidate
del newlist[i]
leftlen -=1
elif r[0] == l[0]: # equal food and less water than candidate - candidate wins
del newlist[j]
break # either way, no need to consider further cells -
# if this cell doesn't beat candidate, then candidate belongs
if r[1] <= l[1]: # cell with less water encountered before cell with more food
del newlist[j]
for k in range(j -1, leftlen - 1, -1): # continue through right list, deleting cells until a value with
# higher food is found
r = newlist[k]
if r[0] > l[0]: break
else: del newlist[k]
break
newlist.sort(reverse=True)
return newlist
This code does work, but I am wondering if there is a faster approach to solving this kind of problem? When the lists are long, I end up making a lot of pairwise comparisons.
I've tried to prune out some unnecessary comparisons, relying upon the fact that items in each list are always greater on one criterion and lesser on the other. Thus, the lists are reverse sorted on the first value in the tuple, and therefore also sorted on the second value!
One idea I had was to try and use a different ADT - some type of tree perhaps, but I'm not sure if this is going to help or not.
Any suggestions? This is for an assignment, so I'm looking for ideas rather than for somebody to rewrite the whole thing for me :) Cheers!
This is my first question here so I accept any/all tips in case my question is not properly worded.
First off, the problem:
Say we have the following array A = [(1,'cat'), (1,'dog'), (0,'giraffe'), (0,'cheetah'),(1,'elephant')]
I need to use an algorithm that will sort this list based on the first element of each component (meaning the number). It is specifically mentioned that it does not have to be a stable algorithm (I guess meaning that we do not need to consider the sorting of the words after the number, just the 0s being before all the 1s)
I should also mention that I do not wish to employ the strategies of creating side-arrays that I will populate with the results as the algorithm sorts them. The requirement is for the algorithm to sort the array within the same array.
The code I have devised is the following:
def SwapAround(A, start, end):
start_pos = start
for i in range(start,end):
if A[i] < A[end]:
A[i], A[start_pos] = A[start_pos], A[i]
start_pos = start_pos + 1
A[start_pos], A[end] = A[end], A[start_pos]
return start_pos
A=[(1,'cat'),(0,'dog'),(1,'kangaroo'),(0,'mule'),(1,'giraffe'),(1,'dog'),
(0,'bat')]
SwapAround(A,0,len(A) - 1)
print(A)
I am having rather peculiar issues with it, although I am pretty confident that it should be working (at least walking down the code line by line it made good sense to me).
The code sorts the list, but not completely... for example, my most recent output was:
[(0, 'bat'), (0, 'dog'), (1, 'kangaroo'), (0, 'mule'), (1, 'giraffe'), (1, 'dog'), (1, 'cat')]
With a smaller list, it has actually worked, and I was dumbfounded to wake up this morning to add a couple more components to my list and it not working.
Could you possibly tell me if you can see something readily "wrong" that I should consider, and the rationale behind that so that I may fully comprehend it.
Second Question: If I wanted to add components to my list that had the number 2 as well (meaning my algorithm would have to sort between 0,1,2 instead of just 0 and 1) would you use a completely different algorithm or should swapping still work? My initial thought was that Swapping would not work properly (and it hasn't with this algorithm) and maybe a CountingSort or RadixSort would work better, so your input would be very much appreciated.
Your algorithm is conceptually wrong, though it's already close to the solution.
The important problem with your algorithm is that only the first element to swap will ever change. Instead you need to move both the index of the last and first element.
def sort_binary_key(arr):
l, u = 0, len(arr) - 1
while l < u:
if arr[l] == 1 and arr[u] == 0:
arr[l], arr[u] = arr[u], arr[l]
elif arr[l] == 1:
u -= 1
elif arr[u] == 0:
l += 1
else:
u -= 1
l += 1
For the second part of the question:
Yes, it's possible. Run the above algorithm twice. In the first run, move all elements with 0 to the lower part of the array, all other elements to the upper. Afterwards run the same algorithm over the second part of the array, sorting between elements with 1 and 2 as first tuple-member.
def sort_binary_key(arr, l, u, key_mapper):
while l < u:
al, au = key_mapper(arr[l]), key_mapper(arr[u])
if al == 1 and au == 0:
arr[l], arr[u] = arr[u], arr[l]
elif al == 1:
u -= 1
elif au == 0:
l += 1
else:
u -= 1
l += 1
return l
def sort_ternary(arr):
tmp = sort_binary_key(arr, 0, len(arr) - 1, lambda t: 0 if t == 0 else 1)
sort_binary_key(arr, tmp, len(arr) - 1, lambda t: 0 if t == 1 else 1)
Note that the above code is only intended as a demonstration. This code works with lists of integers instead of the input provided by OP.
Your algorithm could not work from a much deeper level.
You running an O(n) complexity general sorting algorithm. There is no algorithm for general sorting with that average complexity (if you find one, let me know!). Check this table of complexities of different sorting algorithms.
If your scenario is only with 0 and 1, and you want all the 0 at the start and 1 at the end (unstable), then it could be done with O(n)
(Check Paul's Answer to see how) and your algorithm is utterly different.
You can use python list sort and just pass the key:
A = [(1,'cat'), (1,'dog'), (0,'giraffe'), (0,'cheetah'),(1,'elephant')]
sorted(A, key=lambda x: x[0])
>>> [(0, 'giraffe'), (0, 'cheetah'), (1, 'cat'), (1, 'dog'), (1, 'elephant')]
You can also make it consider secondary key (first name and secondly a number):
B = [(1,'cat'), (0,'cat'), (0,'giraffe'), (0,'cheetah'),(1,'elephant')]
sorted(B, key=lambda x: (x[1], x[0]))
>>> [(0, 'cat'), (1, 'cat'), (0, 'cheetah'), (1, 'elephant'), (0, 'giraffe')]
Python has a default sorting and in your case, you could simply write:
C = [(1,'cat'), (0,'cat'), (0,'giraffe'), (0,'cheetah'),(1,'elephant')]
sorted(C)
>>> [(0, 'cat'), (1, 'cat'), (0, 'cheetah'), (1, 'elephant'), (0, 'giraffe')]
I'm trying to create a function that returns the largest element of an array, I feel I have the correct code but my syntax is in the wrong order, I'm trying to use a for/while loop in order to do so. So far I have the following:
def manindex(arg):
ans = 0
while True:
for i in range (len(arg)):
if arg[i] > arg[ans]:
pass
ans = i
return ans
Not sure where I'm going wrong if anyone could provide some guidance, thanks
EDIT: So it's been pointing out I'm causing an infinite loop so if I take out the while statement I'm left with
def manindex(arg):
ans = 0
for i in range (len(arg)):
if arg[i] > arg[ans]:
ans = i
return ans
But I have a feeling it's still not correct
When you say array I think you mean list in Python, you don't need a for/loop or while/loop to achieve this at all.
You can also use index with max, like so:
xs.index(max(xs))
sample:
xs = [1,123,12,234,34,23,42,34]
xs.index(max(xs))
3
You could use max with the key parameter set to seq.__getitem__:
def argmax(seq):
return max(range(len(seq)), key=seq.__getitem__)
print(argmax([0,1,2,3,100,4,5]))
yields
4
The idea behind finding the largest index is always the same, iterating over the elements of the array, compare to the max value we have at the moment, if it's better, the index of the current element is the maximum now, if it's not, we keep looking for it.
enumerate approach:
def max_element_index(items):
max_index, max_value = None, None
for index, item in enumerate(items):
if item > max_value:
max_index, max_value = index, item
return max_index
functional approach:
def max_element_index(items):
return reduce(lambda x,y: x[1] > y[1] and x or y,
enumerate(items), (None, None))[0]
At the risk of looking cryptic, the functional approach uses the reduce function which takes two elements and decides what is the reduction. Those elements are tuples (index, element), which are the result of the enumerate function.
The reduce function, defined on the lambda body takes two elements and return the tuple of the largest. As the reduce function reduces until only one element in the result is encountered, the champion is the tuple containing the index of the largest and the largest element, so we only need to access the 0-index of the tuple to get the element.
On the other hand if the list is empty, None object is returned, which is granted on the third parameter of the reduce function.
Before I write a long winded explanation, let me give you the solution:
index, value = max(enumerate(list1), key=lambda x: x[1])
One line, efficient (single pass O(n)), and readable (I think).
Explanation
In general, it's a good idea to use as much of python's incredibly powerful built-in functions as possible.
In this instance, the two key functions are enumerate() and max().
enumerate() converts a list (or actually any iterable) into a sequence of indices and values. e.g.
>>> list1 = ['apple', 'banana', 'cherry']
>>> for tup in enumerate(list1):
... print tup
...
(0, 'apple')
(1, 'banana')
(2, 'cherry')
max() takes an iterable and returns the maximum element. Unfortunately, max(enumerate(list1)) doesn't work, because max() will sort based on the first element of the tuple created by enumerate(), which sadly is the index.
One lesser-known feature of max() is that it can take a second argument in the form max(list1, key=something). The key is a function that can be applied to each value in the list, and the output of that function is what gets used to determine the maximum. We can use this feature to tell max() that it should be ranking items by the second item of each tuple, which is the value contained in the list.
Combining enumerate() and max() with key (plus a little help from lambda to create a function that returns the second element of a tuple) gives you this solution.
index, value = max(enumerate(list1), key=lambda x: x[1])
I came up with this recently (and am sprinkling it everywhere in my code) after watching Raymond Hettinger's talk on Transforming Code into Beautiful, Idiomatic Python, where he suggests exorcising the for i in xrange(len(list1)): pattern from your code.
Alternatively, without resorting to lambda (Thanks #sweeneyrod!):
from operator import itemgetter
index, value = max(enumerate(list1), key=itemgetter(1))
I believe if you change your for loop to....
for i in range (len(arg)):
if arg[i] > ans:
ans = arg[i]
it should work.
You could try something like this. If the list is empty, then the function will return an error.
m is set to the first element of the list, we then iterate over the list comparing the value at ever step.
def findMax(xs):
m = xs[0]
for x in xs:
if x > m:
m = x
return m
findMax([]) # error
findMax([1]) # 1
findMax([2,1]) # 2
if you wanted to use a for loop and make it more generic, then:
def findGeneric(pred, xs):
m = xs[0]
for x in xs:
if pred(x,m):
m = x
return m
findGeneric(lambda a,b: len(a) > len(b), [[1],[1,1,1,1],[1,1]]) # [1,1,1,1]
I have to check if list1 is contained in list2. It also should check if it appears in that order in the list as well. If it's true, it should return true and false if it doesn't.
def check(lst1, lst2):
for x in lst1:
for y in lst2:
xx = lst1.sort()
yy = lst2
if xx != yy:
return False
else:
return True
I'm confusing myself with the for loops and also, I don't know where to go from here to fix my code. Pointers please?
example of what it should do:
check([4,0],[9,1,4,6,8,9])
True
check([1,2],[2,3,1])
False
I thought the problem was begging to be solved recursively, so I did:
def check(needle, haystack):
if not needle:
return True
try:
offset = haystack.index(needle[0])
return check(needle[1:], haystack[offset+1:])
except:
return False
Edit:
Brief explanation:
First we check if the list we're looking for is empty (this is important once we start calling ourselves), since all lists have the empty list inside it, we return True. Then we try finding the first item of the list we're looking for in the list we're looking at. If we find it, we then call the function again, but change the arguments a bit: we already looked at the first item of the 'needle' and saw it in the 'haystack'. So now we need to check if the remainder of 'needle' is in the remainder of 'haystack'. So we call the function again only with the remainder of both lists. The remainder of needle is everything but the first element, and the remainder of haystack is all items after the one we found. If we get to a point where the first list is empty, it means we found it in the haystack. If we get an exception, what we were looking for wasn't found, so we return False, which bubbles up the call stack and gets returned.
You could start with something like:
set(lst1).issubset(lst2)
to see if lst1 is contained within lst2 ignoring order. If it passes the test that one list is contained within the other, then you could do something like:
ii = lst2.index(lst1[0])
if lst2[ii:ii+len(lst1)] == lst1:
return True
else:
return False
Originally I stated that the first check was irrelevant given the second, although you'd have to properly handle the ValueError if the first element of lst1 is not in lst2.
Edit:
Just as a side note, I compared a version of my code vs yan's and mine is significantly faster under almost all use cases, especially if len(lst1) is larger (up to 200x speedup vs yan's implementation). Give it a try with the timeit module.
def check(lst1,lst2):
try:
ii = lst2.index(lst1[0])
except ValueError:
return False
if lst2[ii:ii+len(lst1)] == lst1:
return True
else:
return False
As an explanation of how it works ii = lst2.index(lst1[0]) finds the index in lst2 that matches the first element of lst1. If that item is missing from lst2 it catches the ValueError and returns False. If that element does exist, lst2[ii:ii+len(lst1)] == lst1 compares all of lst1 vs the sublist of lst2 starting from the matched element and taking the next len(lst) elements.
>>> a=[9,1,4,6,8,9]
>>> by_twos=zip(a, a[1:])
>>> by_twos
[(9, 1), (1, 4), (4, 6), (6, 8), (8, 9)]
>>> (4,6) in by_twos
True