얼레벌레

[py] 기본 문법 본문

취업/KT AIVLE SCHOOL

[py] 기본 문법

__Lucie__ 2022. 8. 4. 19:04

Python Programming

7월 28, 29일 : 파이썬 기본 문법을 배웠다.

기본적인 입출력부터 간단한 연산, list 자료구조, 검색 알고리즘에 대해 알아보았다.

 

fstring

length = input('정사각형의 한 변의 길이를 입력하세요.')
print(f'정사각형의 넓이는 {int(length)**2}입니다.)

위의 코드는 기본적인 입력과 출력, fstring에 대한 내용이다.

  • input을 통해 사용자에게 입력받으면 사용자가 입력한 내용이 string(문자열)으로 저장된다.
  • fstring을 이용하면 문자열 내에 변수를 포함해서 출력할 수 있다.
    (c언어의 printf와 비슷한 것 같다. fstring의 f와 printf의 f가 동일한 format이라는 걸 이 글을 쓰면서 알아간다..)

 

그리고

  • if 사용법 (if , elif, else)
  • while 사용법
  • for 사용법
    range(1, 3) 이면 1부터 3까지가 아니라 1부터 2까지임
  • 반복문 사용시 무한루프 & break, continue와 break

를 배웠다.

 

자료구조

List

리스트는 C++의 배열과 비슷하다.

하나의 리스트에 여러가지 원소를 담을 수 있다.

 

Tuple

튜플은 리스트와 비슷한데, 원소의 값을 변경할 수 없다.

튜플과 리스트에는 차이가 여러가지 있다.

  • 튜플은 원소의 값 변경 불가능
    리스트는 원소의 값 변경 가능
  • 선언 시 튜플은 ()로 선언,
    리스트는 []로 선언

 

리스트의 모든 원소를 스캔하기

[1] 전통적인 방식

x = ['John', 'George', 'Paul', 'Ringo']
for i in range(len(x)):
	print(f'x[{i}] = {x[i]}')

[2] enumerate 함수 활용

enumerate 함수란, 인덱스와 원소를 짝지어서 튜플로 꺼내는 함수이다.

x = ['John', 'George', 'Paul', 'Ringo']
for i, name in enumerate(x):
	print(f'x[{i}] = {name}')

[3] 인덱스 값을 사용하지 않는 경우

x = ['John', 'George', 'Paul', 'Ringo']
for i in x:
	print(i)

원소의 값만 필요하면 3번째 방식, 인덱스가 필요하다면 2번째 방식을 이용하자.

 

원소 역순 정렬 (순서를 뒤집는 것)

[19, 4, 6, 7, 2, 5, 8, 3] 이렇게 들어왔을 때

[19, 8, 7, 6, 5, 4, 3, 2] 이렇게 정렬하는 것이 아니라 [3, 8, 5, 2, 7, 6, 4, 19] 이렇게 뒤집는 문제이다.

리스트를 반으로 쪼개서

0번째 인덱스 <-> n-1번째 인덱스, 1번째 인덱스 <-> n-2번째 인덱스

이런식으로 진행해주면 된다.

def reverse_list(a):
	n = len(a)
    for i in range(n//2):
    	a[i], a[n-1-i] = a[n-1-i], a[i]

** 파이썬에서는 n/2라고 그냥 치면 float가 되어버린다. type 선언을 안해줘서일까?

(어이없고 신기하기도 했다. 맨날 c++만 했던 나에게는 너무나 생소한 파이썬)

c++의 n/2 효과를 내려면 n//2라고 해주어야 한다. n//2는 나누기인데 소수점을 버린다.

 

검색 알고리즘

리스트에 여러 원소가 있기 때문에 특정 값이 있는지 검색하는 알고리즘이 필요하다.

파이썬 리스트 함수 중에 index() 함수가 있다고는 하는데, 배우는 입장이니까 ^^

선형 검색 (Linear Search)

검색 알고리즘에서 제일 쉬운 선형 검색이다.

직선 모양으로 늘어선 리스트에서 검색하는 경우, 원하는 값을 가진 원소를 찾을 때까지 맨 앞부터 스캔해서 순서대로 검색하는 알고리즘이다.

  • 검색에 성공한 경우 : 검색할 값과 같은 원소를 찾은 경우
  • 검색에 실패한 경우 : 검색할 값과 같은 원소를 찾지 못한 채 리스트의 맨 끝에 도달하는 경우

for문 또는 while문을 사용할 수 있다.

# for loop 사용
def seq_search_for(a, key): #a리스트에서 key 값 찾기
	for i in range(lent(a)):
    	if a[i] == key:
        	return i
    return -1
# while loop 사용

def seq_search_while(a, key):
	i=0
    while True:
    	if i==len(a):
        	return -1
        if a[i] == key:
        	return i
        i+=1

** 보초법 (sentinel method) **

while문 선형 검색에서는 아래처럼 두가지를 언제나 체크한다.

  1. 검색할 값과 같은 원소를 찾은 경우 (성공)
  2. 검색할 값을 찾지 못하고 리스트의 맨 끝에 도달한 경우 (실패)

이 2.를 줄이고 싶어서 원래 리스트의 맨 뒤에 보초를 하나 붙인다.

이 때 이 보초값은 검색해야하는 대상인 key값으로 붙여준다.

그래서 이 방법을 사용하면 언제나 성공하는 것처럼 보인다.

하지만 사실상 인덱스 값을 확인해서 찾은 위치가 보초인지 진짜로 값을 찾은 건지 체크해야 한다.

def seq_search_sentinel(a, key):
	
    b = a.copy()
    b.append(key)
    
    i=0
    while True:
    	if a[i] == key:
        	break
        i+=1
    return -1 if i==len(a) else i
  • 보초법을 쓰는 이유
    • 보초법을 사용하지 않는 while 문 선형 탐색의 경우, while 문 안에서 if 검색을 2번 해야 한다.
    • 하지만 보초법을 사용하면 if 검색을 1번만 할 수 있다.
    • 그래서 시간복잡도를 1.5배 줄일 수 있다.
  • 위 코드에서 b=a.copy() 를 하는 이유는
    • 그냥 b=a 를 하면 '얕은 복사'로, b의 값을 변경하면 a의 값까지 같이 변경된다.
    • 하지만 b=a.copy() 를 하면 copy() 함수가 '깊은 복사'로, b의 값 변경이 a에게 영향을 주지 않는다.

얕은 복사 vs 깊은 복사

사실 얕은 복사 깊은 복사에 대한 내용이 이 날 채팅에 올라왔었지만, 강사님이 설명해주시지 않았다. ㅠㅠ

얕은 복사와 깊은 복사가 무엇인지는 어제(7일차) 알게 되었다.

<얕은 복사>

우리가 a=2 를 먼저 선언하고, b=a를 통해 b에 a 값을 넣어주지만,

사실상 a를 선언 할 때 메모리 어딘가에 2를 저장하고 a가 가리키는 것은 그 메모리 어딘가에 있는 주솟값이다.

그래서 b=a 라고 하면 a가 가리키고 있는 주솟값을 b가 가리키게 되고,

그렇게 되니까 b=3 이렇게 변경을 하면 a까지 3으로 변경된다.

<깊은 복사>

하지만 b=a.copy() 로 b를 만들면 말이 달라진다.

저렇게 b를 만들면 메모리 어딘가에 새로 2를 저장하고 그 새로운 곳을 b가 가리킨다.

그래서 b=a.copy() 후에 b=3으로 변경한다면 a에는 영향을 주지 않는다.

이진 검색 (binary search)

이진검색은 리스트의 데이터가 오름차순/내림차순으로 정렬되어 있어야 적용 가능하다.

우리가 '1-100까지 중에 내가 숫자 하나 생각할 테니까 맞춰봐~' 놀이 할 때

50->50보다 작으면 25 ->25보다 크면 35->... 이런식으로 절반 절반 타고 들어가는 게 사실 이진 검색을 하고 있던 거다.

 

** 이진 검색 프로세스

  1. 중앙에 위치한 값을 찾는다.
  2. 지금 중간에 위치한 값보다 찾을 값이 더 큰 경우 -> 뒤의 인덱스들을 보고
    만약 찾을 값이 더 작은 경우 -> 앞의 인덱스들을 본다.

이렇게 하면 선형 탐색보다 검색횟수가 적어진다.

 

이걸 코드로 바꾸기 위해 일반화를 해보자.

검색 범위 맨 앞 인덱스를 pl, 맨 끝 pr, 중앙 pc 라고 하면,

맨 처음에 pl은 0, pr은 n-1, pc는 (n-1)//2 이다.

  1. a[pc] < key : pl을 pc+1로 변경, pr은 그대로, pc는 (pl+pr)//2
  2. a[pc] > key : pr을 pc-1로 변경, pl은 그대로, pc는 (pl+pr)//2
  3. 종료 조건: a[pc]와 key가 일치할 경우, 또는 검색 범위가 더이상 없는 경우

이진탐색 그림

def bin_search(a, key):
	pl = 0
    pr = len(a)-1
    while True:
    	pc = (pl+pr)//2
        if a[pc] == key:
        	return pc
        elif a[pc] > key:
        	pr = pc-1
        elif a[pc] < key:
        	pl = pc+1
        if pl>pr:
        	break
	return -1

 

내가 궁금해서 찾아봤던 것

  1. 삼항 연산자
    참인경우 값 if 조건 else 거짓인 경우 값
  2. enumerate() 함수
    enumerate() 함수는 인자로 넘어온 목록을 기준으로 인덱스와 원소를 차례대로 접근하게 해주는 반복자(iterator) 객체를 반환해주는 함수
  3. map 함수
    여러가지 수를 입력 받을 때 아래 처럼 map 함수를 이용해 입력받는 에이블러가 있어서 map 함수에 대해 궁금해졌다.
    a, b = map(int, input().split()) 
    map 함수 사용법은 map(적용시킬 함수, 적용할 값) 이렇게 사용한다.
    그래서 이렇게도 사용할 수 있다.
    a = list(str, range(10))
  4. 제곱근 : math 패키지를 import 하고나서 sqrt() 함수를 사용한다.
    import math
    math.sqrt(n)