Finding if a number is a perfect square using maths trickery - python

First post here;
I'm trying to find if an inputted number is a perfect square. This is what i've come up with (im a complete first timer noob)
import math
num = int(input("enter the number:"))
square_root = math.sqrt(num)
perfect_square = list[1, 4, 5, 6, 9, 00]
ldigit = num%10
if ldigit in perfect_square:
print(num, "Is perfect square")
The list are digits that if the integer ends in, it will be a perfect square.
perfect_square = list[1, 4, 5, 6, 9, 00]
TypeError: 'type' object is not subscriptable
Never seen this before (surprise). Apologies if it's a total mess of logic and understanding.

You have an error in your code:
perfect_square = list[1, 4, 5, 6, 9, 00]
Should be:
perfect_square = ['1', '4', '5', '6', '9', '00']
Secondly these are defined as ints, so you cannot have a number 00, instead convert everything to string to do the check and then back to ints with str and int.
Personally I'd rather go with another approach:
import math
num = int(15)
square_root = math.sqrt(num)
if square_root == int(square_root):
print(f"{num} is a perfect square")
else:
print(f"{num} is not a perfect square")

You declare a list without the keyword 'list', like this:
perfect_square = [1, 4, 5, 6, 9, 00]

We dont need list keyword in order to create List object in python .
List is a python builtin type. List literals are written within square brackets [].
For Example :
squares = [1, 4, 9, 16]
squares is a List here .
Ashutosh

Related

my list value changing incorrectly - python

im trying to generate mastercard card number.
requirements :
first element must be 5
second element must be between 1 and 5
last element must be lcheck digit returned from luhn algorithm.
i have check digit function with luhn algorithm, so far everything is okay.
but when i give parameter my card number to generateCheckDigit function in generateMasterCard function, my card number is returned as multiplied by 2, one element apart during the luhn algorithm.
sorry for my bad english
here is the codes:
def generateCheckDigit(numbers):
if len(numbers)%2 == 1:
for i in range(0,len(numbers),2):
numbers[i] *= 2
else:
for i in range(1,len(numbers),2):
numbers[i] *= 2
check_digit = (sum(numbers)*9) % 10
return check_digit
def generateMasterCard():
card_number = [5, rd.randint(1,5)]
for i in range(13):
card_number.append(rd.randint(0,9))
print(f"first number : {card_number}")
check_digit = generateCheckDigit(card_number)
card_number.append(check_digit)
return card_number
output :
first number : [5, 4, 1, 4, 0, 8, 4, 8, 0, 4, 2, 8, 8, 2, 9]
[10, 4, 2, 4, 0, 8, 8, 8, 0, 4, 4, 8, 16, 2, 18, 4]
You can import copy and use generateCheckDigit(copy.copy(card_number)) as
Alexey Larionov sais in comments "In Python if you pass to a function some complicated value, like class instance, list, dictionary, etc, then your function can freely modify it. In your case, you do operation numbers[i] *= 2 and it changes the list you passed". Passing a copy allows you to avoid this.

Does anyone know why Python .sort() is not working as Intended?

This is just a simple python code to print out the divisors of a number.
First, it checks if the number can be divided by (x) and appends (x) and (number/x) to an empty list. It then sorts and converts the list to a set (to remove any duplicates)
import math
number = int(input("Enter a number : "))
divisors = list()
for x in range(1, math.ceil(math.sqrt(number))):
if(number%x == 0):
divisors.append(x)
divisors.append(int(number/x))
divisors.sort()
print(set(divisors))
Output :
Enter a number : 56
{1, 2, 4, 7, 8, 14, 56, 28}
Enter a number : 60
{1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 60, 30}
It seems like the last two elements have been flipped.
I am still very much a beginner so go easy on me, thanks!.
You are sorting them first which gets them in the order you want but once you set(divisors) to remove the duplicates, it unorders them as sets are unordered.
A set is an unordered collection with no duplicate elements.
https://docs.python.org/3/tutorial/datastructures.html
You need to change the last two lines.
divisors.sort()
print(set(divisors))
to
set(divisors)
divisors.sort()
print(divisors)

