0

I seek to make a simple script that does the following:

  1. Open a file called Accounts2.txt that contains a list of lists in string form. Each entry in the bigger list is a smaller list in the format of [username,password].

  2. Turn the Accounts2 string into a list of lists that python can manipulate.

  3. Check each entry in the big list to see if it matches [CheckName,Checkpass]. If it does, then login is successful. If it doesnt, then it goes to the next entry in the list. If it reaches the end of the list, then it says login unsuccessful.

CheckName = "user3"
CheckPass = "pass3"

path = "C:\\Users\\drago\\Desktop\\PythonPractice\\Accounts2.txt"
InFile = open(path, "r")
CredentialsString = str(InFile.read())
print(CredentialsString)
InFile.close()

CredentialsList = CredentialsString.split("],[")

i=0
for i in range(len(CredentialsList)):
    print(CredentialsList[i])
    if [CheckName,CheckPass] == CredentialsList[i]:
        print("Login successful")
    else: print("Login unsuccessful")

I'm stuck on part 2, as i'm not sure how to turn it into a list of lists using the split function without it removing parts of the formatting.

I am also stuck on part 3, as it prints 'login unsuccesful' at each check rather than doing it at the end if it fails every check.

Preferred if any solutions can be reasonably understood at my skill level. The difference between my question and past questions is that I don't understand the use of ast.literaleval or any similar imported library functions. If that can be explained that would be good.

Edit: Tried doing this:

import ast
CredentialsList = ast.literal_eval(CredentialsString)
print(CredentialsList)

which returns this error message:

  File "C:\Users\drago\OneDrive\Desktop\Python374\Softwares\Lib\ast.py", line 71, in _raise_malformed_node
    raise ValueError(msg + f': {node!r}')
ValueError: malformed node or string on line 1: <ast.Name object at 0x000002E4CADBD5D0>
aw92
  • 1
  • 1
  • Don't escape backslashes yourself. [Raw string](https://stackoverflow.com/q/647769/21305238) exists for that very purpose. – InSync Mar 18 '23 at 01:36
  • My apologies, can you point out where I am escape backslashing? Is it when I am retrieving the file? Is that the cause of the error? – aw92 Mar 18 '23 at 02:36
  • I meant this: `"C:\\Users\\drago\\Desktop\\PythonPractice\\Accounts2.txt"`. You can use this instead: `r"C:\Users\drago\Desktop\PythonPractice\Accounts2.txt"`. This is just a minor thing you may want to know; for actual answer, scroll down. – InSync Mar 18 '23 at 02:48
  • I see. So writing r"string" means that it considers whatever is in the "" as a string, and ignores any backslashes that try to escape? – aw92 Mar 18 '23 at 03:14
  • Yes. This helps you from duplicating backslashes. – InSync Mar 18 '23 at 03:15

2 Answers2

0

First, given that you already have the string, parse it; I'd recommend using regex for this purpose. We should also define a function as this allows reusing.

Reference: re.finditer().

import re

def check_password(check_username, check_password):
  for match in re.finditer(r'\[([^\[,\]]+),([^\[,\]]+)\]', your_string_here):
    username, password = match.groups()

Explanation for the regex:

\[               # Match a literal `[`
  (              # Capturing group
    [^\[,\]]+    # Anything that is not '[', ']' nor ','
  )              # Close the group, and we have the username
  ,              # Match a literal ','           
  (              # Samething as above,
    [^\[,\]]+    # but for the password
  )              #
\]               # Match a literal ']'

Then, check them against check_username and check_password; if this results to True, that means we have already found it and can stop iterating at that very moment. Use return to exit the function and prevent any later lines from being reached. That being said, the last line will be reached if and only if the loop did not find a match.

def check_password(...):
  for match in re.finditer(...):
    username, password = match.groups()
    
    if (username, password) == (check_username, check_password):
      print('Login successful')
      return

  print('Login unsuccessful')

So, in conclusion:

import re

def check_password(check_username, check_password):
  for match in re.finditer(r'\[([^\[,\]]+),([^\[,\]]+)\]', your_string_here):
    username, password = match.groups()
        
    if (username, password) == (check_username, check_password):
      print('Login successful')
      return
    
  print('Login unsuccessful')

A shorter version using a higher-order function and a hash map (hash table) to ensure O(1) lookup time complexity:

