오늘의 할 일 목록은 '오늘 하루'를 관리하기 위한 목적으로, 대부분의 어플이 최대한 편리하고 가벼운 프로그램, 마치 내 손에 딱 맞는 아담한 다이어리 하나와 볼펜 하나 같은 도구를 지향하는 듯하다. 그 때문인지 '지난 하루들'을 돌아보는 일에는 별로 초점을 두지 않는다. 과거의 데이터를 집계하고 보여주는 기능의 부재는 늘 아쉬웠기 때문에, 이번에 직접 만들어서 사용해 보고자 한다.

대략 이런 식으로 지난 기간의 '오늘의 할 일 달성률'을 확인하고자 이 프로그램을 만들었다. 성취도를 평가하는 지표는 대개 결과중심적이다. 나아지지 않는 결과에 낙담하기도 한다. 그럴 때 완벽하지는 않더라도 꾸준하게 하루를 채우며 걸어온 지난 날들을 한 눈에 볼 수 있다면 기분이 괜찮지 않을까?


요약

데이터 저장 및 시각화 위주의 기능만으로 구성하며, 모든 입력은 표준입력으로 받는다

사용 언어

Python

라이브러리

  1. csv
  2. datetime
  3. numpy
  4. matplotlib

기능

  • 사용자에게 어떤 기능을 이용할 것인지 묻는 프롬프트 출력
  • 기능 1: "오늘 할 일" 목록에 입력받은 "할 일"을 삽입함
    • csv 파일: todoList.csv
  • 기능 2: "오늘 할 일" 목록을 출력함
    • csv 파일: todoList.csv
  • 기능 3: "오늘 할 일" 목록에서 입력받은 "해낸 일"을 삭제하고, "오늘 해낸 일" 목록에 추가함
    • csv 파일: todoList.csv, completeList.csv
    • 예외) 입력받은 "해낸 일"이 "오늘 할 일" 목록에 없다면, 오류 메시지를 출력하고 재입력을 요구함
  • 기능 4: "오늘 해낸 일" 목록을 출력함
    • csv 파일: completeList.csv
  • 기능 5: "오늘 할 일", "오늘 해낸 일" 목록에 있는 요소들을 "전체 데이터" 테이블에 추가하고, 두 개의 목록을 각각 비어있는 파일로 초기화함
    • csv 파일: todoList.csv, completeList.csv, data.csv
    • data.csv: [날짜, 오늘 할 일 목록, 오늘 할 일 개수, 오늘 해낸 일 목록, 오늘 해낸 일 개수]
    • 예외) 현재 날짜에 저장된 데이터가 있다면, "덮어쓰기"를 할 것인지 묻는 프롬프트 출력 후 처리함
  • 기능 6: 조회하려는 기간을 묻는 프롬프트를 출력하고, 입력받은 기간 동안의 데이터를 그래프로 나타냄
    • csv 파일: data.csv
    • 누적 막대 그래프
      • top : 오늘 할 일
      • bottom : 오늘 해낸 일
    • 꺾은선 그래프
      • 달성률 = C / (C + U) * 100 (%) [C: 오늘 해낸 일, U: 오늘 할 일]
      • 꺾은선 그래프의 점 위에 달성률 값을 표시함

프로그램의 메인 메뉴

 

오늘 할 일을 추가하는 기능

 

오늘 추가해 놓은 할 일 목록을 조회하는 기능
오늘 할 일 중 완료한 일을 완료 처리하는 기능
'백준 1문제 풀기'가 현재 할 일에서 사라진 모습이다
'백준 1문제 풀기'가 오늘 해낸 일 목록에 추가된 모습이다
오늘자 데이터를 저장하려고 할 때, 이미 오늘 저장된 데이터가 있으면 덮어쓸지 여부를 묻는다. 제일 먼저 개선해야 할 부분.

 

이 프로그램의 핵심 기능
역시 긴 것보다는 짧은 게 보기 좋다.
별 것 아닌 듯했던 기능들도, 서로 마찰을 빚지 않고 매끄럽게 동작하도록 하기 위해 꽤 신경을 많이 써야 하는구나 싶었던 오늘이다.

