Python: Building An Offline Random Name Picker
Tags: python, programming, tutorial, opensource
Published: 2022-02-13 06:50:48

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](https://pypi.org/project/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. 

```python
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.'
)
```

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](https://pypi.org/project/cowsay/). 

```python
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.'
)
```

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](https://pypi.org/project/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.

```python
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)
```

## 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.

```python
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)
```

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.

```python
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()
```

## 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)`. 

```python
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
```

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

```python
def clear_terminal():
    os.system('cls' if os.name == 'nt' else 'clear')
```

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

```python
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)
```

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.

<hr>

Grab a copy of the code from the project's repository [here](https://github.com/joiellantero/name-roulette). 

This post is also available in my [blog](https://joiellantero.com).