본문 바로가기

학습 노트/알고리즘 (Python)

Python #3

서론


이전 시간에 풀다 남은 276번 부터 290번 까지.
파일 입출력을 건너 뛰고 에러 처리에 해당하는 4문제 가량을 진행했다.

 

본론


20220416.ipynb
0.02MB

 

276번 부터 280번 까지는 이전의 271번 부터 275번 까지 문제의 연장선이다.
따라서 이전의 코드를 개선하는 방향으로 진행했다.

문제와 조건은 다음과 같다.

  • Account 인스턴스에 저장된 정보를 충역하는 display_info 메서드 추가할 것.
    단, 잔고는 세자리마다 쉼표를 출력할 것.
  • 입금 횟수가 5회가 될 때 잔고 기준 1%의 이자가 발생 되도록 할 것.
  • 생성된 Account 인스턴스를 리스트에 저장할 것.
  • 반복문을 사용해 100만원 이상인 고객의 정보를 출력할 것.
  • 입금과 출금 내역이 기록되도록 코드를 업데이트 할 것.
    각각을 출력하는 deposit_history 메서드와 withdraw_history 메서드를 추가할 것.

 

Egg's

import random
import math
import datetime as date

CustomList = []

def genAccount():
  top = 0
  middle = 0
  bottom = 0

  top = str(random.random())[4:7]
  middle = str(random.random())[4:6]
  bottom = str(random.random())[4:10]

  result = top + '-' + middle + '-' + bottom

  return result

def benefit(original):
  benefit = original * 0.01
  result = math.floor(original + benefit)
  return result

class Account:
  counter = 0
  
  def __init__(self,customer_name,cost):
    self.log = {}
    self.log[date.datetime.now().strftime("%A %d. %B %Y. %H:%M:%S.%f")] = "Generate"
    print(type(self.log))
    self.bank_name = "SC_bank"
    self.customer_name = customer_name
    self.account_num = genAccount()
    self.cost = cost
    self.depositCnt = 0
    Account.counter += 1
    CustomList.append(self)

  def get_aacount_num():
    print(Account.counter)

  def deposit(self, deposit):
    if deposit > 0:
      self.cost += deposit
      self.depositCnt += 1
      #이자지급
      if self.depositCnt % 5 == 0:
        self.cost = benefit(self.cost)
      self.log[date.datetime.now().strftime("%A %d. %B %Y. %H:%M:%S.%f")] = "Deposit"
      print(type(self.log))
    else:
      print('deposit must over 0')

  def withdraw(self, withdraw):
    if withdraw > self.cost:
      print('can\'t withdraw over current cost.')
    else:
      self.cost -= withdraw
      self.log[date.datetime.now().strftime("%A %d. %B %Y. %H:%M:%S.%f")] = "Withdraw"

  def display_info(self):
    print("은행이름:", self.bank_name)
    print("예금주:", self.customer_name)
    print("계좌번호:", self.account_num)
    print(format(self.cost, ','))

  def deposit_history(self):
    for key, value in self.log.items():
      keys = []
      if value == "Deposit":
        keys.append(key)
      print(keys)
  
  def withdraw_history(self):
    for key, value in self.log.items():
      if value == "Withdraw":
        print(key)

def printOver(CustomList):
  for target in CustomList:
    if target.cost > 1000000:
      target.display_info()

display_info 메서드는 인스턴스의 속성에 접근해 특정 양식으로 출력하도록 구현했다.
잔고를 세 자리씩 끊어 출력하기 위해 format 메서드를 이용했다.

입금 횟수를 계산하기 위해 새로운 속성인 depositCnt를 정의했다.
deposit 메서드가 동작을 완료할 때 해당 변수를 1 증가시킨다.
해당 변수가 5의 배수일 때 1%의 이자를 잔고에 추가하도록 구현했다.

Account 인스턴스를 리스트에 저장하기 위해 CustomList를 선언하고,
Account 클래스의 생성자에서 속성을 초기화 한 다음 해당 리스트에 append 하도록 구현했다.

100만원 이상의 잔고를 가진 고객의 정보를 출력하기 위해 새로운 함수 printOver를 정의했다.
CustomList를 순회하며 잔액을 확인하고 100만원 이상이면 즉시 display_info 메서드를 호출하도록 구현했다.

