- 프로그램명 : TRIZSolver
- 제작기간 : '25. 5. 10.(1일)
- 제작자 : REDUCTO
- 사용언어 : Python, CustomTkinter

- 버전 : v1.0


소개

우리는 항상 아이디어에 굶주려 있습니다. 저또한 아이디어를 무엇을 만들어야하나 항상 고민을 하고 있었는데, 기술사공부를 하면서 봐왔던 아이디어 기법인 TRIZ와 SCAMPER기법을 섞은 데스크탑 어플리케이션이 있으면 좋겠다 생각했었습니다. 대 AI시대 gemini API를 이용해서 이를 고도화하면 좋은 어플리케이션이 나오지 않을까 한 단계 생각이 퍼져나갔고 한번 만들어 보았습니다.
 

* 무단배포는 금지합니다.(댓글달아주세용)
* 기능에 커스터마이징이 필요하시다면 댓글달아주세용


사용법
평소에는 소스코드를 올리지만 구조가 단일 코드 구조가 아니어서 별도의 사용방법을 올리겠습니다. 

 

* 사전준비

 - python이 설치되어있어야합니다.(https://www.python.org/downloads/)

 - 필자의 환경은 python 3.13.0입니다. 가급적 맞추어주시거나 상위버전을 사용해 주시는게 좋습니다.

 

1. 아래 파일을 다운로드 받아주시거나 git을 사용하시는 분은 "git clone https://github.com/ace30126/TRIZSolver.git"으로 project를 clone 해주십시오

TRIZSolver.zip
0.02MB

 

2. 압축해제(혹은 git clone)이후에는 TRIZSolver.zip경로에서 cmd를 켜고 아래의 명령어를 bash에서 입력해서 외부 라이브러리를 가져옵니다

  1. cmd 켜기(윈도우 버튼 -> cmd)
  2. 아까전에 경로를 복사하고 cd "[아까 그 경로]"
  3. 아래 명령어 입력
pip install -r requirements.txt

 

 

3. 설치가 완료되었으면 아래의 명령어로 코드의 실행이 가능합니다

python main.py

 

<main 실행화면>

예시로 SCAMPER를 눌러보면 아래와 같이 여러 기법을 눌러볼 수 있습니다

 

문제점/아이디어를 입력하고 각 기법을 누르면 다음과 같이 제안을 해줍니다

Gemini API키를 우측상단에 넣어두셨다면 Gemini 아이디어 버튼을 통해서 Gemini에게 해당 아이디어의 조언을 받을 수 있습니다.

- 프로그램명 : CommentKiller.py
- 제작기간 : '25. 5. 10.(1일)
- 제작자 : REDUCTO
- 사용언어 : Python

- 버전 : v1.0


소개

기술사공부를 하다가 평소 gemini AI와의 대화(Live)로 연습을 하는데 얘한테 input source로 제가 공부하고 있는 파일을 주고 싶어서 만들어 보았습니다. exe로 만들까 하다가 cli기반이고, 파라미터들로 직접조정하는게 좋을거 같다는 것과, 어짜피 python을 기반으로 exe를 만들면 너무 느려지기 때문에, 코드로만 배포합니다. 하단에 사용법에 코드 실행방법까지 자세하게 표기하겠습니다.
 

* 무단배포는 금지합니다.(댓글달아주세용)
* 기능에 커스터마이징이 필요하시다면 댓글달아주세용


사용법

import tkinter as tk
from tkinter import scrolledtext, OptionMenu, StringVar, messagebox, Frame, Label
import re
import json
import os
from datetime import datetime

# --- 상수 정의 ---
PLACEHOLDER_PREFIX = "__COMMENT_KILLER_STR_LITERAL_"
PLACEHOLDER_SUFFIX = "__"
DEFAULT_LANGUAGES_FILE = "languages.json"
BACKUP_DIR = "comment_killer_backups" # 백업 파일 저장 디렉토리
LOG_FILE = os.path.join(BACKUP_DIR, "comment_killer_processing_log.txt") # 로그 파일 경로

# --- 언어 정의 로드 함수 (이전과 동일) ---
def load_language_definitions(filepath=DEFAULT_LANGUAGES_FILE):
    try:
        with open(filepath, 'r', encoding='utf-8') as f:
            definitions = json.load(f)
        return definitions
    except FileNotFoundError:
        messagebox.showerror("파일 오류", f"언어 정의 파일({filepath})을 찾을 수 없습니다.")
        return {}
    except json.JSONDecodeError:
        messagebox.showerror("JSON 오류", f"언어 정의 파일({filepath})의 형식이 잘못되었습니다.")
        return {}
    except Exception as e:
        messagebox.showerror("로드 오류", f"언어 정의 파일({filepath}) 로드 중 오류: {e}")
        return {}

# --- 스마트 주석 제거 함수 (이전과 동일) ---
def remove_comments_smarter(code, language_name, all_language_definitions):
    if language_name not in all_language_definitions:
        messagebox.showerror("오류", f"'{language_name}' 언어는 아직 지원되지 않습니다 (정의 파일 확인 필요).")
        return code

    lang_spec = all_language_definitions[language_name]
    string_patterns = lang_spec.get("strings", [])
    comment_patterns = lang_spec.get("comments", [])

    extracted_strings = []
    current_code = code
    placeholder_idx_counter = 0

    if string_patterns:
        for str_pattern_regex in string_patterns:
            processed_parts = []
            last_end_pos = 0
            for match in re.finditer(str_pattern_regex, current_code):
                start_match, end_match = match.span()
                processed_parts.append(current_code[last_end_pos:start_match])
                original_string = match.group(0)
                extracted_strings.append(original_string)
                placeholder = f"{PLACEHOLDER_PREFIX}{placeholder_idx_counter}{PLACEHOLDER_SUFFIX}"
                processed_parts.append(placeholder)
                placeholder_idx_counter += 1
                last_end_pos = end_match
            processed_parts.append(current_code[last_end_pos:])
            current_code = "".join(processed_parts)

    code_with_strings_replaced = current_code

    for _, comment_pattern_regex in comment_patterns:
        code_with_strings_replaced = re.sub(comment_pattern_regex, "", code_with_strings_replaced)

    final_code = code_with_strings_replaced
    for i in range(len(extracted_strings) - 1, -1, -1):
        placeholder_to_restore = f"{PLACEHOLDER_PREFIX}{i}{PLACEHOLDER_SUFFIX}"
        final_code = final_code.replace(placeholder_to_restore, extracted_strings[i], 1)

    lines = final_code.splitlines()
    non_empty_lines = [line for line in lines if line.strip()]
    processed_code_final = "\n".join(non_empty_lines)
    return processed_code_final

# --- 백업 디렉토리 및 로그 파일 준비 함수 ---
def ensure_backup_infrastructure():
    """백업 디렉토리와 초기 로그 파일을 준비합니다."""
    if not os.path.exists(BACKUP_DIR):
        try:
            os.makedirs(BACKUP_DIR)
        except OSError as e:
            messagebox.showerror("백업 오류", f"백업 디렉토리 생성 실패: {BACKUP_DIR}\n{e}")
            return False # 디렉토리 생성 실패

    # 로그 파일이 없으면 헤더와 함께 생성
    if not os.path.exists(LOG_FILE):
        try:
            with open(LOG_FILE, "w", encoding="utf-8") as logf:
                logf.write("CommentKiller 처리 및 백업 로그\n")
                logf.write("=" * 40 + "\n")
        except Exception as e:
            # 로그 파일 생성 실패는 치명적이지 않으므로 경고만 표시
            messagebox.showwarning("로그 오류", f"로그 파일 생성 실패: {LOG_FILE}\n{e}")
    return True # 인프라 준비 완료 (또는 디렉토리는 이미 존재)

# --- CommentKiller 애플리케이션 UI 클래스 (수정됨) ---
class CommentKillerApp:
    def __init__(self, master):
        self.master = master
        master.title("CommentKiller - 주석 제거기 (백업 기능)")
        master.geometry("900x650") # 높이 약간 늘림 (필요시)

        self.language_definitions = load_language_definitions()
        self.backup_infra_ok = ensure_backup_infrastructure() # 백업 인프라 준비

        # --- UI 요소들 (이전과 동일하게 설정) ---
        top_frame = Frame(master, pady=10)
        top_frame.pack(fill="x", padx=10)
        Label(top_frame, text="프로그래밍 언어 선택:", padx=5).pack(side="left")
        self.language_var = StringVar(master)
        lang_keys = list(self.language_definitions.keys())
        if lang_keys:
            self.language_var.set(lang_keys[0])
            self.language_menu = OptionMenu(top_frame, self.language_var, *lang_keys)
        else:
            self.language_var.set("정의된 언어 없음")
            self.language_menu = OptionMenu(top_frame, self.language_var, "정의된 언어 없음")
        self.language_menu.pack(side="left", padx=5)

        text_frame = Frame(master)
        text_frame.pack(fill="both", expand=True, padx=10, pady=5)
        input_frame = Frame(text_frame)
        input_frame.pack(side="left", fill="both", expand=True, padx=(0, 5))
        Label(input_frame, text="주석이 있는 원본 코드:", pady=5).pack(anchor="w")
        self.input_text = scrolledtext.ScrolledText(input_frame, wrap=tk.WORD, height=15, undo=True)
        self.input_text.pack(fill="both", expand=True)
        output_frame = Frame(text_frame)
        output_frame.pack(side="right", fill="both", expand=True, padx=(5, 0))
        Label(output_frame, text="주석 제거된 코드:", pady=5).pack(anchor="w")
        self.output_text = scrolledtext.ScrolledText(output_frame, wrap=tk.WORD, height=15, undo=True)
        self.output_text.pack(fill="both", expand=True)
        self.output_text.config(state=tk.DISABLED)

        bottom_frame = Frame(master, pady=10)
        bottom_frame.pack(fill="x")
        self.remove_button = tk.Button(bottom_frame, text="주석 제거 실행!", command=self.process_remove_comments, width=20, height=2)
        self.remove_button.pack()

    def process_remove_comments(self):
        if not self.backup_infra_ok:
            messagebox.showerror("오류", "백업 시스템이 준비되지 않아 처리를 중단합니다.\n프로그램을 재시작하거나 폴더 권한을 확인해주세요.")
            return

        original_code_full = self.input_text.get("1.0", tk.END) # 백업을 위해 전체 텍스트 가져오기 (마지막 개행 포함)
        source_code_to_process = original_code_full.strip() # 처리를 위해 앞뒤 공백 제거
        selected_language = self.language_var.get()

        if not source_code_to_process:
            messagebox.showwarning("입력 필요", "제거할 코드를 좌측에 입력해주세요.")
            return

        if selected_language == "정의된 언어 없음" or not self.language_definitions:
             messagebox.showwarning("언어 문제", "선택할 언어가 없거나 언어 정의 파일을 불러오지 못했습니다.")
             return

        # --- 백업 로직 시작 ---
        backup_info_for_user = ""
        backup_filename_leaf = ""
        try:
            # 파일 이름에 사용할 안전한 언어 이름 생성 (특수문자 제거)
            safe_lang_name = "".join(c if c.isalnum() else "_" for c in selected_language)
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # 밀리초까지
            backup_filename_leaf = f"backup_{timestamp}_{safe_lang_name}.txt.back"
            backup_filepath = os.path.join(BACKUP_DIR, backup_filename_leaf)

            with open(backup_filepath, "w", encoding="utf-8") as bf:
                bf.write(original_code_full) # 원본 전체 내용 저장

            # 로그 파일에 기록
            try:
                with open(LOG_FILE, "a", encoding="utf-8") as logf:
                    log_entry = (f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - "
                                 f"백업 파일: {backup_filename_leaf}, 언어: {selected_language}\n")
                    logf.write(log_entry)
            except Exception as log_e:
                # 로그 기록 실패는 경고만 표시, 주 기능에 영향 주지 않음
                 messagebox.showwarning("로그 기록 오류", f"백업 로그 기록 중 오류 발생: {log_e}")


            backup_info_for_user = (f"\n\n[백업 성공]\n"
                                    f"원본이 '{backup_filename_leaf}'로 저장되었습니다.\n"
                                    f"(저장 위치: {os.path.abspath(BACKUP_DIR)})")
        except Exception as e:
            backup_error_details = f"원본 코드 백업 중 오류 발생:\n{e}"
            messagebox.showwarning("백업 오류", backup_error_details) # 백업 실패 시 즉시 알림
            backup_info_for_user = f"\n\n[백업 실패]\n{backup_error_details}"
        # --- 백업 로직 끝 ---

        # 주석 제거 로직 실행
        cleaned_code = remove_comments_smarter(source_code_to_process, selected_language, self.language_definitions)

        self.output_text.config(state=tk.NORMAL)
        self.output_text.delete("1.0", tk.END)
        self.output_text.insert(tk.INSERT, cleaned_code)
        self.output_text.config(state=tk.DISABLED)

        # 최종 결과 메시지
        result_summary = ""
        if source_code_to_process and not cleaned_code.strip() and source_code_to_process.strip():
            result_summary = "모든 내용(주석 포함)이 제거되었거나 입력이 주석으로만 이루어져 있었습니다."
        elif source_code_to_process and cleaned_code.strip():
            result_summary = f"'{selected_language}' 언어의 주석이 성공적으로 제거되었습니다!"
        else: # 입력이 처음부터 비어있었던 경우 (위에서 이미 처리했지만 방어적으로)
            result_summary = "처리할 코드가 없었습니다."

        messagebox.showinfo("처리 결과", result_summary + backup_info_for_user)

# --- 메인 프로그램 실행 부분 (이전과 동일) ---
if __name__ == "__main__":
    root = tk.Tk()
    app = CommentKillerApp(root)
    root.mainloop()

 

* 사전준비

 - python이 설치되어있어야합니다.

 - 필자의 환경은 python 3.13.0입니다. 가급적 맞추어주시거나 상위버전을 사용해 주시는게 좋습니다.

 

해당 파일을 CommentKiller.py로 저장합니다.

bash 셸에서 다음의 명령어를 통해서 실행하면 됩니다.

python CommentKiller.py

EASY합니다. tkinter를 통해 개발하였기 때문에 별도의 라이브러리 설치는 필요없습니다. 다음과 같은 화면을 볼 수 있습니다. 

왼편에 코드를 입력하고 상단에 스크롤을 통해서 언어를 선택하고 주석을 제거할 수 있습니다. 그게답니다. EASY

 

* 혹시나 주석삭제가 제대로 이루어지지 않을 수도 있기 때문에, 같은 폴더 내 comment_killer_backups\ 아래에 제거한 주석의 백업파일을 확인할 수 있습니다. 단순 문자열수준에서 처리하기 때문에 잘 확인해야합니다! (주인장은 날아간거 책임 안짐!😎)

- 프로그램명 : XLS2JSON.py
- 제작기간 : '25. 5. 6.(1일)
- 제작자 : REDUCTO
- 사용언어 : Python
- 사용라이브러리 : pandas, tqdm

- 버전 : v1.0


소개

기술사공부를 하다가 평소 gemini AI와의 대화(Live)로 연습을 하는데 얘한테 input source로 제가 공부하고 있는 파일을 주고 싶어서 만들어 보았습니다. exe로 만들까 하다가 cli기반이고, 파라미터들로 직접조정하는게 좋을거 같다는 것과, 어짜피 python을 기반으로 exe를 만들면 너무 느려지기 때문에, 코드로만 배포합니다. 하단에 사용법에 코드 실행방법까지 자세하게 표기하겠습니다.
 

* 무단배포는 금지합니다.(댓글달아주세용)
* 기능에 커스터마이징이 필요하시다면 댓글달아주세용


사용법

# ==============================================================================
# XLS2JSON: Excel to JSON Converter
# ==============================================================================
#
# 설명:
#   이 스크립트는 Excel 파일(.xlsx, .xls)을 읽어 JSON 형식으로 변환하는
#   명령줄 인터페이스(CLI) 도구입니다. 데이터 시작 위치를 자동으로 감지하며,
#   특정 시트 또는 모든 시트를 변환하고, 결과를 콘솔 또는 파일로 출력할 수 있습니다.
#   여러 시트를 처리할 때 진행률 표시줄을 보여줍니다.
#
# 필요한 라이브러리 설치:
#   pip install pandas openpyxl tqdm
#
# 기본 사용법:
#   python xls2json.py <입력_Excel_파일경로> [옵션]
#
# 옵션:
#   <입력_Excel_파일경로> : 변환할 Excel 파일의 경로 (필수)
#
#   -o, --output <출력_파일경로> :
#       변환된 JSON 결과를 저장할 파일 경로입니다.
#       지정하지 않으면 결과가 콘솔(표준 출력)에 표시됩니다.
#       예: -o result.json
#
#   -s, --sheet <시트_이름_또는_인덱스> :
#       변환할 특정 시트의 이름(문자열) 또는 0부터 시작하는 인덱스(숫자)입니다.
#       기본값은 0 (첫 번째 시트)입니다.
#       --all-sheets 옵션이 사용되면 이 옵션은 무시됩니다.
#       예: -s "데이터 시트"
#       예: -s 1  (두 번째 시트를 의미)
#
#   -a, --all-sheets :
#       Excel 파일 내의 모든 시트를 변환합니다.
#       이 옵션을 사용하면 결과 JSON은 각 시트 이름을 키로 가지는 객체 형태가 됩니다.
#       예: {"Sheet1": [...], "Sheet2": [...]}
#
# 실행 예시:
#   # 1. 첫 번째 시트를 콘솔에 출력
#   python xls2json.py data.xlsx
#
#   # 2. '매출데이터' 시트를 result.json 파일로 저장
#   python xls2json.py data.xlsx -s "매출데이터" -o result.json
#
#   # 3. 두 번째 시트(인덱스 1)를 console_output.json 파일로 저장
#   python xls2json.py data.xlsx -s 1 -o console_output.json
#
#   # 4. 모든 시트를 all_sheets.json 파일로 저장 (시트 이름별로 구분됨)
#   python xls2json.py data.xlsx -a -o all_sheets.json
#
# ==============================================================================

import pandas as pd
import argparse
import sys
import json
import os
from tqdm import tqdm

# --- 이하 코드는 이전과 동일 ---

def find_and_process_data(df):
    """
    데이터프레임에서 데이터 시작 위치를 찾아 처리하고,
    첫 행을 헤더로 사용하여 Python 객체 리스트로 변환합니다.
    """
    df_processed = df.dropna(how='all').dropna(how='all', axis=1)
    if df_processed.empty:
        return []
    new_header = df_processed.iloc[0].astype(str)
    df_processed = df_processed[1:]
    df_processed.columns = new_header
    df_processed = df_processed.reset_index(drop=True)
    df_processed = df_processed.where(pd.notnull(df_processed), None)
    return df_processed.to_dict(orient='records')

def convert_single_sheet_to_json(file_path, sheet_name=0):
    """
    Excel 파일의 지정된 단일 시트를 읽어 자동 감지 후 JSON 문자열로 변환합니다.
    """
    try:
        df = pd.read_excel(file_path, sheet_name=sheet_name, header=None, engine='openpyxl')
        processed_data = find_and_process_data(df)
        json_data = json.dumps(processed_data, indent=4, ensure_ascii=False)
        return json_data
    except FileNotFoundError:
        print(f"오류: 파일을 찾을 수 없습니다 - {file_path}", file=sys.stderr)
        return None
    except ValueError as ve:
        print(f"오류: 시트 '{sheet_name}' 처리 중 오류 발생. 시트 이름/인덱스를 확인하세요. 에러: {ve}", file=sys.stderr)
        return None
    except Exception as e:
        print(f"오류: 단일 시트 처리 중 예외 발생 ({type(e).__name__}) - {e}", file=sys.stderr)
        return None


def convert_all_sheets_to_json(file_path):
    """
    Excel 파일의 모든 시트를 읽어 자동 감지 후 시트 이름을 키로 하는 JSON 객체 문자열로 변환합니다.
    """
    try:
        all_sheets_df_map = pd.read_excel(file_path, sheet_name=None, header=None, engine='openpyxl')

        all_sheets_data = {}
        print("시트 처리 중...", file=sys.stderr)
        for sheet_name, df in tqdm(all_sheets_df_map.items(), desc="Processing sheets", unit="sheet", leave=False):
            try:
                processed_data = find_and_process_data(df)
                all_sheets_data[sheet_name] = processed_data
            except Exception as e:
                print(f"\n경고: 시트 '{sheet_name}' 처리 중 오류 발생하여 건너뜁니다 ({type(e).__name__}) - {e}", file=sys.stderr)
                all_sheets_data[sheet_name] = {"error": f"Failed to process sheet: {e}"}

        json_data = json.dumps(all_sheets_data, indent=4, ensure_ascii=False)
        return json_data

    except FileNotFoundError:
        print(f"오류: 파일을 찾을 수 없습니다 - {file_path}", file=sys.stderr)
        return None
    except Exception as e:
        print(f"오류: 모든 시트 처리 중 예외 발생 ({type(e).__name__}) - {e}", file=sys.stderr)
        return None

def save_to_file(data, output_file):
    """
    주어진 데이터를 지정된 파일 경로에 UTF-8 인코딩으로 저장합니다.
    """
    try:
        output_dir = os.path.dirname(output_file)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir, exist_ok=True)
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(data)
        return True
    except IOError as e:
        print(f"오류: 파일 '{output_file}'에 쓰는 중 오류 발생 - {e}", file=sys.stderr)
        return False
    except Exception as e:
        print(f"오류: 파일 저장 중 예기치 않은 오류 발생 ({type(e).__name__}) - {e}", file=sys.stderr)
        return False

