Why is 'int" not iterable in Python, but 'str' are? - python

I'm getting some practice with list comprehensions and I ran into the following error:
Find all of the numbers from 1-1000 that have a 3 in them
result = [i for i in range(1, 1001) if 3 in i]
print(result)
result = [i for i in range(1, 1000) if 3 in i]
TypeError: argument of type 'int' is not iterable
but it works perfectly if I write this code:
result = [i for i in range(1, 1001) if "3" in str(i)]
print(result)
...So clearly strings are iterable. But ints are not. Why?

Because it’s not clear what iterating over an int would do. You seem to expect that it iterates over the decimal digits (?) but I for example find that unreasonable: I’d find it much more natural to iterate over its bits (i.e. its binary digits). Inside memory, ints are represented as binary numbers, not decimal numbers, so I could argue that mine is the more natural expectation.
But since there’s no obviously right answer, Python’s designers decided not to make int iterable at all.
By contrast, for strings there’s an immediately intuitive “right” answer (although the devil is in the details): we iterate over its characters. Unfortunately that actually opens a can of worms since whole books have been written about the definition of “character”, and in practice something subtly different happens (if you’re curious, start reading The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)). But the answer “we iterate over characters” is close enough.

From a conceptual perspective, the answer by #KonradRudolph is the right one.
But from a technical perspective, what makes an object able to be iterated over? This property is called being iterable. And in Python, an object is iterable as long as it implements the magic method __iter__. Magic methods are special methods Python looks for in a class if it wants to use certain built-in behavior. Like iterating over an object, or adding two objects with +.
And so, we could easily implement our own number class that is iterable:
class IterableInt:
def __init__(self, i):
self.i = i
def __iter__(self):
return map(int, str(self.i))
Here I chose iterable to mean what you expected, i.e. iterating over the decimal digits. Iterating over the binary digits instead would only mean changing one line.
Note that this is not really a number (yet), you cannot add or multiply it. For that one would probably want to inherit from int, but that complicates things a bit more and is left as an exercise.
In any case, now we have an iterable integer:
n = IterableInt(123)
print(3 in n)
# True
print(list(n))
# [1, 2, 3]
If you need to find out if an object is iterable, there are two ways. One is called "Look before you leap" (LBYL), you can test for the existence of the __iter__ method or use the typing module:
hasattr(n, "__iter__")
# True
hasattr(123, "__iter__")
# False
from typing import Iterable
isinstance(n, Iterable)
# True
isinstance(123, Iterable)
# False
And the other is "it’s easier to ask for forgiveness than permission" (EAFP), where you just assume the object is iterable and deal with it if it is not:
try:
3 in 123
except TypeError:
print("too bad, should have used IterableInt")

Because a string is an array of characters. An int is not a sequence of anything (it can be represented as a sequence of digits, but that is not what it is).

if you want to check the int, you have to match with every digit in the int by every tenth place.
from math import floor
def check_if_number_exists(number, digit_to_find):
# check if the number is positive
if number < 0:
Number = -number
else:
Number = number
while(Number != 0):
# run loop for extracting each digit
Digit = Number % 10 # gives the last number
Number = floor(Number / 10) # removing the last digit
if Digit == digit_to_find:
print("the number contains " + str(digit_to_find) + ' in ' + str(number))
break
check_if_number_exists(45638, 3)
you check any number you want with above code.
you cannot do the same way you did for str because int is different from string. A small example
s + e + v + e + n = seven (string is array which comprise of letters)
43567 = 40000 + 3000 + 500 + 60 + 7 (not 4+3+5+6+7)
hope you understand, why iteration dont work on int

Related

I don't see my error here - Trying to learn Python

I'm trying to become comfortable with python. I've been trying some simple activities that I've given in my beginning c++ classes when I was teaching. I did one involving functions and writing a file which worked flawlessly. I thought this one would be easier. It acts like it is in a silent endless loop, but it won't even let me trace it. Can someone see where I am going awry?
# Find Adam Numbers
def isAdamNumber(candidate):
isAdam = False
rev = reverse(candidate)
square = candidate * candidate
revsq = rev*rev
if revsq == reverse(square):
isAdam = True
return isAdam
def reverse(num):
rev=0
while num > 0:
rev = rev * 10 + num%10
num/=10
return rev
for x in range (11,25):
if isAdamNumber(x):
print(x, " is an adam number\n")
The quick fix is to change /= with the integer division version, //=
Inside the reverse function, you are going into an infinite loop. num value always will be greater than 0, therefore the while loop will continuously run. In python, you can get the reverse of the function without much effort. Convert the integer to string and reverse the string and now change the string back to integer.
def reverse(num):
num_str = str(num)[::-1]
return int(num_str)
I think this function definition can solve your problem.
To visualize the python to learn and teach, use this link
The problem has already been addressed by the other answers, so here's the expanded and simplified version of the slicing that's going on [this doesn't actually use slicing]:
def reverse(num):
rev = ''
num = str(num)
for i in range(len(num) - 1, -1, -1):
rev += num[i]
return int(rev)
This counts backward from the last element in the string version of num, and adds all the elements of num (in reverse order) to rev.
num > 0 is never False. Dividing a positive number by 10 repeatedly makes it smaller, but it never becomes zero, so the while loop keeps repeating.
Use //= instead. It rounds to the nearest integer, so it will reach 0.
This also wouldn't reverse numbers (unless I'm missing something). Alternatively, you can use
int(str(num)[::-1])
which converts the number to a string, reverses it using slicing, and turns it back into an integer.