더보기
# © 2022 starsein <dbtjd1928@gmail.com>
import csv
import datetime as dt
import numpy as np
from matplotlib import pyplot as plt
from typing import List, Tuple


def add_todo():
    print("[할 일 추가]")
    with open('todoList.csv', 'a', newline='') as af:
        writer = csv.writer(af)
        print("오늘 할 일을 입력하세요.",
              "이전 메뉴로 돌아가려면 q 또는 Q를 입력하세요.", sep='\n')
        while True:
            todo_str = input().rstrip()
            if todo_str == 'q' or todo_str == 'Q':
                return 0
            writer.writerow([todo_str])
            print(f"{todo_str}가 할 일 목록에 정상적으로 추가되었습니다!")


def get_todo() -> List[str]:
    with open('todoList.csv', 'r') as rf:
        todo_list = []
        reader = csv.reader(rf)
        for todo in reader:
            todo_list.append(*todo)
    return todo_list


def show_todo():
    print("[할 일 확인]")
    todo_list = get_todo()
    print("+------------------------------------+")
    for idx, todo in enumerate(todo_list, start=1):
        print(idx, todo)
    print("+------------------------------------+")
    while True:
        cmd = input("이전 메뉴로 돌아가려면 q 또는 Q를 입력하세요.\n").rstrip()
        if cmd == 'q' or cmd == 'Q':
            return 0


def add_completed_task():
    print("[해낸 일 추가]")
    todo_list = get_todo()
    with open('completedTaskList.csv', 'a', newline='') as af:
        writer = csv.writer(af)
        print("오늘 해낸 일을 입력하세요.",
              "이전 메뉴로 돌아가려면 q 또는 Q를 입력하세요.", sep='\n')
        while True:
            print("[현재 할 일 목록]")
            print("+------------------------------------+")
            for idx, todo in enumerate(todo_list, start=1):
                print(idx, todo)
            print("+------------------------------------+")
            todo_str = input().rstrip()
            if todo_str == 'q' or todo_str == 'Q':
                break

            if todo_str not in todo_list:
                print("오늘 할 일에 없는 입력입니다.")
                continue

            todo_list.remove(todo_str)
            writer.writerow([todo_str])
    with open('todoList.csv', 'w') as wf:
        writer = csv.writer(wf)
        for todo in todo_list:
            writer.writerow([todo])


def get_completed_task() -> List[str]:
    with open('completedTaskList.csv', 'r') as rf:
        ct_list = []
        reader = csv.reader(rf)
        for completed_task in reader:
            ct_list.append(*completed_task)
    return ct_list


def show_completed_task():
    print("[해낸 일 확인]")
    ct_list = get_completed_task()
    print("+------------------------------------+")
    for idx, completed_task in enumerate(ct_list, start=1):
        print(idx, completed_task)
    print("+------------------------------------+")
    while True:
        cmd = input("이전 메뉴로 돌아가려면 q 또는 Q를 입력하세요.\n").rstrip()
        if cmd == 'q' or cmd == 'Q':
            return 0


def check_data(current_time_str: str) -> Tuple[str, List[List[str]]]:
    with open("data.csv", "r") as rf:
        data_list = []
        reader = csv.reader(rf)
        for day_data in reader:
            data_list.append(day_data)
            date = day_data[0]
            if date == current_time_str:
                print("현재 날짜에 이미 저장된 데이터가 있습니다.")
                while True:
                    user_cmd = input("새로운 데이터로 덮어쓰기 하시겠습니까? [y/n]").rstrip()
                    if user_cmd == 'y':
                        return "OVERWRITE", data_list
                    elif user_cmd == 'n':
                        return "DON\'T OVERWRITE", data_list
    return "NOT TO OVERWRITE", data_list


