3

I wanna show the lines in a file, let the user decide which line should be deleted and then write all lines back to the file, except the one the user wants to delete.

This is what I tried so far, but I'm kinda stuck.

def delete_result():
    text_file = open('minigolf.txt', 'r')
    zork = 0
    for line in text_file:
        zork = zork + 1
        print zork, line

    delete_player = raw_input ("Who's result do you want to delete?")

    text_file.close()

or

def delete_result():
    text_file = open('minigolf.txt', 'r')
    for line in text_file:
        if ';' in line:
            line2 = line.split(";")
        print line2
    print "***"
    delete = raw_input ("Who's result do you want to delete? ")

    text_file.close()

minigolf.txt contains:

Sara;37;32;47;
Johan;44;29;34;
Kalle;33;34;34;
Oskar;23;47;45;
3
  • are you trying to delete with the username or the line number? Commented Jul 18, 2016 at 13:13
  • if the user wants to delete lines 5 and 10 does deleting 5 change what is line 10 or will you have to basically rewrite the file without the lines they want to delete? Commented Jul 18, 2016 at 13:14
  • @depperm basically rewrite the file without the lines they want to delete Commented Jul 18, 2016 at 13:18

5 Answers 5

2

All other answers are valid so you probably got a good idea on how to do it by loading the file, changing the content and then saving the file back.

I just want to point out that there is a possibility of changing the content of the file directly in storage memory. It is not always wise to do so, the thing has its drawbacks, but as it may be useful for some future usages there it is.

To change (delete or insert) content into an existing file you can use the mmap module.

It allows you to map a portion of the RAM or storage memory (file) and access and edit it like it is a string. Perhaps a list it's better to say.

So, to remove the line you want you open the file, load its content and perform find() or something else to find the index of the line you want to delete and its length.

Then you memory map the file, and simply move the rest of the content following the line you wish to delete upward, thus "covering" the unwanted line. You do it using slicing. Then you resize the memory map to cut off the remaining bytes after you shifted the content. Thus you resize the file to right size and then you can close the mmap which will not close the file.

You can insert a line in this manner into a file. You first resize the file, shift the content toward the end to make a space where you want your line to appear, and joust write it in.

It sounds a bit complicated and a lot of work, but it isn't really. It saves you the trouble of writing the whole file each time you remove the line.

I didn't check how fast it is, and is it faster than overwrite each time. But this is one solution I felt worth mentioning.

Here is some quickly assembled code:



# This needs checks and rechecks
# Also, its efficiency is questionable. Some optimization can be done with find() and rfind()
# But we can choose to believe in the module and just do it.
# The nice thing is that we can use find() to point the user, not searching for the line number like mad.

from mmap import mmap

def removeline (fname, nl):
    f = open(fname, "rb+")
    m = mmap(f.fileno(), 0)
    size = m.size()
    ixl = 0 # Index of line to delete
    nle = 0 # Count new lines found
    # Find the line:
    while nle!=nl:
        # Suppose we know our EOL will always be \n
        i = m.find("\n", ixl)
        if i==-1: break
        ixl = i+1
        nle += 1
    if ixl>=size: f.close(); return # nl is greater than number of lines in the f
    ixle = m.find("\n", ixl) # Index of end of that line
    ixle = (ixle+1, None)[ixle==-1] #Either include the EOL in deletion or delete to the EOF
    # Line length:
    if ixle!=None: ll = ixle-ixl
    else:
        # Remove from ixl to the end of file.
        # I.e. just shrink the file.
        ns = size-(size-ixl)
        if ns==0:
            # Delete all
            m.close()
            f.close()
            f = open(fname, "wb")
            f.close()
            return
        m.resize(ns) # Cut off the rubbish
        m.close(); f.close()
        return
    # Shift the rest over the offending line:
    try: m[ixl:size-ll] = m[ixle:size]
    except:
        m.close()
        f.close()
        raise
    ns = size-ll
    if ns==0:
        # Delete all - mmap doesn't like to resize to 0 bytes., hm, perhaps f.truncate()
        m.close()
        f.close()
        f = open(fname, "wb")
        f.close()
        return
    m.resize(ns) # Cut off the rubbish
    m.close()
    f.close()

Sign up to request clarification or add additional context in comments.

2 Comments