Why does the range(first_num, second_num) not include second_num? [duplicate]

This question already has answers here:
Why are slice and range upper-bound exclusive?
(6 answers)
Closed last month.
>>> range(1,11)
gives you
[1,2,3,4,5,6,7,8,9,10]
Why not 1-11?
Did they just decide to do it like that at random or does it have some value I am not seeing?
Because it's more common to call range(0, 10) which returns [0,1,2,3,4,5,6,7,8,9] which contains 10 elements which equals len(range(0, 10)). Remember that programmers prefer 0-based indexing.
Also, consider the following common code snippet:
for i in range(len(li)):
pass
Could you see that if range() went up to exactly len(li) that this would be problematic? The programmer would need to explicitly subtract 1. This also follows the common trend of programmers preferring for(int i = 0; i < 10; i++) over for(int i = 0; i <= 9; i++).
If you are calling range with a start of 1 frequently, you might want to define your own function:
>>> def range1(start, end):
... return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Although there are some useful algorithmic explanations here, I think it may help to add some simple 'real life' reasoning as to why it works this way, which I have found useful when introducing the subject to young newcomers:
With something like 'range(1,10)' confusion can arise from thinking that pair of parameters represents the "start and end".
It is actually start and "stop".
Now, if it were the "end" value then, yes, you might expect that number would be included as the final entry in the sequence. But it is not the "end".
Others mistakenly call that parameter "count" because if you only ever use 'range(n)' then it does, of course, iterate 'n' times. This logic breaks down when you add the start parameter.
So the key point is to remember its name: "stop".
That means it is the point at which, when reached, iteration will stop immediately. Not after that point.
So, while "start" does indeed represent the first value to be included, on reaching the "stop" value it 'breaks' rather than continuing to process 'that one as well' before stopping.
One analogy that I have used in explaining this to kids is that, ironically, it is better behaved than kids! It doesn't stop after it supposed to - it stops immediately without finishing what it was doing. (They get this ;) )
Another analogy - when you drive a car you don't pass a stop/yield/'give way' sign and end up with it sitting somewhere next to, or behind, your car. Technically you still haven't reached it when you do stop. It is not included in the 'things you passed on your journey'.
I hope some of that helps in explaining to Pythonitos/Pythonitas!
Exclusive ranges do have some benefits:
For one thing each item in range(0,n) is a valid index for lists of length n.
Also range(0,n) has a length of n, not n+1 which an inclusive range would.
It works well in combination with zero-based indexing and len(). For example, if you have 10 items in a list x, they are numbered 0-9. range(len(x)) gives you 0-9.
Of course, people will tell you it's more Pythonic to do for item in x or for index, item in enumerate(x) rather than for i in range(len(x)).
Slicing works that way too: foo[1:4] is items 1-3 of foo (keeping in mind that item 1 is actually the second item due to the zero-based indexing). For consistency, they should both work the same way.
I think of it as: "the first number you want, followed by the first number you don't want." If you want 1-10, the first number you don't want is 11, so it's range(1, 11).
If it becomes cumbersome in a particular application, it's easy enough to write a little helper function that adds 1 to the ending index and calls range().
It's also useful for splitting ranges; range(a,b) can be split into range(a, x) and range(x, b), whereas with inclusive range you would write either x-1 or x+1. While you rarely need to split ranges, you do tend to split lists quite often, which is one of the reasons slicing a list l[a:b] includes the a-th element but not the b-th. Then range having the same property makes it nicely consistent.
The length of the range is the top value minus the bottom value.
It's very similar to something like:
for (var i = 1; i < 11; i++) {
//i goes from 1 to 10 in here
}
in a C-style language.
Also like Ruby's range:
1...11 #this is a range from 1 to 10
However, Ruby recognises that many times you'll want to include the terminal value and offers the alternative syntax:
1..10 #this is also a range from 1 to 10
Consider the code
for i in range(10):
print "You'll see this 10 times", i
The idea is that you get a list of length y-x, which you can (as you see above) iterate over.
Read up on the python docs for range - they consider for-loop iteration the primary usecase.
Basically in python range(n) iterates n times, which is of exclusive nature that is why it does not give last value when it is being printed, we can create a function which gives
inclusive value it means it will also print last value mentioned in range.
def main():
for i in inclusive_range(25):
print(i, sep=" ")
def inclusive_range(*args):
numargs = len(args)
if numargs == 0:
raise TypeError("you need to write at least a value")
elif numargs == 1:
stop = args[0]
start = 0
step = 1
elif numargs == 2:
(start, stop) = args
step = 1
elif numargs == 3:
(start, stop, step) = args
else:
raise TypeError("Inclusive range was expected at most 3 arguments,got {}".format(numargs))
i = start
while i <= stop:
yield i
i += step
if __name__ == "__main__":
main()
The range(n) in python returns from 0 to n-1. Respectively, the range(1,n) from 1 to n-1.
So, if you want to omit the first value and get also the last value (n) you can do it very simply using the following code.
for i in range(1, n + 1):
print(i) #prints from 1 to n
It's just more convenient to reason about in many cases.
Basically, we could think of a range as an interval between start and end. If start <= end, the length of the interval between them is end - start. If len was actually defined as the length, you'd have:
len(range(start, end)) == start - end
However, we count the integers included in the range instead of measuring the length of the interval. To keep the above property true, we should include one of the endpoints and exclude the other.
Adding the step parameter is like introducing a unit of length. In that case, you'd expect
len(range(start, end, step)) == (start - end) / step
for length. To get the count, you just use integer division.
Two major uses of ranges in python. All things tend to fall in one or the other
integer. Use built-in: range(start, stop, step). To have stop included would mean that the end step would be assymetric for the general case. Consider range(0,5,3). If default behaviour would output 5 at the end, it would be broken.
floating pont. This is for numerical uses (where sometimes it happens to be integers too). Then use numpy.linspace.