def store_data():
    info = dt.datetime.now()
    current_time_list = list(info.strftime("%Y %m %d %A").split())
    translate_table = {"Monday": "월",
                       "Tuesday": "화",
                       "Wednesday": "수",
                       "Thursday": "목",
                       "Friday": "금",
                       "Saturday": "토",
                       "Sunday": "일"}
    current_time_list[3] = translate_table.get(current_time_list[3])
    current_time_str = ' '.join(current_time_list)
    todo_list = get_todo()
    ct_list = get_completed_task()

    res, data_list = check_data(current_time_str)

    if res == "OVERWRITE":
        data_list.pop()
    elif res == "DON\'T OVERWRITE":
        return 0
    else:
        pass
    data_list.append([current_time_str, todo_list, len(todo_list), ct_list, len(ct_list)])

    with open("data.csv", 'w', newline='') as wf:
        writer = csv.writer(wf)
        writer.writerows(data_list)

    with open("todoList.csv", 'w'):
        pass
    with open("completedTaskList.csv", 'w'):
        pass
    print("오늘의 데이터 집계 및 초기화가 완료되었습니다!")


def visualize_data():
    date_arr = np.array([])
    num_ut_arr = np.array([])
    num_ct_arr = np.array([])
    cr_arr = np.array([])
    with open("data.csv", 'r') as rf:
        reader = csv.reader(rf)
        for data in reader:
            stored_date, ut, num_ut, ct, num_ct = data
            num_ut = int(num_ut)
            num_ct = int(num_ct)
            date_arr = np.append(date_arr, stored_date)
            num_ut_arr = np.append(num_ut_arr, num_ut)
            num_ct_arr = np.append(num_ct_arr, num_ct)
            cr = round(num_ct / (num_ct + num_ut) * 100) if num_ct | num_ut != 0 else 0
            cr_arr = np.append(cr_arr, cr)
    print("[현재까지 집계된 데이터 시각화]")
    print(f"총 {len(date_arr)}개 날짜의 데이터가 저장되어 있습니다.")
    user_cmd = int(input("최근에 저장된 데이터를 몇 개까지 표시할까요?\n"))
    date_arr = date_arr[-user_cmd:]
    num_ct_arr = num_ct_arr[-user_cmd:]
    num_ut_arr = num_ut_arr[-user_cmd:]
    cr_arr = cr_arr[-user_cmd:]

    plt.figure(figsize=(16, 8))
    plt.title("TodoTracker", fontsize=17)
    plt.bar(date_arr, num_ct_arr, color='aqua')
    plt.bar(date_arr, num_ut_arr, bottom=num_ct_arr, color='lightcoral')
    plt.legend(["완료한 일", "미완료한 일"])
    plt.plot(date_arr, cr_arr, color='springgreen')
    for i, v in enumerate(date_arr):
        plt.text(v, cr_arr[i], f"{int(cr_arr[i])}%", fontsize=9, horizontalalignment='center',
                 verticalalignment='bottom', color='springgreen')
    plt.xlabel("일자", fontsize=15)
    plt.xticks(rotation=45)
    plt.show()


def main():
    func_str_dict = {1: "할 일 추가",
                     2: "할 일 확인",
                     3: "해낸 일 추가",
                     4: "해낸 일 확인",
                     5: "오늘의 데이터 집계 및 초기화",
                     6: "현재까지 집계된 데이터 시각화"}
    func_exec_dict = {1: "add_todo()",
                      2: "show_todo()",
                      3: "add_completed_task()",
                      4: "show_completed_task()",
                      5: "store_data()",
                      6: "visualize_data()"}
    while True:
        print("+--------------------+",
              "| TodoTracker v1.1.1 |",
              "+--------------------+",
              "사용하고자 하는 기능의 번호를 입력하세요!",
              "프로그램을 종료하려면 기능의 번호 이외의 숫자나 문자를 입력하세요.", sep='\n')
        print("+--+---------------------------+")
        for func_key, func_str in func_str_dict.items():
            print(f"|{func_key:>2d}|{func_str}")
        print("+--+---------------------------+")
        try:
            user_cmd = int(input())
        except ValueError:
            break
        try:
            exec(func_exec_dict[user_cmd])
        except KeyError:
            break
    print("프로그램을 종료합니다.",
          "이용해주셔서 감사합니다!", sep='\n')
    return 0


if __name__ == '__main__':
    main()

 

이 프로그램을 일주일 사용해 보고, 핵심 기능이 정말로 내게 의미가 있다면 사용성을 개선하고 세부 기능을 확장할 예정이다.

+ Recent posts