I am trying to grasp what mechanism is keeping track of values being returned to the sum() function in the following:
def narcissisticNumber(value):
return value == sum(int(x) ** len(str(value)) for x in str(value))
From what I see, x ** y is being calculated for each character in the value string - however, since the 'for char in str' construct is within the sum function, the result of each of these computations is being returned to sum(). The return value of sum() accounts for all iterations, and I would like to know how sum() was able to track all the return values of the x ** y expression.
I am using Python 3.7.4. Coming from Java, I understand the function does not follow best practices, I would just like to know how it works in terms of Python.
What you are using is a Generator:
Generators are a simple and powerful tool for creating iterators. They
are written like regular functions but use the yield statement
whenever they want to return data. Each time next() is called, the
generator resumes where it left-off (it remembers all the data values
and which statement was last executed)
Check here for better understanding: https://wiki.python.org/moin/Generators
I think a good way of understanding it better would be to break it down to a more readable format:
def narcissisticNumber(value):
listOfValues = []
lenOfValue = len(str(value))
# Iterate through each character in your string
for x in str(value):
# Get the int-value of that character
# then raise it to the length of your string
xRaiseToLength = int(x) ** lenOfValue
# Append that value to your final list
listOfValues.append(xRaiseToLength)
# Compute the sum of the list using sum()
sumOfValues = sum(listOfValues)
return value == sumOfValues
References:
sum()
Okey lets break it into parts:
str(value) is casting value into a string (str)
len(str(value)) is getting the length of this string
... for x in str(value) is a generator expression. It will assign to x each element in str(value) (this is, each character) and evaluate the first clause. To simplify this, you can think that it returns an array. It actually doesn't, but conceptually yopu can think of it like that. It is actually creating a special construct that can be iterated over and that evaluates each element when they are requested, so it is way more memory efficent that creating an array.
int(x) is casting x (which is each of the characters of the string) into an integer (int).
int(x) ** len(str(value)) is calculating x to the power of the length of the complete string.
sum is a fucntion that accepts any iterable construct (in this case a generator, but it could be an array of some sort) and adds its elements.
So in conclussion, if you do narcissisticNumber(123) it will return 1^3 + 2^3 + 3^3 = 1 + 8 + 27 = 36.
Related
So I was trying to complete this kata on code wars and I ran across an interesting solution. The kata states:
"Given an array of integers, find the one that appears an odd number of times.
There will always be only one integer that appears an odd number of times."
and one of the solutions for it was:
def find_it(seq):
return [x for x in seq if seq.count(x) % 2][0]
My question is why is there [0] at the end of the statement. I tried playing around with it and putting [1] instead and when testing, it passed some tests but not others with no obvious pattern.
Any explanation will be greatly appreciated.
The first brackets are a list comprehension, the second is indexing the resulting list. It's equivalent to:
def find_it(seq):
thelist = [x for x in seq if seq.count(x) % 2]
return thelist[0]
The code is actually pretty inefficient, because it builds the whole list just to get the first value that passed the test. It could be implemented much more efficiently with next + a generator expression (like a listcomp, but lazy, with the values produced exactly once, and only on demand):
def find_it(seq):
return next(x for x in seq if seq.count(x) % 2)
which would behave the same, with the only difference being that the exception raised if no values passed the test would be IndexError in the original code, and StopIteration in the new code, and it would operate more efficiently by stopping the search the instant a value passed the test.
Really, you should just give up on using the .count method and count all the elements in a single pass, which is truly O(n) (count solutions can't be, because count itself is O(n) and must be called a number of times roughly proportionate to the input size; even if you dedupe it, in the worst case scenario all elements appear twice and you have to call count n / 2 times):
from collections import Counter
def find_it(it):
# Counter(it) counts all items of any iterable, not just sequence,
# in a single pass, and since 3.6, it's insertion order preserving,
# so you can just iterate the items of the result and find the first
# hit cheaply
return next(x for x, cnt in Counter(it).items() if cnt % 2)
That list comprehension yields a sequence of values that occur an odd number of times. The first value of that sequence will occur an odd number of times. Therefore, getting the first value of that sequence (via [0]) gets you a value that occurs an odd number of times.
Happy coding!
That code [x for x in seq if seq.count(x) % 2] return the list which has 1 value appears in input list an odd numbers of times.
So, to make the output as number, not as list, he indicates 0th index, so it returns 0th index of list with one value.
There is a nice another answer here by ShadowRanger, so I won't duplicate it providing partially only another phrasing of the same.
The expression [some_content][0] is not a double list. It is a way to get elements out of the list by using indexing. So the second "list" is a syntax for choosing an element of a list by its index (i.e. the position number in the list which begins in Python with zero and not as sometimes intuitively expected with one. So [0] addresses the first element in the list to the left of [0].
['this', 'is', 'a', 'list'][0] <-- this an index of 'this' in the list
print( ['this', 'is', 'a', 'list'][0] )
will print
this
to the stdout.
The intention of the function you are showing in your question is to return a single value and not a list.
So to get the single value out of the list which is built by the list comprehension the index [0] is used. The index guarantees that the return value result is taken out of the list [result] using [result][0] as
[result][0] == result.
The same function could be also written using a loop as follows:
def find_it(seq):
for x in seq:
if seq.count(x) % 2 != 0:
return x
but using a list comprehension instead of a loop makes it in Python mostly more effective considering speed. That is the reason why it sometimes makes sense to use a list comprehension and then unpack the found value(s) out of the list. It will be in most cases faster than an equivalent loop, but ... not in this special case where it will slow things down as mentioned already by ShadowRanger.
It seems that your tested sequences not always have only one single value which occurs an odd number of times. This will explain why you experience that sometimes the index [1] works where it shouldn't because it was stated that the tested seq will contain one and only one such value.
What you experienced looking at the function in your question is a failed attempt to make it more effective by using a list comprehension instead of a loop. The actual improvement can be achieved but by using a generator expression and another way of counting as shown in the answer by ShadowRanger:
from collections import Counter
def find_it(it):
return next(x for x, cnt in Counter(it).items() if cnt % 2)
The biggest problem for me is to express the value of mid according to the range.
plz I need a help Even if it's not a perfect answer, I need a hint. I don't even know if the reason I'm having a hard time is that Python is awkward or that I'm stupid.
def bsearch(ss, x): #just call bsearch_range
return bsearch_range( ss, x, range(len(ss)) )
def bsearch_range(ss, x, r): #ss is list, r=range(len(ss))
left,right=0,0
while len(r)>0:
mid= (r.start+(r.stop-1)) // 2
if ss[mid]==x:
left,right=mid,mid+1
return range(left,right)
elif x<ss[mid]:
right=right-1
else:
left=left+1
return range(left,right)
First. The bsearch function is a binary search function that tells you the range you want to find in range.
Second. Lists are sorted and may contain duplicate numbers.
For example) list ss=[1,2,2,2,3,6]
So. If duplicate elements exist in the list, we need to express the range of duplicate elements.
For example) list ss[1,2,2,2,3,6] , bsearch(ss,2)==range(1,4)
Third. If the element you want to find does not exist in the list, you should be able to tell the place where it should be in range.
For example) ss[1,2,2,2,3,6] bsearch(ss,7)==range(6,6)
Looking at your code, I believe you did not fully understand how the "normal" binary search algorithm works. (What is r for? It does not change throughout the loop body, so the while loop goes on forever...)
As a hint, this is what binary search in Python looks like:
def bi_search_left(array: list[int], x: int) -> int:
a, b = 0, len(array)
while a < b:
mid = (a + b) // 2
if array[mid] < x:
a = mid + 1
else:
b = mid
return a
Notice, the name of the function is bi_search_left. That is because it returns the index of the leftmost element matching the value of the x parameter. (Actually, it returns the index at which x needs to be inserted in in order for it to become the first element in the list with this value, so it also works for empty lists.)
By changing the function just a tiny bit, you could get bi_search_right, which, as the name says, returns the rightmost index. Combining these two methods could get you the desired result.
I think that was enough of a hint :)
I'm trying to get the minimum odd number using python. I used lambda, loops and other methods to get minimum odd number but i was not able to get that using functions. here is my code
z= [1,8,-4,-9]
def min_odd(x):
for i in x:
if (i%2!=0):
return min(i)
y = min_odd(z)
print (y)
Can some please tell me what i was missing here.
The min() function expects an iterable like a list which it will then yield the smallest element from.
E.g. min([1,0,3]) gives 0.
So if you want to use it, you must create a list (or other iterable) of the odd numbers that you can then pass into it:
def min_odd(x):
odds = []
for i in x:
if i % 2 != 0:
odds.append(i)
return min(odds)
note that we could also use a list-comprehension:
def min_odd(x):
return min([i for i in x if i % 2 != 0])
which both work.
An alternative method would be to store the current minimum odd value in a variable and update this variable if we come across a smaller odd value:
def min_odd(x):
min_v = float('inf')
for i in x:
if i % 2 != 0 and i < min_v:
min_v = i
return min_v
Try:
min([val for val in z if val % 2 != 0])
It seems your code logics are wrong. First off, you seem to have an indentation error in the return statement. Second off, the min() function requires a collection of items (like an array for example) or a series of arguments to determine the minimum in that series. You can try multiple things.
Use another variable to store a temporary minimum. Replace it every time you find a smaller odd value ( for every i in x... if the value is odd and is smaller than the previous odd value, replace it) and have it started with the first odd number you can find.
Take all the odd numbers and add them to another array on which you will apply the min function.
Hope this proves useful!
You could pass a generator into the min() function:
def min_odd(iterable):
return min(i for i in iterable if i % 2)
I didn't write i % 2 != 0 because any odd number will return 1 which has a Boolean value of True.
I added a parameter to the function that takes the iterable so it can be used for any iterable passed in.
min operates on an iterable. i is not an iterable in your code; it's the last element of the list.
You can achieve what you want with a filter, though:
min(filter(lambda e: e%2 != 0, x))
So I've recently picked up John Guttag's Introduction to Computation and Programming Using Python,the revised and expanded edition, after having worked through most of LPTHW. I am using the book in conjunction with MIT OCW 006. Now, I was trying to complete one of the Finger Exercises listed in the book, specifically the one of page 85, chapter 7, where the author asks you to implement a function using a try-except block:
def sumDigits(s):
"""Assumes s is a string
Returns the sum of the decimal digits in s
For example, if is is'a2b3c' it returns 5"""
This is my code:
def sumDigits(s):
try:
total = 0
list1 = [s]
new_list = [x for x in list1 if x.isdigit()]
for e in new_list:
total += new_list[e]
return total
except TypeError:
print "What you entered is not a string."
When I run this program in the IDLE using a test input, the total is always computed to be zero, indicating that none of the elements of new_list are being passed to the accumulator. Could someone suggest why that is? Thanks.
It seems like the errors have been pointed out already by Rafael but it is still important to note that the more pythonic way to approach this would be:
return sum([int(x) for x in s if x.isdigit()])
There are actually several errors with your code.
Let's break them down in detail
The main problem is located in these lines:
list1 = [s]
new_list = [x for x in list1 if x.isdigit()]
You should loop directly over the string first
new_list = [x for x in s if x.isdigit()] #s is the input string
When you create a new list as you did, the variable x in x for x in list1 will take place as elements of the list. So, in your case, the list will have only one element, which happen to be whole string (because you defined the list as [s]. As the whole string is not a digit, new_list will be an empty list.
That is why you are getting 0 as a return.
However, if you loop through the string directly, x will take place as each letter in the string, and then it will be possible to check whether x is digit or not.
It is also important to highlight that new_list[e] will raise IndexError. You should correct that for e only. The sintax of for e in new_list makes the local variable e assume each value inside the list, so you do not have to get the value via indexes: you can use e directly.
Finally, in order to sum the values in your new_list, the values should be integers (int) and not string (str), so you have to cast the values to int before summing (or, you can cast each element to int during the list comprehension, by using int(x) for x in s if x.isdigit() instead of x for x in s if x.isdigit()). Also, in order to check if the input is a string or not, you better use isinstance(s, basestring) if you're in python2, or isinstance(s, str) if you're using python3.
So the whole code would look like this :
def sumDigits(s):
if isinstance(s, basestring):
total = 0
new_list = [x for x in s if x.isdigit()]
for e in new_list:
total += int(e)
return total
else:
print "What you entered is not a string."
I'm working through the same book and the MITx: 6.00.1x course on edX; here's my solution:
def sumDigits(s):
'''
Assumes s is a string
Returns the sum of the decimal digits in s
For example, if s is 'a2b3c' it returns 5
'''
result = 0
try:
for i in range(len(s)):
if s[i].isdigit():
result += int(s[i])
return result
except:
print('Your input is not a string.')
Since we are to assume that s is a string, the except block should handle those cases where s is not a string. So simple, but it was not obvious to me at first.
You can use reduce method
reduce( (lambda x, y: x + y), [int(x) for x in new if x.isdigit()] )
I'm working through the same book too. I think we should use the try-except block on determining whether characters of string convertible to an integer. So here is my solution.
def sumDigits(s):
"""Assumes s is a string
Returns the sum of the decimal digits in s
For example, if s is 'a2b3c' it returns 5"""
sum = 0
for i in s:
try:
sum += int(i)
except ValueError:
None
return sum
errors = int(0)
for i in range(len(expectedData)):
if data[i] != expectedData[i]:
errors += int(binary_compare(data[i], expectedData[i]))
return errors
I have the above code which I am trying to use to calculate some integer (number of errors) for some data. I have casted everything I can see possible as an integer, yet the line "errors += ..." still appends the value, rather than adds it.
For example, if my answer should be 7, I might get 500002. (5 + 0 + 0 + .. + 2). I have never run into this before. The function binary_compare is returning an integer as well, but I'm completely in the dark as to why this isn't working.
python is not javascript
it's no way to get concatenated strings instead of math sum, when you do count += value starting with count = 0. if you try to add a string to integer, exception is raised:
>>> x = 0
>>> x += "1"
TypeError: unsupported operand type(s) for +=: 'int' and 'str'
to compare values of which you don't know whether they are strings or integers, i'd use
str(data[i]).strip() == str(expectedData[i]).strip()
for noninteger-proof math sum, you might want to do something like this
try:
value = int(expectedData[i])
except:
value = 0
count += value
I think the error is outside of your code, but anyway, in Python, list operations are seldom done with loops, as this focuses on the implementation rather on the purpose. List comprehension, generators etc are preferred, and there are also many built-in and standard library functions for common tasks.
In your case, I would write the function as
return sum(binary_compare(x, y) for x, y in zip(data, expectedData) if x != y)
If you are using Python 2.x, itertools.izip should be used instead of zip.