How are values tracked with the 'for el in sequence' syntax?

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.

How do I check if each digit of a number is less than 2? (python) [duplicate]

This question already has answers here:
How to check if a input is in binary format(1 and 0)?
(8 answers)
Closed 7 years ago.
I'm trying to create a program that checks if each digit of a given number is less than 2, possibly using
range(len(a)):
def is_bin(number):
try:
int(str(number), 2)
except ValueError:
return False
return True
You can convert number to string and check like this:
all(int(c) < 2 for c in str(n))
For example:
>>> all(int(c) < 2 for c in str(1011))
True
>>> all(int(c) < 2 for c in str(1211))
False
You can try this
num = 123457
>>>all(int(i)<2 for i in str(num))
False
num = 11011
>>>all(int(i)<2 for i in str(num))
True
Python is dynamically typed, so this is relatively easy to do. I would suggest converting your integer into a string, then iterating through the string and checking each digit.
Using that method, the code would probably look something like:
your_number_string = str(your_number)
for d in range(0, len(your_number_string)):
i = your_number_string[d]
if (int(i) > 2):
raise new Exception("Digit " + str(i) + "is not less than 2")
Couple things to note with this: It's bad practice to throw bare Exceptions. If you like the exception route, then extend the exception class and make your own. This also assumes that your number is a valid integer. Finally, this also will only alert you for the first digit larger than two, it won't tell you about any subsequent ones that also are larger than 2. This will require some adjustments for negative numbers and floats.
Could use the good ol' fashioned method :) just take your number and extract each digit and then update your number.
while yournumber != 0 :
digit = yournumber % 10
yournumber = younumber / 10
if digit < 2
do stuff
but I'm sure there are easier (maybe not as fast) ways.
At the moment, all solutions depend on converting the number to a string. While that works, you could do it completely numerically:
def check_digits(nbr, max=1):
nbr = abs(nbr) # only look at a positive numbers
while nbr != 0:
nbr, rem = divmod(nbr, 10)
if rem > max: return False
return True
The trick here is that each time the digit furthest to the right gets inspected. Once that's done, the number gets divided by 10, ignoring the remainder. So 1201 becomes 120, which becomes 12 and the cycle stops there, because divmod(12, 10) is (1, 2), and the remainder, 2, is bigger than your maximum digit.
Note that TigerhawkT3's answer and #John La Rooy's comment probably nailed it (upvoted) as it is by far the most Pythonic solution. All others, including mine, work for various programming languages, TigerhawkT3's solution uses Python exceptions to good effect.

Forcing a variable to be an integer

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.

Categories

Resources