입출금 내역을 저장할 딕셔너리인 log를 선언했다.
입금과 출금시 해당 딕셔너리에 쌍을 추가한다.
Key는 유일값이어야 하고, 가장 사용하기 쉬운 현재 시간을 Key로 사용했다.
기본적으로 반환하는 값이 date 형식이기 때문에 strftime 메서드로 형식을 지정해 저장했다.
초 단위로 저장되면 빠른 연산이 진행 될 시 로그가 덮히는 문제가 생겨
밀리초 단위까지 표시해 모든 로그가 저장될 수 있도록 구현했다.

입출력 내역을 출력할 deposit_history 메서드와 withdraw_history 메서드는
log 딕셔너리에서 Value를 검색해 해당하는 모든 Key들을 출력한다.

리뷰중 log에 쌍 추가시 Value를 리스트로 선언해 키워드와 함께 입출금 잔액을 추가하는 방향을 제시 받았다.
이는 이후 스위프트로 변환하면서 추가해 볼 생각이다.

 

Rabbit's

import random
import datetime

def randnum():
    ran_num1 = random.randint(0,999)
    ran_num2 = random.randint(0,99)
    ran_num3 = random.randint(0,999999)
        
    num1 = str(ran_num1).zfill(3)
    num2 = str(ran_num2).zfill(2)
    num3 = str(ran_num3).zfill(6)
        
    final = num1+"-"+num2+"-"+num3
    return final

class Account:
    
    Hu_count = 0
    put_count = 0
    i = 0
    
    deposit_log = []
    deposit_money = 0
    withdraw_log = []
    withdraw_money = 0
    
    def __init__(self, name, money):
        self.bank = "SC은행"
        self.name = name
        self.code = randnum()
        self.money = money
        
        Account.Hu_count += 1
        
    def get_account_num(self):
        print("{}".format(self.count))
        
    def deposit(self, money):
        Account.deposit_money = money
        if money >= 1:
            print("{}".format(self.money + money))
            self.money += money
            Account.put_count += 1
        else:
            print("입금이 불가합니다.")
        
        if Account.put_count%5==0:
            print("이자 추가요")
            print("{}".format(self.money + self.money*0.01))
            self.money += self.money*0.01
            
    def withdraw(self, money):
        Account.withdraw_money = money
        if money > self.money:
            print("출금이 불가합니다.")
        else:
            print("{}".format(self.money - money))
    
    def display_info(self):
        print("은행 이름: ", self.bank)
        print("예금주: ", self.name)
        print("계좌번호: ", self.code)
        print("잔고: ",format(self.money,','), "원")
    
    def deposit_history(self):
        Account.deposit_log.append(Account.deposit_money)
        d = datetime.datetime.now()
        print("+",Account.deposit_money,"원", d)
    
    def withdraw_history(self):
        Account.withdraw_log.append(Account.withdraw_money)
        d = datetime.datetime.now()
        print("-",Account.withdraw_money,"원", d)
data = []
a = Account("A", 10000000000)
b = Account("B", 100000)
c = Account("C", 500000000)

data.append(a)
data.append(b)
data.append(c)

배열에 직접 append 하는 방식으로 문제를 해결했다.

for num in data:
    if num.money >= 1000000:
        print("{}".format(num.name))

잔고가 100만원 이상인 고객의 정보를 출력하기 위해
리스트를 순회하며 money 속성을 비교한다.
for문의 밸류바인딩을 이해하고 사용한 모습이다.

입출금 완료시 각각 deposit_log와 withdraw_log 리스트에 날짜를 저장하고,
직전에 입출금 된 금액을 별도의 속성에 저장해 표시하도록 돼있다.
이 경우 가장 최근의 입출금 금액만 보여줄 수 있다는 문제가 존재한다.

 

Whale's