def main():
    parser = argparse.ArgumentParser(
        description="XLS2JSON: Excel to JSON Converter with auto-detection and progress bar.",
        epilog="Example: python xls2json.py data.xlsx -a -o output.json" # 에필로그 추가
    )
    parser.add_argument("excel_file", help="Path to the input Excel file.")
    parser.add_argument(
        "-s", "--sheet",
        default=0,
        help="Specify sheet name or 0-based index to convert (default: 0). Ignored if --all-sheets is used."
    )
    parser.add_argument(
        "-a", "--all-sheets",
        action='store_true',
        help="Convert all sheets in the Excel file. Output JSON will be an object with sheet names as keys."
    )
    parser.add_argument(
        "-o", "--output",
        help="Path to the output JSON file. If not specified, output will be printed to the console."
    )

    args = parser.parse_args()

    json_output = None
    action_description = ""

    if args.all_sheets:
        action_description = "Convert all sheets with auto-detection"
        json_output = convert_all_sheets_to_json(args.excel_file)
    else:
        sheet_identifier = args.sheet
        try:
            sheet_identifier_int = int(args.sheet)
            sheet_identifier = sheet_identifier_int
        except ValueError:
            pass
        action_description = f"Convert sheet '{sheet_identifier}' with auto-detection"
        print(f"Processing: {action_description}...", file=sys.stderr)
        json_output = convert_single_sheet_to_json(args.excel_file, sheet_name=sheet_identifier)

    if json_output:
        if args.output:
            print(f"Saving results to '{args.output}'...", file=sys.stderr)
            if save_to_file(json_output, args.output):
                 print(f"Success: {action_description} completed. Results saved to '{args.output}'.", file=sys.stderr)
            else:
                sys.exit(1)
        else:
            print(json_output) # Print JSON to stdout
    else:
        print(f"Error: Failed during '{action_description}'.", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()

 

* 사전준비

 - python이 설치되어있어야합니다.

 - 필자의 환경은 python 3.13.0입니다. 가급적 맞추어주시거나 상위버전을 사용해 주시는게 좋습니다.

 

1. 해당 파일을 xls2json.py로 저장합니다.

2. 아래 requiremets.txt를 다운로드 받습니다.

requirements.txt
0.00MB

 

3. cmd를 켜고(Python 경로 Path에 포함되어있어야합니다) 다음과 같이 입력합니다(requirements.txt가 있는 경로에서)

pip install -r requirements.txt

 

4. 코드 상단에 사용방법과 같이 XLS2JSON을 사용해 주시면 됩니다. 

# ==============================================================================
# XLS2JSON: Excel to JSON Converter
# ==============================================================================
#
# 설명:
#   이 스크립트는 Excel 파일(.xlsx, .xls)을 읽어 JSON 형식으로 변환하는
#   명령줄 인터페이스(CLI) 도구입니다. 데이터 시작 위치를 자동으로 감지하며,
#   특정 시트 또는 모든 시트를 변환하고, 결과를 콘솔 또는 파일로 출력할 수 있습니다.
#   여러 시트를 처리할 때 진행률 표시줄을 보여줍니다.
#
# 필요한 라이브러리 설치:
#   pip install pandas openpyxl tqdm
#
# 기본 사용법:
#   python xls2json.py <입력_Excel_파일경로> [옵션]
#
# 옵션:
#   <입력_Excel_파일경로> : 변환할 Excel 파일의 경로 (필수)
#
#   -o, --output <출력_파일경로> :
#       변환된 JSON 결과를 저장할 파일 경로입니다.
#       지정하지 않으면 결과가 콘솔(표준 출력)에 표시됩니다.
#       예: -o result.json
#
#   -s, --sheet <시트_이름_또는_인덱스> :
#       변환할 특정 시트의 이름(문자열) 또는 0부터 시작하는 인덱스(숫자)입니다.
#       기본값은 0 (첫 번째 시트)입니다.
#       --all-sheets 옵션이 사용되면 이 옵션은 무시됩니다.
#       예: -s "데이터 시트"
#       예: -s 1  (두 번째 시트를 의미)
#
#   -a, --all-sheets :
#       Excel 파일 내의 모든 시트를 변환합니다.
#       이 옵션을 사용하면 결과 JSON은 각 시트 이름을 키로 가지는 객체 형태가 됩니다.
#       예: {"Sheet1": [...], "Sheet2": [...]}
#
# 실행 예시:
#   # 1. 첫 번째 시트를 콘솔에 출력
#   python xls2json.py data.xlsx
#
#   # 2. '매출데이터' 시트를 result.json 파일로 저장
#   python xls2json.py data.xlsx -s "매출데이터" -o result.json
#
#   # 3. 두 번째 시트(인덱스 1)를 console_output.json 파일로 저장
#   python xls2json.py data.xlsx -s 1 -o console_output.json
#
#   # 4. 모든 시트를 all_sheets.json 파일로 저장 (시트 이름별로 구분됨)
#   python xls2json.py data.xlsx -a -o all_sheets.json
#
# ==============================================================================

 

EASY합니다 끝t

PhotoCollage.zip
15.49MB

 

- 프로그램명 : PhotoCollage.exe
- 제작기간 : '25. 4. 17.(1일)
- 제작자 : REDUCTO
- 사용언어 : Python
- 사용라이브러리 : Tkinter

- 버전 : v1.0


소개

문득 집에서 공부하다가 이런 프로그램이 로컬수준으로 있으면 좋을거 같다는 생각이 들어서 만들어보았습니다. python tkinter로 제작하였고, Java로 할라다가 코드가 너무 길어져서 짧게 치려는 느낌으로 만들었습니다.
 

* 무단배포는 금지합니다.(댓글달아주세용)
* 기능에 커스터마이징이 필요하시다면 댓글달아주세용


사용법

 

프로그램의 화면은 위와 같습니다. 사용자는 캔버스 크기를 지정한다음에 이미지 추가로 여러이미지를 캔버스에 배치할 수 있습니다.(그러고 보니 DnD를 구현안했네요) 아무튼 이렇게 불러온 이미지는 캔버스 여러군데에 자유롭게 배치할 수 있습니다. 테두리나 각 사진들의 경계에 가면 자동으로 붙게끔 만들어보았고 배경사진이나 배경색도 선택할 수 있게 만들어 보았습니다.

 

다 된 사진은 캔버스 저장을 통해서 png파일로 저장할 수 있습니다. 

 

아직 구현되지 않은 기능으로 사진 크기조절이랑 음... 또 뭐가 있으면 좋을까는 생각중입니다! 좋은 아이디어 있으신분은 알려주세요! (아이콘은 지정했는데 resource 로딩 오류같습니다. 혹시나 프로그램을 수정할 일이 생기면 반영해서 고치려구요!

 

* 숨겨진 기능인데, Shift를 누르고 여러사진을 한번에 이동시키거나 [Shift]+화살표로 미세이동 할 수 있습니다!

 

최강 자격증 기출문제 전자문제집 CBT

전자문제집, CBT, 컴씨비티, 씨비티, 기사, 산업기사, 기능사, 컴활, 컴퓨터활용능력, 1급, 2급, 워드, 정보처리, 전기, 소방, 기계, 사무자동화, 정보기기, 제과, 제빵, 한국사, 공무원, 수능, 필기,

www.comcbt.com

PC정비사 공부를 하면서 COMCBT 사이트에서 도움을 받고 있다. 우리나라 자격증의 특징인데, 문제은행식이어서 중복되는 문제들이 너무 많았다. 이를 제거해서 자주출제되었던 것들만 공부할 수 있으면 효율적이지 않을까 하는 생각에 코드를 만들었다. 코드는 다음과 같다.

 

import os
from PyPDF2 import PdfReader

location = ### 저장소 위치, 있는 폴더로 저장하세요 ###"

def extract(i) :
    reader = PdfReader(i)
    pages = len(reader.pages)
    text = ""
    for i in range(pages) :
        text += reader.pages[i].extract_text()
    return text

def duplicate_checker(l,cand) :
    if(cand[0] in l and cand[1] in l and cand[2] in l) :
        return True
    if(cand[0] in l and cand[2] in l and cand[3] in l) :
        return True
    if(cand[0] in l and cand[3] in l ) :
        return True
    if(cand[1] in l and cand[2] in l ) :
        return True
    if(cand[1] in l and cand[3] in l ) :
        return True
    if(cand[2] in l and cand[3] in l ) :
        return True
    return False

def file_write(i,text) :
    with open(str(i)+".txt","wt",encoding="UTF-8") as f:
        f.write(text)

if __name__=="__main__" :

    os.chdir(location)
    try :
        os.mkdir(r".\OUTPUT")
        os.mkdir(r".\RAW")
    except Exception as e:
        pass
    
    RAW_PDF_LIST = os.listdir(r".\RAW")

    ## Extract Code ##

    dict_dat = dict()
    
    count = 0

    for i in RAW_PDF_LIST :
        dict_dat[i] = extract(".\\RAW\\"+i)
        file_write(".\\OUTPUT\\"+str(count),dict_dat[i])
        count+=1
        print(i," done")

    ## Dict Generate Code
    PARSE_PDF_LIST = os.listdir(r".\OUTPUT")
    STAT_Q = 0

    STAT_A = 1
    STAT_B = 2
    STAT_C = 3
    STAT_D = 4

    cand_id_list = ["①","②","③","④"]
    ans_id_list = ["❶","❷","❸","❹"]

    qs = dict()
    total_count = 0

    for txt in PARSE_PDF_LIST :
        data = list()
        with open(".\\OUTPUT\\"+txt,"rt",encoding="UTF-8") as f :
            lines = f.read().split("\n")
        
        q_count = 0
        question = ""
        cand = ["","","",""]
        answer = 0
        cur_state = -1
        
        for l in lines :
            
            write_flag=False
            
            if(l.strip() == "") :
                continue

            
            if(str(q_count+1)+". " in l) :
                if(question != "") :
                    if(question not in qs) :
                        qs[question] = dict()
                    qs[question]["cand"] = cand
                    qs[question]["answer"] = answer
                    if("count" in qs[question]) :
                        qs[question]["count"] += 1
                    else :
                        qs[question]["count"] = 1
                    question = ""
                    cand = ["","","",""]
                    answer = 0
                    total_count +=1
                question = l.split(str(q_count+1)+". ")[1].strip()
                cur_state = STAT_Q
                write_flag = True
                
            
            if(cand_id_list[0] in l) :
                cand[0] = l.split(cand_id_list[0])[1].split(cand_id_list[1])[0]
                cand[0] = cand[0].split(ans_id_list[1])[0]
                cur_state = STAT_A
                write_flag = True
            elif(ans_id_list[0] in l) :
                cand[0] = l.split(ans_id_list[0])[1].split(cand_id_list[1])[0]
                answer = 1
                cur_state = STAT_A
                write_flag = True
            
            if(cand_id_list[1] in l) :
                cand[1] = l.split(cand_id_list[1])[1].split(cand_id_list[2])[0]
                cand[1] = cand[1].split(ans_id_list[2])[0]
                cur_state = STAT_B
                write_flag = True
            elif(ans_id_list[1] in l) :
                cand[1] = l.split(ans_id_list[1])[1].split(cand_id_list[2])[0]
                answer = 2
                cur_state = STAT_B
                write_flag = True
            
            if(cand_id_list[2] in l) :
                cand[2] = l.split(cand_id_list[2])[1].split(cand_id_list[3])[0]
                cand[2] = cand[2].split(ans_id_list[3])[0]
                cur_state = STAT_C
                write_flag = True
            elif(ans_id_list[2] in l) :
                cand[2] = l.split(ans_id_list[2])[1].split(cand_id_list[3])[0]
                answer = 3
                cur_state = STAT_C
                write_flag = True
            
            if(cand_id_list[3] in l) :
                cand[3] = l.split(cand_id_list[3])[1]
                cur_state = STAT_D
                write_flag = True
                q_count+=1
            elif(ans_id_list[3] in l) :
                cand[3] = l.split(ans_id_list[3])[1]
                answer = 4
                cur_state = STAT_D
                write_flag = True
                q_count+=1
            
            if(write_flag==False) :
                if(cur_state==STAT_Q) :
                    question+= l
                elif(cur_state==STAT_A) :
                    cand[0] += l
                elif(cur_state==STAT_B) :
                    cand[1] += l
                elif(cur_state==STAT_C) :
                    cand[2] += l
                elif(cur_state==STAT_D) :
                    cand[3] += l
                    
    qs_sorted = sorted(qs.items(),key = lambda x:x[1]["count"],reverse=True)
                    
    with open("OUTPUT.txt","wt",encoding="utf-8") as f:
        f.write("#### TARGET FILE ####\n")
        for l in RAW_PDF_LIST :
            f.write(l+"\n")
        f.write("\n")
        f.write("Total Question : "+str(total_count)+"\n\n")
        f.write("Trimmed Question : "+str(len(qs_sorted))+"\n\n")

        f.write("\n")
        f.write("### EXAM LINE ###\n")
        for v in qs_sorted :
            f.write("Question : "+v[0]+" ## Count : "+str(v[1]["count"])+"\n\n")
            f.write("1. "+v[1]["cand"][0]+"\n")
            f.write("2. "+v[1]["cand"][1]+"\n")
            f.write("3. "+v[1]["cand"][2]+"\n")
            f.write("4. "+v[1]["cand"][3]+"\n\n")
            f.write("Answer : "+str(v[1]["answer"]))
            f.write("\n\n\n")

    print("PARSE FINISH")

조금 비효율적인 조건문이 있으나, 알바없다.... 사용방법은 폴더아래 RAW라는 폴더를 만들고, 그 안에 comcbt.com에서 다운로드 받은 교사용.pdf를 넣어두고 코드를 실행하면 끝이다. 중복된 문제들을 제외하고, 나왔던 빈도수의 내림차순으로 output.txt가 생성된다.

 

* 참고사항

 1. 텍스트만 추출했기 때문에, 그림문제는 정상적으로 안보인다.

 2. 이유는 모르겠는데, 번호를 구분하는 ① 이런게 한줄에 엄청 많으면 그 파일을 불러오는데 실패한다. 필요하면 나중에 고치지 뭐;

 3. PyPDF2는 pip3로 install 해야한다.

* 프로그램 요청이나 산출물 요청은 댓글로 달아주세요!

시현입니다. 아직 미완인데, 귀차니즘으로 잠깐 정지해두고 다른 공부하다 오려그럽니다. 일전에 스크립트를 포스팅하였으나, 뭐하는 짓인가 싶어 비공개로 돌렸습니다.

프로그램은 사용자가 진입, 구매, 판매조건을 설정하여 그 조건에 맞게 트레이드로봇이 동작하는 구조입니다


MainWindow

기본 화면 구성은 다음과 같다.

위쪽에 menu들은 다음과 같은 하위 항목이 있다.

프로그램은 아래와 같이 tray_icon에 표시되며, 24시간 돌아가야 되는 특성상 X버튼을 눌러도 꺼지지 않고, main 화면만 invisible된다.


SubWindow

1. 거래키 Window

필자의 KEY가 기본으로 들어있어 지우고 사진을 올렸다. 로컬파일인 key.dat을 로딩해서 보여준다.

2. 전체 잔고 확인 Window

가지고 있는 코인을 보여주며, 우클릭으로 판매할 수 있다.(어... VTHO쟨 뭐지?)

3. 시뮬레이터

과거 데이터를 기준으로 구매/판매 조건이 어떤 결과를 가져올지 시뮬레이팅하는 기능이다. 당연히 과거데이터가 한정되어있어 약간의 차이가 존재한다.(upbit에서 ohlcv를 길게 요구하면 중간중간 빠지는 경우가 있더라)

(아... 이거 만들겠다는 생각만 안했으면,... 차라리 안정성이나 늘려두지...)

조건생성을 클릭하면 다음과 같이 구매 / 판매조건을 걸어줄 수 있다. 옆에 [...]버튼을 눌러서 기존 조건을 가져올 수도 있다. 동작중인 조건은 ./criteria 폴더아래 [코인명]_[buy/sell]_criteria.dat로 저장되어 있다.

조건을 클릭하면 다음과 같이 편집할 수 있다.

과거데이터 덤프를 가져오는 화면은 아래와 같다. 궁금한 종목을 가져오고 [가져오기]버튼을 눌러 가져올 수 있다.

가져온 데이터는 ./dump 아래 [코인명].dump로 저장된다.

4. 진입 조건 Window

현재 진입조건으로는 2가지 경우를 만들어 두었다. 업데이트하고 조금 손보면 더 늘어날 예정이다. 글을 쓰는 현재 2분동안 -5%감소한 종목이 없는지 좀처럼 시작을 안해서 2일동안 5%감소한 항목으로 조건을 바꾸어 하나를 시작 시켰다.

짜잔.

저 한 행이 트레이딩봇 한 개이다. 우클릭하면 트레이딩 봇에 대한 동작 기록 확인 / 거래 조건 변경 / 강제 종료 / 일시정지등을 지시할 수 있다.

거래조건확인은 시뮬레이팅에서 본 그 화면과 동일하다. 단 window이름이 코인명으로 지정되어있는데, 글로벌 프리셋에서 복사되어 코인별로 조건을 따로따로 지정하는 동작이다. 현재는 거래가 안되게끔 현재가<현재가로 걸어두었다.

동작기록은 메모장으로 [코인명]_history.log가 열린다. 이 파일은 ./var/log 아래 일자별로 폴더가 생성된다.

마지막으로 강제종료하여 해당 트레이드 봇을 강제종료 시켰다.(...응? 이상한게 하나 시작되었다.)

5. 설정변경 window

프로그램의 동작 설정을 변경할 수 있는 window이다.


Q. 왜 잔고(KRW)가 0인데 시작하나요?

A. 프로그램 테스트 용도로 가짜 돈이 조금 들어가 있습니다.

Q. 남은 개발항목은 어떤게 있나요?

A. 시뮬레이팅 결과화면이 아직 덜 준비되었습니다. 그 외 프로그램의 사용기간 제한이나, 제작자에게 e-mail건의 등을 하는 기능이 미구현상태이고, 전체적으로 안정성을 늘려야합니다. 디버깅없이 열심히 삘받아서 코딩한거라 잔 오류가 조금씩 보이네요. 가능하다면 UI도 조금 꾸미고 싶어요.

Q. 자문자답 쪽팔리지 않으세요?

A. 네

+ Recent posts