Python: Convert list of numbers to according letters

I know the answer is going to be obvious once I see it, but I can't find how to convert my output list of numbers back to letters after I've manipulated the list.
I am putting in data here:
import string
print [ord(char) - 96 for char in raw_input('Write Text: ').lower()]
and I want to be able to reverse this so after I manipulate the list, I can return it back to letters.
example: input gasoline / output [7, 1, 19, 15, 12, 9, 14, 5]
manipulate the output with append or other
then be able to return it back to letters.
Everything I search is only to convert letterst to numbers and nothing to convert that list of numbers back to letters.
Thank you!
It can be done by using chr() built-in function :
my_list = [7, 1, 19, 15, 12, 9, 14, 5]
out = ""
for char in my_list:
out += chr( 96 + char )
print(out) # Prints gasoline
If you want the final output as a list of characters use the first one otherwise the last one.
l = [7, 1, 19, 15, 12, 9, 14, 5] # l is your list of integers
listOfChar = list(map(chr,[x+96 for x in l]))
aWord = "".join(list(map(chr,[x+96 for x in l])))#your word here is "gasoline"

Iterate through a string in chunks of different sizes python

So I am working with files in python, feel like there is a name for them but I'm not sure what it is. They are like csv files but with no separator. Anyway in my file I have lots of lines of data where the first 7 characters are an ID number then the next 5 are something else and so on. So I want to go through the file reading each line and splitting it up and storing it into a list. Here is an example:
From the file: "0030108102017033119080001010048000000"
These are the chunks I would like to split the string into: [7, 2, 8, 6, 2, 2, 5, 5] Each number represents the length of each chunk.
First I tried this:
n = [7, 2, 8, 6, 2, 2, 5, 5]
for i in range(0, 37, n):
print(i)
Naturally this didn't work, so now I've started thinking about possible methods and they all seem quite complex. I looked around online and couldn't seem to find anything, only even sized chunks. So any input?
EDIT: The answer I'm looking for should in this case look like this:
['0030108', '10', '20170331', '190800', '01', '01', '00480', '00000']
Where each value in the list n represents the length of each chunk.
If these are ASCII strings (or rather, one byte per character), I might use struct.unpack for this.
>>> import struct
>>> sizes = [7, 2, 8, 6, 2, 2, 5, 5]
>>> struct.unpack(''.join("%ds" % x for x in sizes), "0030108102017033119080001010048000000")
('0030108', '10', '20170331', '190800', '01', '01', '00480', '00000')
>>>
Otherwise, you can construct the necessary slice objects from partial sums of the sizes, which is simple to do if you are using Python 3:
>>> psums = list(itertools.accumulate([0] + sizes))
>>> [s[slice(*i)] for i in zip(psums, psums[1:])]
['0030108', '10', '20170331', '190800', '01', '01', '00480', '00000']
accumulate can be implemented in Python 2 with something like
def accumulate(itr):
total = 0
for x in itr:
total += x
yield total
from itertools import accumulate, chain
s = "0030108102017033119080001010048000000"
n = [7, 2, 8, 6, 2, 2, 5, 5]
ranges = list(accumulate(n))
list(map(lambda i: s[i[0]:i[1]], zip(chain([0], ranges), ranges))
# ['0030108', '10', '20170331', '190800', '01', '01', '00480', '00000']
Could you try this?
for line in file:
n = [7, 2, 8, 6, 2, 2, 5, 5]
total = 0
for i in n:
print(line[total:total+i])
total += i
This is how I might have done it. The code iterates through each line in the file, and for each line, iterate through the list of lengths you need to pull out which is in the list n. This can be amended to do something else instead of print, but the idea is that a slice is returned from the line. The total variable keeps track of how far into the lines we are.
Here's a generator that yields the chunks by iterating through the characters of the lsit and forming substrings from them. You can use this to process any iterable in this fashion.:
def chunks(s, sizes):
it = iter(s)
for size in sizes:
l = []
try:
for _ in range(size):
l.append(next(it))
finally:
yield ''.join(l)
s="0030108102017033119080001010048000000"
n = [7, 2, 8, 6, 2, 2, 5, 5]
print(list(chunks(s, n)))
# ['0030108', '10', '20170331', '190800', '01', '01', '00480', '00000']

pythonic format for indices

I am after a string format to efficiently represent a set of indices.
For example "1-3,6,8-10,16" would produce [1,2,3,6,8,9,10,16]
Ideally I would also be able to represent infinite sequences.
Is there an existing standard way of doing this? Or a good library? Or can you propose your own format?
thanks!
Edit: Wow! - thanks for all the well considered responses. I agree I should use ':' instead. Any ideas about infinite lists? I was thinking of using "1.." to represent all positive numbers.
The use case is for a shopping cart. For some products I need to restrict product sales to multiples of X, for others any positive number. So I am after a string format to represent this in the database.
You don't need a string for that, This is as simple as it can get:
from types import SliceType
class sequence(object):
def __getitem__(self, item):
for a in item:
if isinstance(a, SliceType):
i = a.start
step = a.step if a.step else 1
while True:
if a.stop and i > a.stop:
break
yield i
i += step
else:
yield a
print list(sequence()[1:3,6,8:10,16])
Output:
[1, 2, 3, 6, 8, 9, 10, 16]
I'm using Python slice type power to express the sequence ranges. I'm also using generators to be memory efficient.
Please note that I'm adding 1 to the slice stop, otherwise the ranges will be different because the stop in slices is not included.
It supports steps:
>>> list(sequence()[1:3,6,8:20:2])
[1, 2, 3, 6, 8, 10, 12, 14, 16, 18, 20]
And infinite sequences:
sequence()[1:3,6,8:]
1, 2, 3, 6, 8, 9, 10, ...
If you have to give it a string then you can combine #ilya n. parser with this solution. I'll extend #ilya n. parser to support indexes as well as ranges:
def parser(input):
ranges = [a.split('-') for a in input.split(',')]
return [slice(*map(int, a)) if len(a) > 1 else int(a[0]) for a in ranges]
Now you can use it like this:
>>> print list(sequence()[parser('1-3,6,8-10,16')])
[1, 2, 3, 6, 8, 9, 10, 16]
If you're into something Pythonic, I think 1:3,6,8:10,16 would be a better choice, as x:y is a standard notation for index range and the syntax allows you to use this notation on objects. Note that the call
z[1:3,6,8:10,16]
gets translated into
z.__getitem__((slice(1, 3, None), 6, slice(8, 10, None), 16))
Even though this is a TypeError if z is a built-in container, you're free to create the class that will return something reasonable, e.g. as NumPy's arrays.
You might also say that by convention 5: and :5 represent infinite index ranges (this is a bit stretched as Python has no built-in types with negative or infinitely large positive indexes).
And here's the parser (a beautiful one-liner that suffers from slice(16, None, None) glitch described below):
def parse(s):
return [slice(*map(int, x.split(':'))) for x in s.split(',')]
There's one pitfall, however: 8:10 by definition includes only indices 8 and 9 -- without upper bound. If that's unacceptable for your purposes, you certainly need a different format and 1-3,6,8-10,16 looks good to me. The parser then would be
def myslice(start, stop=None, step=None):
return slice(start, (stop if stop is not None else start) + 1, step)
def parse(s):
return [myslice(*map(int, x.split('-'))) for x in s.split(',')]
Update: here's the full parser for a combined format:
from sys import maxsize as INF
def indices(s: 'string with indices list') -> 'indices generator':
for x in s.split(','):
splitter = ':' if (':' in x) or (x[0] == '-') else '-'
ix = x.split(splitter)
start = int(ix[0]) if ix[0] is not '' else -INF
if len(ix) == 1:
stop = start + 1
else:
stop = int(ix[1]) if ix[1] is not '' else INF
step = int(ix[2]) if len(ix) > 2 else 1
for y in range(start, stop + (splitter == '-'), step):
yield y
This handles negative numbers as well, so
print(list(indices('-5, 1:3, 6, 8:15:2, 20-25, 18')))
prints
[-5, 1, 2, 6, 7, 8, 10, 12, 14, 20, 21, 22, 23, 24, 25, 18, 19]
Yet another alternative is to use ... (which Python recognizes as the built-in constant Ellipsis so you can call z[...] if you want) but I think 1,...,3,6, 8,...,10,16 is less readable.
This is probably about as lazily as it can be done, meaning it will be okay for even very large lists:
def makerange(s):
for nums in s.split(","): # whole list comma-delimited
range_ = nums.split("-") # number might have a dash - if not, no big deal
start = int(range_[0])
for i in xrange(start, start + 1 if len(range_) == 1 else int(range_[1]) + 1):
yield i
s = "1-3,6,8-10,16"
print list(makerange(s))
output:
[1, 2, 3, 6, 8, 9, 10, 16]
import sys
class Sequencer(object):
def __getitem__(self, items):
if not isinstance(items, (tuple, list)):
items = [items]
for item in items:
if isinstance(item, slice):
for i in xrange(*item.indices(sys.maxint)):
yield i
else:
yield item
>>> s = Sequencer()
>>> print list(s[1:3,6,8:10,16])
[1, 2, 6, 8, 9, 16]
Note that I am using the xrange builtin to generate the sequence. That seems awkward at first because it doesn't include the upper number of sequences by default, however it proves to be very convenient. You can do things like:
>>> print list(s[1:10:3,5,5,16,13:5:-1])
[1, 4, 7, 5, 5, 16, 13, 12, 11, 10, 9, 8, 7, 6]
Which means you can use the step part of xrange.
This looked like a fun puzzle to go with my coffee this morning. If you settle on your given syntax (which looks okay to me, with some notes at the end), here is a pyparsing converter that will take your input string and return a list of integers:
from pyparsing import *
integer = Word(nums).setParseAction(lambda t : int(t[0]))
intrange = integer("start") + '-' + integer("end")
def validateRange(tokens):
if tokens.from_ > tokens.to:
raise Exception("invalid range, start must be <= end")
intrange.setParseAction(validateRange)
intrange.addParseAction(lambda t: list(range(t.start, t.end+1)))
indices = delimitedList(intrange | integer)
def mergeRanges(tokens):
ret = set()
for item in tokens:
if isinstance(item,int):
ret.add(item)
else:
ret += set(item)
return sorted(ret)
indices.setParseAction(mergeRanges)
test = "1-3,6,8-10,16"
print indices.parseString(test)
This also takes care of any overlapping or duplicate entries, such "3-8,4,6,3,4", and returns a list of just the unique integers.
The parser takes care of validating that ranges like "10-3" are not allowed. If you really wanted to allow this, and have something like "1,5-3,7" return 1,5,4,3,7, then you could tweak the intrange and mergeRanges parse actions to get this simpler result (and discard the validateRange parse action altogether).
You are very likely to get whitespace in your expressions, I assume that this is not significant. "1, 2, 3-6" would be handled the same as "1,2,3-6". Pyparsing does this by default, so you don't see any special whitespace handling in the code above (but it's there...)
This parser does not handle negative indices, but if that were needed too, just change the definition of integer to:
integer = Combine(Optional('-') + Word(nums)).setParseAction(lambda t : int(t[0]))
Your example didn't list any negatives, so I left it out for now.
Python uses ':' for a ranging delimiter, so your original string could have looked like "1:3,6,8:10,16", and Pascal used '..' for array ranges, giving "1..3,6,8..10,16" - meh, dashes are just as good as far as I'm concerned.

Categories