import random, datetime
class Account :
  count=0
  dList=[]
  wList=[]
  def rand() :
    i=random.randrange(1,999)
    i2=random.randrange(1,99)
    i3=random.randrange(1,999)
    
    
    if (i/100)>=1 :
      r=str(i)
    elif (i/100)<1 :
      if (i/10)>=1 and (i/10)<10 :
        r="0"+str(i)
      elif (i/10)<1 :
        r="00"+str(i)

    if (i2/10)>=1 :
      r2=str(i2)
    elif (i2/10)<1 :
      r2="0"+str(i2)

    if (i3/100)>=1 :
      r3=str(i3)
    elif (i3/100)<1 :
      if (i3/10)>=1 and (i3/10)<10 :
        r3="0"+str(i3)
      elif (i3/10)<1 :
        r3="00"+str(i3)

    re=r+"-"+r2+"-"+r3

    return re;

  def __init__(self, name, money) -> None:

      self.name=name
      self.money=money
      self.bank="SC은행"
      self.number=Account.rand()
      self.sCount=0
      Account.count+=1

  def get_number(self) :
    return self.count

  def deposit(self, don) :
    if don>=1 :
      self.money+=don
      self.sCount+=1

      if (self.sCount/5)>=1 and (self.sCount%5)==0 :
        self.money+=(self.money)*0.1

      d=datetime.datetime.now()

      depLi=[d, don]

      Account.dList.append(depLi)

      print(don, "원 추가")
    else :
      print("1원 이상 넣어주세요")

  def withdraw(self, don) :
    if self.money<don :
      print("없는걸 달라하니 니놈은 강도로구나")
    elif self.money>=don and don>=0 :
      self.money-=don
      d=datetime.datetime.now()
      
      withLi=[d, don]

      Account.wList.append(withLi)

      print(don,"만큼 출금되었습니다. |n 남은 금액은 ", self.money,"원 입니다." )

  def display_info(self) :
    print("은행이름 :"+self.bank)
    print("예금주 :"+self.name)
    print("계좌번호 :"+self.number)
    print("잔고 : ", format(int(self.money),','))

  def insList(self) :
    li=[self.bank, self.name, self.number, self.money]
    return li;

  def deposit_history(self) :
    print(Account.dList)

  def withdraw_history(self) :
    print(Account.wList)
    
for i in range(len(data)) :
  if data[i].money >= 1000000 :
    data[i].display_info()

입금 횟수를 계산하기 위해 새로운 속성인 depositCnt를 정의하고,
완료되는 시점에 이자를 지급하도록 구현했다.
이 때 중복된 조건이 두 개 인 점과, 실수로 10%의 이자율을 부과한 것이 지적 포인트였다.

data=[]
a=Account("a", 10000000)
b=Account("b", 1000000)
c=Account("c", 10000)

data.append(a)
data.append(b)
data.append(c)

Whale도 별도의 기능으로 구현하지 않고,
생성된 계좌를 직접 배열에 추가하는 방식으로 문제를 해결했다.

for i in range(len(data)) :
  if data[i].money >= 1000000 :
    data[i].display_info()

100만원 이상의 잔고를 가진 고객의 정보를 출력하기 위해 특정 데이터를 체크해 display_info 메서드를 호출한다.
Python의 for문은 Whale이 배웠던 Java와는 조금 다르게 밸류바인딩의 성격을 가지고 있어
지금과 같이 서브스트링 문법을 고집할 필요는 없다는 것이 지적 포인트였다.

c.deposit(1000000)
c.deposit(121212)
c.deposit_history()

c.withdraw(100)
c.withdraw(200)
c.withdraw_history()
결과

1000000 원 추가
121212 원 추가
[[datetime.datetime(2022, 4, 16, 5, 19, 27, 876465), 100],
 [datetime.datetime(2022, 4, 16, 5, 19, 33, 310208), 1000000],
[datetime.datetime(2022, 4, 16, 5, 19, 33, 310360), 121212],
[datetime.datetime(2022, 4, 16, 5, 21, 5, 156039), 1000000],
[datetime.datetime(2022, 4, 16, 5, 21, 5, 158407), 121212]]

100 만큼 출금되었습니다. |n 남은 금액은 2252024 원 입니다.
200 만큼 출금되었습니다. |n 남은 금액은 2251824 원 입니다.
[[datetime.datetime(2022, 4, 16, 5, 19, 33, 310600), 100],
[datetime.datetime(2022, 4, 16, 5, 19, 33, 310773), 200],
[datetime.datetime(2022, 4, 16, 5, 21, 5, 160355), 100],
[datetime.datetime(2022, 4, 16, 5, 21, 5, 160553), 200]]

