DEV Community

Cover image for Python: Building An Offline Random Name Picker
Joie
Joie

Posted on • Edited on

Python: Building An Offline Random Name Picker

When we shifted to an online setup due to the pandemic, we had so many online activities where we needed a random name picker. There are websites that offer this service but, I wanted to have a privacy-centered program that can read a large list of names and runs locally on my own computer. Since this is offline and we control the code, we become sure that the data stays private.

I created a program that a user can run in their terminal using Python and it can read a list of names from either a comma-separated-value (.csv) file or a text (.txt) file.

Giving the program settings some flexibility

I wanted the program to take in arguments and command-line options so that I can easily adjust the settings to my liking. I used Python's argument parser library, argparse, for this. I created two positional arguments that takes in a file and integer value. The file should contain the list of names whereas the integer is the number of people to be chosen randomly.

parser = argparse.ArgumentParser(description='Name Roulette. A random picker for names.')

parser.add_argument(
    'file',
    metavar='filename',
    type=str,
    help='Files accepted are csv and txt files. Add the filename of the text or csv file containing the names as an argument, e.g., fileName.csv. Each name should be entered in a new line. You may refer to the README.md to see more examples.'
)

parser.add_argument(
    'amount',
    metavar='amount',
    nargs='?',
    default=1,
    type=int,
    help='The number of people to be chosen randomly.'
)
Enter fullscreen mode Exit fullscreen mode

Notice that the amount argument has a default value of 1. The reason for this is if the user doesn't specify the number of people to be chosen, then the program should only choose one random name at a time.

Next, I also wanted to have optional arguments for removing the name after it was chosen, displaying the list of names, and cowsay.

parser.add_argument(
    '--repeat',
    action="store_true",
    required=False,
    help='Loop through the names of the players forever. Not including the --repeat flag will remove the player from the list once they are chosen.'
)

parser.add_argument(
    '--display',
    action="store_true",
    required=False,
    help='Show the list of names.'
)

parser.add_argument(
    '--cowsay',
    action="store_true",
    required=False,
    help='Show chosen name/s with cowsay illustration.'
)
Enter fullscreen mode Exit fullscreen mode

When the --repeat flag is specified by the user, it should choose random names from the list without removing them. On the other hand, when it's not specified, the program will remove the name from the list after it is chosen.

File handling

The first thing that my program does is to read the filenames check if it exists. I also added error handling to make sure that the user remembers to put the file type, i.e., if it's a .csv or .txt file. I used Python's pandas library to obtain the names from the .csv or .txt file. Note that the program doesn't edit the input file instead, it saves a copy of the names. This is related to the –repeat flag because not indicating the said flag would remove the name from the list.

def get_names(filename):
    try:
        names_df = pd.read_csv(filename, sep=",", header=None, names=["Names"])
        names_df['Names'] = names_df['Names'].astype('string')
        return names_df
    except FileNotFoundError:
        print(f"\nFile '{filename}' does not exist.")
        if filename[-4:] != '.csv' and filename[-4:] != '.txt':
            print(f"Please include file type, e.g., {filename}.txt or {filename}.csv.")
        sys.exit(0)
Enter fullscreen mode Exit fullscreen mode

Randomly choosing a name

Now that I have the list of names ready, we can now proceed to creating the function to pick a random name. This function should take in all the positional and optional arguments. I again used pandas to pick a random name from the list and to also remove the name from the list.

def draw_name(df, amount, repeat, display, acowsay):
    while not df.empty:
        chosen_name = []
        for i in range(0, amount):
            if df.empty:
                break
            chosen_name.append(df.loc[df.sample().index[0], 'Names'])
            if not repeat:
                df = df[df["Names"].str.contains(chosen_name[i])==False]
        if display and not df.empty:
            print(df)
        if acowsay:
            cowsay.tux(', '.join(chosen_name))
        else:
            print(f"\nChosen Name/s: {', '.join(chosen_name)}")
    else:
        print("\nProgram Ended.\nList of names is now empty.")
        sys.exit(0)
Enter fullscreen mode Exit fullscreen mode

The user should also have an option to pick another name or not. Thus, I created another function called ask_choose_again() to do this task. I then called this function at the end of the while loop in the draw_name() function.

def ask_choose_again():
    again = input("\nChoose again? [Y/N]: ")
    if (again == 'y' or again == 'Y'):
        return
    elif again == 'n' or again == 'N':
        print("\nProgram ended.")
        sys.exit(0)
    else:
        print("\nInvalid input: type Y for yes or N for no.")
        ask_choose_again()
Enter fullscreen mode Exit fullscreen mode

Improving the user experience

To give some aesthetics to my program, I added a loading animation to simulate that the program is picking a name–kind of like a drum roll. Note that the loading time is fake and it doesn't really depict the true speed of the program when choosing a name from a list because doing that would be very quick to even see the animation. In my program, I set the loading animation to show up for one-second but you can easily change it by adjusting the value in the time.sleep(x).

def loading_animation():
    done = False
    def animate():
        for c in itertools.cycle(['|', '/', '-', '\\']):
            if done:
                break
            sys.stdout.write('\rchoosing someone... ' + c)
            sys.stdout.flush()
            time.sleep(0.1)
    t = threading.Thread(target=animate)
    t.start()
    time.sleep(1)
    done = True
Enter fullscreen mode Exit fullscreen mode

I also added a clear screen function to clean up the terminal every time we pick a random name.

def clear_terminal():
    os.system('cls' if os.name == 'nt' else 'clear')
Enter fullscreen mode Exit fullscreen mode

Finally, I added the loading_animation() and clear_terminal() function calls to my draw_name() function.

def draw_name(df, amount, repeat, display, acowsay):
    while not df.empty:
        clear_terminal()
        loading_animation()
        clear_terminal()
        chosen_name = []
        for i in range(0, amount):
            if df.empty:
                break
            chosen_name.append(df.loc[df.sample().index[0], 'Names'])
            if not repeat:
                df = df[df["Names"].str.contains(chosen_name[i])==False]
        if display and not df.empty:
            print(df)
        if acowsay:
            cowsay.tux(', '.join(chosen_name))
        else:
            print(f"\nChosen Name/s: {', '.join(chosen_name)}")
        ask_choose_again()
    else:
        print("\nProgram Ended.\nList of names is now empty.")
        sys.exit(0)
Enter fullscreen mode Exit fullscreen mode

That's it! We now have an offline random name picker that runs in our terminal. It's also great to use when you're concerned with data privacy for your names. The best thing is we can also use this program even if our list are not names like a list of truth or dare questions.


Grab a copy of the code from the project's repository here.

This post is also available in my blog.

Top comments (0)