def create_check_password():
    table = {}
    
    for match in re.finditer(r'\[([^\[,\]]+),([^\[,\]]+)\]', your_string_here):
        username, password = match.groups()
        table[username] = password
    
    def check_password(username, password):
        return table.get(username) == password

    return check_password

check_password = create_check_password()

print(check_password('username9', 'password9'))  # True
print(check_password('user3', 'pass3'))          # False

Try it:

your_string_here = '''
[username1,password1]
[username2,password2]
[username3,password3]
[username4,password4]
[username5,password5]
[username6,password6]
[username7,password7]
[username8,password8]
[username9,password9]
'''

check_password('username9', 'password9') # Login successful
check_password('user3', 'pass3')         # Login unsuccessful

For ast.literal_eval, see Using python's eval() vs. ast.literal_eval(). ast means Abstract Syntax Tree and literal_eval means "parse whatever I pass as a Python literal and return the evaluated result".

InSync
  • 4,851
  • 4
  • 8
  • 30
  • I have to be honest with you, this is far beyond my current python levels, which are very basic, so if theres any simpler ways to do this let me know. Nonetheless I'll try to understand what you just said. I need to import 're' which is a library that lets me parse strings. I then use a function called re.finditer(pattern,strings,flags=0) to search for pieces of string in the string that I won't want. It has the format of: re.finditer(r'\[([^\[,\]]+),([^\[,\]]+)\]', your_string_here): username, password = match.groups() r' means consider the next stuff as raw string. – aw92 Mar 18 '23 at 03:05
  • re.finditer(r'\[([^\[,\]]+),([^\[,\]]+)\]', your_string_here): username, password = match.groups() Not sure what this does here. r' means consider the next stuff as raw string. \[ means try to find '['. ([^\[,\]]+) is supposed to be 'username'. Not sure what these symbols mean. Repeat for password. Then I put my string here, so that it searches in the string. There is no third input (the flag=0) Again id like to reiterate that this is too difficult for me to understand at my skill level so if theres a simpler way let me know. – aw92 Mar 18 '23 at 03:06
  • @aw92 `re.finditer()` gives you strings that you *want*, and there you have it in `username` and `password`. Try printing them out as you iterate over them. – InSync Mar 18 '23 at 03:12
  • Response is too large to paste here, copied it here: https://pastebin.com/dJ30h6a1 – aw92 Mar 18 '23 at 03:24
  • @aw92 I edited my answer to include the full program, for your convenience. – InSync Mar 18 '23 at 03:30
  • Thank you for taking the time to answer my questions, but Id like to say I don't understand this code and my skill level is not high enough to understand it. If theres a simpler solution I would like to hear it. This is for an entry level assignment, and I'm skeptical that they would want us to use a solution that is far beyond the scope of the course. – aw92 Mar 18 '23 at 03:36
0

Another solution that uses:

  • .strip(), which removes leading and trailing whitespaces
  • String slicing, which returns a part of the original string
  • split(substring), which split your string at every place where substring is found

Assuming that your string looks like this (note the blank lines):

'''

[username1,password1]
[username2,password2]
[username3,password3]
[username4,password4]
[username5,password5]
[username6,password6]
[username7,password7]
[username8,password8]
[username9,password9]

'''

After .strip():

'''[username1,password1]
[username2,password2]
[username3,password3]
[username4,password4]
[username5,password5]
[username6,password6]
[username7,password7]
[username8,password8]
[username9,password9]'''

After [1:-1], the first and the last character of the string is removed:

'''username1,password1]
[username2,password2]
[username3,password3]
[username4,password4]
[username5,password5]
[username6,password6]
[username7,password7]
[username8,password8]
[username9,password9'''

After .split(']\n[') (\n stands for a line break), the string becomes a list:

[
    'username1,password1',
    'username2,password2',
    'username3,password3',
    'username4,password4',
    'username5,password5',
    'username6,password6',
    'username7,password7',
    'username8,password8',
    'username9,password9'
]

For each element of this list, .split() it again into two strings; that will be username and password.

'username1,password1'.split(',')

# becomes

['username1', 'password1']

Explanation for the rest can be found in my other solution.

Full program:

string = your_string_here.strip()[1:-1].split(']\n[')

def check_password(check_username, check_password):
    for pair in string:
        username, password = pair.split(',')
        
        if (username, password) == (check_username, check_password):
            print('Login successful')
            return
    
    print('Login unsuccessful')
InSync
  • 4,851
  • 4
  • 8
  • 30