입출금 대역은 deposit 메서드와 withdraw 메서드가 동작을 완료할 때 
각각 dList와 wList에 값을 저장하도록 구현 돼있다.

정의된 deposit_history 메서드와 withdraw_history 메서드는 해당 리스트를 출력하도록 돼있다.
다만 마찬가지로 datetime 클래스의 now 메서드의 반환값이 date 형식이기 때문에 원하는 형태의 로그는 아니었고,
strftime 메서드와 포멧을 지정하는 방법을 소개했다.

 

결론


지난시간에 만들었던 결과물을 개선해 이번 시간 까지의 요구 기능들을 추가해 봤다.

import UIKit
import Foundation

let bankList = ["SC은행"]
var accountList: [Account] = []
var accountAmount = 0

class Account {
	var account: String = ""
	var name: String
	var bank: String
	var balance: Int
	var deposit_cnt = 0
	var log: [Date:[String:Int]] = [:]
	
	func genAccount() -> String {
		var head: String
		var thorax: String
		var abdomen: String
		var result: String
		
		head = String(format: "%03d", Int.random(in: 1...999))
		thorax = String(format: "%02d", Int.random(in: 1...99))
		abdomen = String(format: "%06d", Int.random(in: 1...999999))
		
		result = String(format: "%@-%@-%@", arguments: [head, thorax, abdomen])
		
		return result
	}
	
	init(nameVal: String, balanceVal: Int) {
		bank = bankList[0]
		name = nameVal
		balance = balanceVal
		account = genAccount()
		accountAmount += 1
		
		log[Date.now] = ["Generate":balanceVal]
		
		accountList.append(self)
	}
	
	func get_account_num() -> Int {
		return accountAmount
	}
	
	func deposit(depositVal: Int) {
		if depositVal > 0 {
			balance += depositVal
			deposit_cnt += 1
		} else {
			print("deposit val must over 0.")
		}
		
		log[Date.now] = ["Deposit":depositVal]
		
		if deposit_cnt % 5 == 0 {
			let interestVal = Int(ceil(Double(balance) * 0.01))
			balance += interestVal
			
			log[Date.now] = ["Interest":interestVal]
		}
	}
	
	func withdraw(withdrawVal: Int) {
		if (0...balance).contains(withdrawVal) {
			balance -= withdrawVal
		} else {
			print("withdarwVal must between in 1 to 'account balance'")
		}
		
		log[Date.now] = ["Withdraw":withdrawVal]
	}
	
	func display_info() {
		let formatter_num = NumberFormatter()
		formatter_num.numberStyle = .decimal
		print(name)
		print(bank)
		print(account)
		print(formatter_num.string(from: NSNumber(value: balance))!)
	}
	
	func deposit_history() {
		let sortedLog = log.sorted(by: {$0.0 < $1.0})
		sortedLog.forEach { (key: Date, value: [String : Int]) in
			for target in value {
				if target.key == "Deposit" {
					print(key, target.key, target.value)
				}
			}
		}
	}
	
	func withdraw_history() {
		let sortedLog = log.sorted(by: {$0.0 < $1.0})
		sortedLog.forEach { (key: Date, value: [String : Int]) in
			for target in value {
				if target.key == "Withdraw" {
					print(key, target.key, target.value)
				}
			}
		}
	}
	
	func account_history() {
		let sortedLog = log.sorted(by: {$0.0 < $1.0})
		
		for target in sortedLog {
			for detail in target.value {
				print(target.key, detail.key, detail.value)
			}
		}
	}
}

func search(value: Int) {
	var result: [Account] = []
	for account in accountList {
		if account.balance >= value {
			result.append(account)
		}
	}
	
	if result.isEmpty {
		print("searched 0")
	} else {
		for target in result {
			target.display_info()
		}
	}
}

구현한 기본적인 기능들은 Python으로 작성했던 코드와 동일하다.