Makes sense.. Eager to see a code example.. Thanks for sharing!
I added the code example. It's a little bulky. Use with caution.
1
line_number = 5 #for example
file = open("foo.txt")
cont = file.read()
cont = cont.splitlines()

cont.pop(line_number-1)
file.close()
file = open("foo.txt", "w")
cont= "\n".join(cont)
file.write(cont)
file.close()

If you do it with names; try that:

file = open("foo.txt")
cont = file.read()
cont = cont.splitlines()

line_number = 0 
name = "Johan"
for i in cont:
    if i.startswith(name):
        line_number = cont.index(i)


cont.pop(line_number)
file.close()
file = open("foo.txt", "w")
cont= "\n".join(cont)
file.write(cont)
file.close()

Comments

1

A slight modification to danidee's answer for clarity

def delete_result():
    with open('minigolf.txt', 'r') as f:
        results = f.readlines()
        print "\n".join(results)

    delete_player = raw_input ("Who's result do you want to delete?")
    deleted = False

    for res in results:
        if delete_player.lower() in res.lower():
            results.remove(res)

            with open('minigolf.txt', 'w') as f:
                f.writelines(results)
                print "User was found and removed"        
                deleted = True
    # for

    if not deleted:
        print "User not found..."
# def

Result:

>> python delete_user.py
Sara;37;32;47;

Johan;44;29;34;

Kalle;33;34;34;

Oskar;23;47;45;
Who's result do you want to delete?sara
User was found and removed

>> cat minigolf.txt
Johan;44;29;34;
Kalle;33;34;34;
Oskar;23;47;45;
>> python delete_user.py
Johan;44;29;34;

Kalle;33;34;34;

Oskar;23;47;45;
Who's result do you want to delete?nonuser
User not found...

Comments

1
def delete_result():
    with open('minigolf.txt', 'r') as f:
        results = f.readlines()
        print(results)

    user = raw_input('which user do you want to delete')

    for res in results:
        if user.lower() in res:  # assumption that sara == Sara
            results.remove(res)

            with open('minigolf.txt', 'w') as f:
                f.writelines(results)
                return 'user was found and removed'

    return 'user was not found'

2 Comments

Note that you shouldn't be using readlines or writelines if you have a huge txt file to read or a lot of items to write to the file, it's better you loop through them and write/read the items one at a time
Nice approach sir.. Slightly modified the answer and posted below..!
0

This will solve your issue and give you a more robust way of handling user input:

def delete_result():
    with open('minigolf.txt', 'r') as f:
        text_file = f.readlines()

    # find newline char and strip from endings
    if '\r' in text_file[0]:
        if '\n' in text_file[0]:
            newline = '\r\n'
        else:
            newline = '\r'
    else:
        newline = '\n'
    text_file = [t[:-len(newline)]
                    if t[-len(newline):] == newline
                        else t for t in text_file]

    users = set()
    for line_number, line in enumerate(text_file):
        print line_number + 1, line
        users.add(line[:line.index(';')].lower())

    # get result from user with exception handling
    result = None
    while not result:
        delete_player = raw_input('Which user do you want to delete? ')
        try:
            result = str(delete_player).lower()
            assert result in users
        except ValueError:
            print('Sorry, I couldn\'t parse that user.')
        except AssertionError:
            print('Sorry, I couldn\'t find that user.')
            result = None

    # write new file
    new_file = [t + newline for t in text_file
                             if t[:t.index(';')].lower() != result]
    with open('minigolf.txt', 'w') as f:
        f.writelines(new_file)

if __name__ == '__main__':
    delete_result()

EDIT: I saw that you wanted to wanted to delete by name, not line number, so changed it to resemble @danidee's method.

3 Comments

The EOL can be \n, \r or \r\n, depending on the OS's convention and/or editor that produced the file. So better use line.rstrip("\n").rstrip("\r"), or load whole file into RAM, then content.splitlines() and no worries about EOLs. Also if stripping readline()/readlines() results, the file object contains an attribute called newlines that should contain OS valid NL separator.
@Dalen that's a good point, only issue is knowing which newlines to add back in (which wouldn't be a concern if the new file was written line-by-line, I guess). Thanks for the suggestion!
Perhaps get the EOL from first readline() and take it to be our guy through out whole file. Then process the rest in any way that suits the needs. Just an idea.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.