정보 출력

	func display_info() {
		let formatter_num = NumberFormatter()
		formatter_num.numberStyle = .decimal
		print(name)
		print(bank)
		print(account)
		print(formatter_num.string(from: NSNumber(value: balance))!)
	}

잔고를 세자리씩 끊어 표시하기 위해 NumberFormatter를 사용했다.
Formatter에 전달되는 값은 NSNumber의 형식이어야 하므로 저장된 balance 속성을 한번 타입캐스팅 해 줬다.

이자 지급

	func deposit(depositVal: Int) {
		if depositVal > 0 {
			balance += depositVal
			deposit_cnt += 1
		} else {
			print("deposit val must over 0.")
		}
		
		log[Date.now] = ["Deposit":depositVal]
		
		if deposit_cnt % 5 == 0 {
			let interestVal = Int(ceil(Double(balance) * 0.01))
			balance += interestVal
			
			log[Date.now] = ["Interest":interestVal]
		}
	}

Swift의 연산은 Python보다 엄격하다.
이항 연산은 좌변과 우변의 형식이 같아야 하며,
이는 Int16과 Int64를 구분할 정도로 매우 엄격하다.

따라서 0.01을 곱하기 위해 balance 속성을 Double로 캐스팅하고,
소숫점을 버린 뒤 다시 Int로 캐스팅 해 적용하는 모습이다.

Python으로 작성했던 때와는 변화를 주기 위해 이자를 지급할 때도 로그를 남기도록 구혔했다.

객체 순회

func search(value: Int) {
	var result: [Account] = []
	for account in accountList {
		if account.balance >= value {
			result.append(account)
		}
	}
	
	if result.isEmpty {
		print("searched 0")
	} else {
		for target in result {
			target.display_info()
		}
	}
}

Python의 search_over 메서드를 대체하는 메서드이다.
100만원의 고정된 값이 아닌 전달되는 유동적인 값들에 대응해 검색하고,
계좌의 정보를 출력할 수 있도록 변경했다.

입출금 내역

	var log: [Date:[String:Int]] = [:]

입출금된 잔액을 함께 남기기 위해 이중 딕셔너리를 사용하는 log를 선언했다.
Swift에서 빈 Collection을 선언할 때는 형식추론을 사용할 수 없음을 기억하자.
위와 같이 형식 지정의 방식이 돼야한다.

처음에는 배열을 값으로 갖는 딕셔너리를 사용하려고 했었는데,
생각해 보니 저장하는 데이터의 형식이 다양해서 녹록치 않음을 알고 선회했다.
정리하는 지금 다시 생각해 보면 그냥 튜플을 사용하면 되는 거 아니었나 하는 자괴감도 든다. 😇

	func deposit_history() {
		let sortedLog = log.sorted(by: {$0.0 < $1.0})
		sortedLog.forEach { (key: Date, value: [String : Int]) in
			for target in value {
				if target.key == "Deposit" {
					print(key, target.key, target.value)
				}
			}
		}
	}

딕셔너리에 두 번 접근해야 하므로 조금 번거로워 졌다.
value의 value에 접근해야 하므로 key, value를 간편하게 바인딩 하고 싶었고,
최초 접근시 for-in 문이 아닌 forEach를 사용한 것을 볼 수 있다.
이후에는 Key를 비교해 일치하는 정보들만 출력하도록 구현했다.

Python으로 작성할 때는 빠뜨렸던 부분인데,
Swift도 Python도 딕셔너리는 정렬되지 않은 Collection이다.
따라서 순서대로 출력하기 위해 sorted(by:) 메서드를 사용해 정렬을 한 번 한 뒤,
동작을 시작할 수 있도록 조치했다.

길다면 길고 짧다면 짧은 Python 문법 훑어 보기는 막이 내렸다.
이 날 시험삼아 알고리즘 문제를 하나 풀어봤었는데 생각보다 형편 없다는 것을 다시 한 번 느낀 하루였다.

'학습 노트 > 알고리즘 (Python)' 카테고리의 다른 글

Stack #1  (0) 2022.06.04
선형 데이터 구조와 탐색  (0) 2022.05.03
Q1. 방 배정 하기  (0) 2022.04.22
Python #2  (0) 2022.04.15
Python #1  (0) 2022.03.23