자료구조와 알고리즘/자료구조와 함께 배우는 알고리즘

Python - 자료구조와 배열

얄루몬 2021. 5. 31. 20:29

 

자료구조를 정의하고 기본 자료구조인 배열을 살펴보도록 하겠습니다.

 

자료구조란?

데이터 단위와 데이터 자체 사이의 물리적 또는 논리적인 관계

 

배열 - 자료구조의 기본

 

 


2-1 자료구조란? 

배열 개념 알아보기

 

배열을 사용하면 흩어진 변수를 하나로 묶어 사용할 수 있어 코드를 더 쉽고 효율적으로 작성할 수 있습니다.

# 학생 5명의 시험 점수를 입력받아 합계와 평균을 출력하기

print("학생 그룹 점수의 합계와 평균을 구합니다.")

score1 = int(input("1번 학생의 점수를 입력하세요.: "))
score2 = int(input("2번 학생의 점수를 입력하세요.: "))
score3 = int(input("3번 학생의 점수를 입력하세요.: "))
score4 = int(input("4번 학생의 점수를 입력하세요.: "))
score5 = int(input("5번 학생의 점수를 입력하세요.: "))

total = 0

total += score1
total += score2
total += score3
total += score4
total += score5

print(f"합계는 {total}점 입니다.")
print(f"평균은 {total/5}점 입니다."

학생 그룹 점수의 합계와 평균을 구합니다.
1번 학생의 점수를 입력하세요.: 12
2번 학생의 점수를 입력하세요.: 13
3번 학생의 점수를 입력하세요.: 80
4번 학생의 점수를 입력하세요.: 90
5번 학생의 점수를 입력하세요.: 54
합계는 249점 입니다.
평균은 49.8점 입니다.


비슷한 코드가 반복되고 있다 그러나 이 프로그램에 수정을 요구 했을 때 어떻게 수정할 수 있을까?

 

요구 사항 1 : 학생 수를 변경하는 경우

= 학생 수를 먼저 입력받은 다음 합계와 평균을 구하도록 프로그램을 수정해야 한다.

 

요구 사항 2 : 특정 학생의 시험 점수를 확인하거나 변경하는 경우

= 예를 들어 3번 또는 4번의 학생의 점수를 확인하거나 변경하는 기능을 프로그램에 추가해야 한다.

 

요구 사항 3 : 최저점과 최고점을 구하거나 정렬이 필요한 경우

= 최저점과 최고점을 구하는 기능이나 시험 점수를 오름차순 또는 내림차순으로 정렬하는 기능을 프로그램에 추가해야 한다. 이 기능은 학생 수가 변경될 때도 적용해야 한다.

 

- 프로그램을 조금 수정하거나 확장하는 등의 방법으로는 이런 요구 사항을 반영하기가 어렵다는 것을 알 수 있다. 즉, 프로그램의 작성 방식을 근본적으로 바꿔야 이러한 요구사항을 제대로 반영할 수 있다.

-  특히 학생 점수는 하나의 값을 저장하는 변수가 아니라 묶음 단위로 값을 저장하는 배열이라는 자료구조로 다뤄야 한다. 배열에는 객체가 저장이 되며 배열에 저장된 객체 하나하나를 원소(element)라고 합니다. 또한 각 원소는 0,1,2,3,,,,순으로 인덱스(index)를 부여 받습니다.

 

종합적으로 살펴보면 배열이라는 자료구조를 사용하면 위의 요구사항 3가지를 모두 한 번에 해결 할 수 있다. 

또한 파이썬에서 배열 원소의 자료형은 int형 float형 등 어떤 것이라도 상관 없다.


리스트와 튜플 알아보기

 

파이썬에서는 배열을 리스트(List)튜플(Tuple)로 구현할 수 있다.  리스트와 튜플은 데이터 컨테이너(Data container)라고 하며 비슷한 기능을 하는 듯 하지만 원소를 변경할 수 있는지 없는지에 따라 차이가 있다. 

 

리스트의 기초

리스트는 원소를 변경할 수 있는 뮤터블(Mutable) list형 객체이다. 리스트는 연산자 [ ]안에 원소를 쉼표( , )로 구분하고 표기하여 생성할 수 있다. 원소 없이 [ ]만 사용하면 빈 리스트를 생성하게 된다.

list01 = [] 
list02 = [1,2,3]
list03 = ['a','b','c']
list04 = list()
list05 = list("abc") #['a','b','c'] 문자열의 각 문자로부터 원소를 생성
list06 = list([1,2,3]) #[1,2,3] 리스트로부터 원소를 생성
list07 = list((1,2,3)) #[1,2,3] 튜플로부터 원소를 생성
list08 = list({1,2,3}) #[1,2,3] 집합으로부터 원소를 생성
list09 = list(range(7)) #[0,1,2,3,4,5,6]
list10 = list(range(3,8)) #[3,4,5,6,7]
list11 = list(range(3,13,2) #[3,5,7,9,11]
list12 = [None] * 5 #[None,None,None,None,None] 문자열의 자료형도 곱셈 연산자 *을 사용하여 반복하는 문자열로 만들 수 있다.

😊특정 범위의 정수로 구성된 리스트를 만들고자 한다면 range( ), list( )함수를 조합하여 사용하면 된다. 


튜플의 기초

 

튜플은 원소에 순서를 매겨 결합한 것으로 원소를 변경할 수 없는 이뮤터블(Immutable) 자료형 이다. 튜플은 원소를 쉼표( , )로 구분하여 나열한 뒤 결합 연산자( )로 둘러싸는 방식으로 생성한다. 리스트와 마찬가지로 맨 마지막 원소 뒤에 쉼표를 써도 되며, ( )만 사용하면 빈 튜플을 생성합니다. 튜플은 리스트와 다르게 결합 연산자( )를 생략할 수도 있습니다.

 tuple01 = () #빈 튜플
 tuple02 = 1, #결합 연산자 생략과 (1,) 튜플 생성 이때 튜플이라면 꼭 원소 뒤에 쉼표를 입력해야 한다.
 tuple03 = (1,) #(1,) 튜플 생성 쉼표를 붙이지 않으면 단순 변수로 여겨진다. 
 tuple04 = 1, 2, 3 #(1,2,3)
 tuple05 = 1, 2, 3, #(1,2,3,)
 tuple06 = (1,2,3) #(1,2,3)
 tuple07 = (1,2,3,) #(1,2,3,)
 tuple08 = 'A','B','C' #('A','B','C')
 
 tuple09 = tuple() #빈 튜플 생성
 tuple10 = tuple('ABC') #('A','B','C') 문자열의 각 문자로부터 원소를 생성
 tuple11 = tuple([1,2,3]) #리스트로부터 원소를 생성
 tuple12 = tuple({1,2,3}) #집합으로부터 원소를 생성
 tuple13 = tuple(range(7)) #(0,1,2,3,4,5,6)
 tuple14 = tuple(range(3,8)) #(3,4,5,6,7)
 tuple15 = tuple(range(3,13,2)) #(3,5,7,9,11)

🤷‍♀️ 주의해야 할 점: tuple02와 tuple03처럼 원소가 1개인 경우라면 원소 뒤에 꼭 쉼표를 입력해주어야 합니다. 그렇게 하지 않으면 튜플이 아닌 단순 변수로 인식해 버리기 때문입니다.


리스트와 튜플 풀어내기

좌변에는 여러 개의 변수를 놓고 우변에는 리스트나 튜플을 놓으면 우변의 원소를 좌변의 변수에 한 번에 대입할 수 있습니다. 이와 같이 리스트나 튜플의 원솟값들을 풀어 여러 변수에 대입하는 것을 언팩(Unpack)이라고 합니다.

 

x = [1,2,3] #리스트 x선언
a, b, c = x #x를 언팩하여 변수 a,b,c에 대입
a,b,c


(1,2,3) #언팩한 결과

인덱스로 원소에 접근하기

리스트나 튜플의 원소에 접근할 때는 인덱스(imdex)를 사용하면 됩니다. 

양수 인덱스 0             1                      2                     3                   4                  5                    6

11 22 33 44 55 66 77

음수 인덱스 -7            -6                    -5                   -4                   -3                  -2                    -1

 

 

x = [11,22,33,44,55,66,77]
x[2] #리스트 x의 앞에서 3번째 원소를 출력 33이 출력 이때 0부터 시작함을 유의

x[-3] #리스트 x의 뒤에서 3번째 원소 출력 55가 출력 이때 -1부터 시작함을 유의

x[-4] = 3.14 #리스트 x의 뒤에서 4번째 원소에 새로운 값을 대입

x #[11,22,33,3.14,55,66,77] 이때 int형이 float형으로 자동 형변환 됨 또 IDLE 쉘을 쓰면 프린트를 쓰지 않아도 보여줌

x[7] #존재하지 않은 인덱스이기 때문에 오류를 출력한다.
x[7] = 3.14 #x[7]에는 값을 대입할 수 없으므로 오류를 출력한다. 

여기서 알 수 있는 점은 리스트로 선언하지 않은 인덱스를 부를 수 없고 또 그 선언하지 않은 값에 값을 대입하는 것 역시 불가능하다는 점이다. 즉, 존재하지 않은 원소에 접근하거나 대입해도 원소가 새롭게 추가되지 않는다는 점이다.


슬라이드식으로 원소에 접근하기

리스트 또는 튜플의 원소 일부를 연속해서 또는 일정한 간격으로 꺼내 새로운 리스트 또는 튜플을 만드는 것을 슬라이스(slice)라고 한다. 

  • s[i:j] ----- s[i]부터 s[j-1]까지 나열
  • s[i:j:k] ----- s[i]부터 s[j-1]까지 k씩 건너뛰며 나열합니다.

흡사 range( )함수와 비슷하다. 

 

s = [11,22,33,44,55,66,77]

s[0:6] #[11,22,33,44,55,66]

s[0:7] #[11,22,33,44,55,66.77]

s[0:7:2] #[11,33,55]

s[-4:-2] #[44,55]

s[3:1] #[]

  • i,j가 len(s)보다 크면 len(s)가 지정된 것으로 간주된다. 인덱스와 달리 범위에서 벗어나는 값을 지정해도 오류가 나지 않는다. 
  • i가 없거나 None이면 0이 지정된 것으로 간주합니다
  • j가 없거나 None이면 len(s)가 지정된 것으로 간주합니다.
패턴 설명 실행 예 실행 결과
s[ : ] 리스트 s의 원소를 모두 출력한다. s[ : ] []
s[ : n ] 리스트 s의 원소 중에 맨 앞부터 n개까지 출력한다. s[ : 3 ] [11,22,33]
s[ i : ] 리스트 s의 원소 중 s[i]부터 맨 끝까지 출력한다. s[ 3 : ] [44,55,66,77]
s[ -n : ] 리스트 s의 원소 중 -n부터 맨 끝까지 출력한다. s[ -3 : ] [55,66,77]
s[ :: k ] 리스트 s의 원소 중 맨 앞부터 k개씩 건너뛰며 출력한다. s[ :: 2 ] [11,33,55,77]
s[ :: -1] 리스트 s의 원소 중 맨 끝부터 전부 출력한다.  s[ :: -1] [77,66,55,44,33,22,11]
 

 

파이썬은 변수를 선언할 때 자료형을 선언하지 않더라도 변수의 이름에 값을 대입하기만 하면 그 이름의 변수를 사용할 수 있도록 자료형을 자동으로 선언해주는 기능이 있다. 또한 여러 변수에 여러 값을 한꺼번에 대입할 수 있는 기능도 제공한다.

 

누적변수란?
누적변수란 '변숫값에 특정값을 더한 결괏값을 다시 대입하여 업데이트한 변수'를 의미한다. n+=1에서 n이 바로 누적변수이다. n++역시 n+=1과 같은 표현이다.
파이썬의 자료형
  • 뮤터블 자료형: 리스트, 딕셔너리, 집합 등이 있으며 값 변경이 가능하다.
  • 이뮤터블 자료형: 수, 문자열, 튜플 등이 있으며 값 변경이 불가하다.
파이썬의 대입
  • 좌변에 변수 이름이 처음 나온 경우, 그 변수에 맞는 자료형으로 자동 선언 해준다.
  • 대입식은 값 자체가 아니라 참조하는 객체의 식별 번호를 대입한다.
  • 여러 변수에 여러 값을 한꺼번에 대입할 수 있다.

 

또한 파이썬은 대입 기호 =를 연산시에 사용하는 +, *등과 같이 취급하지 않는다(=연산자가 아니다). x+18은 식이지만 x =18은 식이 아니라는 말 결과적으로 자료형 확인도 불가하다. 

자료구조의 개념 알아보기

자료구조(data structure)는 논리적인 관계로 이루어진 데이터 구성을 말한다.

자료구조란? 
데이터 단위와 데이터 자체 사이의 물리적 또는 논리적인 관계

자료구조는 즉, 데이터가 모여있는 구조이며 자료구조를 알아야 하는 이유는 컴퓨터가 처리해야 하는 많은 데이터를 모아 효율적으로 관리하고 구조화하는 데 있다.

 

ex) 튜플 + 리스트 ~> 자료구조에 포함시켜 배열이라고 할 수 있다.

 


리스트와 튜플 1

😶여기서 배열은 리스트와 튜플을 모두 의미한다.

 

len( ) 함수로 배열의 원소 수 구하기

x = [15, 64, 7, 3.14, [32,55],'ABC']
len(x)

>>>>6

원소 자체가 리스트나 집합 또는 튜플인 경우 1개로 계산한다. [33, 55]와 같은 경우는 2개가 아니고 1개로 계산됩니다.

 

min( ), max( ) 함수로 배열의 최솟값과 최댓값 구하기

min( ), max( ) 함수의 인수로 리스트나 튜플을 넘겨주게 되면 그 리스트나 튜플이 가진 원소중 최솟값 또는 최댓값을 얻을 수 있다.

 

빈 배열 판단하기

if x:
	#x가 비어있지 않으면 true 실행

else:
	#x가 비어있으면 false 실행

빈 배열인지를 판단하고 싶다면 배열을 참조하는 변수를 조건식에 그대로 사용하면 된다. 배열이 비어있다면 false가 나올 것이고 배열이 비어있지 않는다면 true를 결과로 보여줄 것입니다.

 

비교 연산자로 배열의 대소 또는 등가 관계 판단하기

[1,2,3] == [1,2,3]
[1,2,3] < [1,2,4]
[1,2,3,4] <= [1,2,3,4]
[1,2,3] < [1,2,3,5]
[1,2,3] < [1,2,3,5] < [1,2,3,5,6] #and 결합
등가성과 동일성은 다릅니다.

파이썬에서는 값을 비교할 때는 등가성(equality)과 동일성(identity)를 사용한다. 등가성 비교는 ==을, 동일성 비교는 is를 사용합니다. 등가성 비교는 좌변과 우변의 값이 같은지 비교하고, 동일성 비교는 값은 물론 객체의 식별번호까지 같은지를 비교합니다. 

  • 객체의 값이 같은지 비교하려면? ==
  • 객체의 값과 식별 번호가 같은지 비교하려면? is

리스트와 튜플의 공통점과 차이점

https://gmnam.tistory.com/170

 

[Python] 리스트(list) vs 튜플(tuple)!

파이썬의 리스트와 튜플의 차이! 코딩을 하다 보면 배열을 다루는 일이 많다. 배열은 비슷한 데이터를 하나의 집합으로 묶어 놓은 자료구조이다. 아마 대부분의 사람들이 파이썬에서 가장 먼

gmnam.tistory.com


2-2 배열이란?

배열을 사용하는 기본 알고리즘을 알아보도록하자

 

 

배열 원소의 최댓값 구하기

#a[0],a[1],a[2]일때의 최댓값과 

maximum = a[0]
if a[1] > maximum :maximum=a[1]
if a[2] > maximum :maximum=a[2]


#a[0],a[1],a[2],a[3]일때 최댓값 구하기

if a[1] > maximum :maximum=a[1]
if a[2] > maximum :maximum=a[2]
if a[3] > maximum :maximum=a[3]

for _in range(1,4):
    a =list(input())



def max_of(a):

    maximum = a[0]
    for i in range(1,len(a)):
        if a[i]>maximum:
            maximum = a[i]


print(f"최댓값은 {a}입니다.")

배열 원소의 최댓값을 구하는 함수 구현하기

#시퀀스 원소의 최댓값 출력하기

from typing import Any, Sequence

def max_of(a: Sequence) -> Any:
    #시퀀스형 a 원소의 최댓값을 반환
    maximum = a[0]
    for i in range(1, len(a)):
        if a[i] > maximum:
            maximum = a[i]
        return maximum


if __name__=='__main__':
    print("배열의 최댓값을 구합니다.")
    num = int(input("원소의 수를 입력하세요: "))
    x = [None] * num  #원소 수가 num인 리스트 생성

    for i in range(num):
        x[i] = int(input(f"x[{i}]값을 입력하세요"))

    print(f"최댓값은 {max_of(x)}입니다.")

☝ 기본 구조 설명 ☝

  1. max_of( )라는 함수를 정의하여 배열 a의 최댓값을 구하도록 한다.
  2. if__name__=='__main__':로 값을 입력받고 max_of( )함수로 최댓값을 구하는 함수를 실행하도록 한다.
함수 어노테이션과 if __name__=='__main__': 에 따라서 프로그램을 실행하도록 작성했다. 

이때, 프로그램의 주석에서는 파이썬 관점에서 시퀀스, 리스트라는 용어를 구분하여 사용한다.
함수 어노테이션이란?
파이썬은 문법의 제약성이 적어 유연성이 높지만 그로 인한 단점도 있다. 자료형 선언을 하지 않아도 변수나 함수를 자유롭게 사용 가능하지만 명시적으로 해석이 어려운 경우가 있다. 그래서 등장한 기능이 어노테이션(annotation, 주석 달기)이라는 기능이고 어노테이션의 가장 큰 장점은 강제성이 없다는 것입니다. 말그대로 주석 달기일 뿐이며 코드 자체에는 어떠한 영향도 미치지 않는다. 함수 어노테이션은 함수의 매개변수와 반환값을 나타내는 역할을 한다. 

 


주석과 자료형 힌트

from typing import AnySequence

Any는 제약이 없는 임의의 자료형을 의미하며 Sequence는 시퀀스형을 의미한다. 
또한 리스트형 바이트 배열형, 문자열형, 튜플형, 바이트열형이 있다

 

def max_of(a: Sequence) -> Any:

  • 건네받는 매개변수 a의 자료형은 Sequence형이다.
  • 반환하는 것은 임의의 자료형인 Any형이다.

 

 

함수 max_of( )의 특성
  • 함수 안에서는 배열 a의 원솟값을 변경하지 않는다.
  • 호출하는 쪽이 넘겨주는 실제 인수의 자료형은 뮤터블인 리스트, 인뮤터블인 튜플, 문자열 등 시퀀스 자료형이라면 무엇이든지 상관 없다.
  • 인수의 원소를 비교 연산자 >로 값을 비교할 수 있다면 다른형(int형, float형)이 섞여 있어도 된다.
  • 최댓값의 원소가 int형 원소이면 int형 값을 반환하고, float형 원소이면 float형 값을 반환합니다.


재사용할 수 있는 모듈 작성하기

 

파이썬에서는 하나의 스크립트 프로그램을 모듈(module)이라고 한다. 확장자(.py)를 포함하지 않는 파일의 이름 자체를 모듈 이름으로 사용합니다. 예를 들어 max.py라고 하면 모듈은 max이고 뒤의 .py는 확장자인 것이다. 

if __name__=='__main__':

if문에서는 __name__ 과  '__main__'이 같은지를 판단한다. 왼쪽 피연산자 name __name__은 모듈 이름을 나타내는 변수이고 작성 규칙을 아래와 같다. 

  • 스크립트 프로그램이 직접 실행될 때 변수 __name__은 '__main__'입니다.
  • 스크립트 프로그램이 임포트될 때 변수 __name__은 원래 모듈의 이름이다. 

👉 https://wikidocs.net/84410 스크립트 프로그램이란 무엇일까? 

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

모든 것을 객체로 다루는 파이썬에서 모듈 역시 객체이다. 모듈은 프로그램이 처음 임포트되는 시점에서 그 모듈 객체가 생성되며 초기화되는 구조입니다. 따라서 if문은 max.py를 직접 시작한 경우에만 참이되어 (즉, __name__과 '__main__'이 일치하는 경우) 아래를 실행할 수 있다.  

👂모듈 객체에는 __name__이외에도 __loader__, __package__, __spec__, __path__, __file__등이 있다.

https://wikidocs.net/29

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

모듈에 관해서 정리해 놓은 곳 다들 가셔서 보셨으면 합니다.


모듈 테스트하기

 

max로 정의된 max_of()함수를 다른 프로그램에서 호출해보는 방법을 살펴보도록 하자.

 

 

입력받을 원소 수를 결정하기 

 

int형 정숫값을 차례로 입력받다가 END를 입력하면 더이상 입력받지 않으며 그 시점에서 원소 수를 확정하는 프로그램입니다. 

 

#배열 원소의 최댓값을 구해서 출력하기(원솟값을 입력받음)

from max import max_of

print("배열의 최댓값을 구합니다.")
print("주의: "End"를 입력하면 종료합니다.")

number = 0
x = [] #빈리스트

while true:
    s = input(f"x[{number}]값을 입력하세요.:")
    if s =='End':
        break
    x.append(int(s)) #입력받는 값을 배열의 맨 뒤에 추가시키는 함수 append
    number+=1

print(f"[{number}]개를 입력했습니다.")
print(f"최댓값은 {max_of(x)입니다.}")

from max import max_of를 사용하여 max_of() 함수를 사용할 수 있도록 임포트 했다. 

또한 x = [ ]로 빈 리스트를 생성하여 11행부터 while문을 실행하고 사용자가 End를 입력하면 break문이 작동하여 while문을 종료한다. 하지만 입력 값이 End가 아니라면 입력받은 문자열을 int() 함수로 변환하여 배열 X에 차례대로 추가한다. 변수 number은 0으로 초기화한 후 정수를 입력받을 때 마다 1씩 증가시키고 입력받은 정수의 개수는 배열 x의 원소 수와 일치하게 된다.

 

 

 

배열의 원솟값을 난수로 결정하기

#난수를 사용하여 최댓값 구하기


import random #난수 사용을 위한 random 함수 호출
from max inport max_of #나는 2-2라고 파일명을 저장했음 저장한 파일명 따라가면 됩니다. 

print("난수의 최댓값을 구합니다.")

num = int(input("난수의 개수를 입력하세요.: "))
lo = int(input("난수의 최솟값을 입력하세요.: "))
hi = int(input("난수의 최솟값을 입력하세요.: "))

x = [None] * num #원소의 갯수가 num개인 리스트 생성

for i in range(num):
    x[i] = random.randint(lo,hi) #lo이상 hi 이사인 정수 난수를 반환
    
print(f"{(x)}")
print(f"이 가운데 최댓값은 {max_of(x)}입니다.")
        

배열의 원소 수, 최댓값, 최솟값은 입력받고, 최댓값과 최솟값 안에서 배열을 구성하는 원솟값 난수로 결정하는 프로그램

 

 

 

튜플, 문자열, 문자열 리스트의 최댓값 구하기

#각 배열 원소의 최댓값을 구해서 출력하기 (튜플, 문자열, 문자열 리스트


from max inport max_of #나는 2-2라고 파일명을 저장했음 저장한 파일명 따라가면 됩니다. 



t = (4,7,5.6,2,3.14,1)
s = "string"
a = ["DTS","AAC","FLAC"]

print(f"{t}의 최댓값은 {max_of(s)}입니다.")
print(f"{s}의 최댓값은 {max_of(s)}입니다.")
print(f"{a}의 최댓값은 {max_of(s)}입니다.")

튜플t는 정수와 실수가 섞여 있어도 최댓값이 7이 출력된다.  s는 문자열로 가장 큰 문자인 t를 출력한다. a는 문자열 리스트로 (모든 원소가 str형 문자열인 list형 리스트)로, 사전 순으로 가장 큰 문자열인 FLAC을 출력한다.

 

 


리스트와 튜플 2 

 

따로따로 생성한 리스트, 튜플의 동일성 판단하기

따로따로 생성한 리스트에서 모든 원소의 값이 같아도 실체는 각각 다르다. 이 말이 무슨 말인지 다음 프롬프트를 확인하도록 하자. 

📄 프롬프트란? 프롬프트는 컴퓨터 터미널의 CLI의 명령줄 대기모드를 가리킨다. 📄
👉 출처: https://g.co/kgs/QoT16F
>>> lst1 = [1,2,3,4,5]
>>> lst2 = [1,2,3,4,5]
>>> lst1 is lst2
False

식별번호가 같은지 연산자 is로 판단하는데 결과가 False이다. (이는 튜플 역시 마찬가지로 False가 뜬다.) lst1과 lst2는 모두 [1,2,3,4,5]로 같은 리스트를 생성한 것처럼 보이지만 이는 리터럴literal(고정된 값)이 아니기 때문에 서로 다른 연산자로 결과가 나온다.

 

리스트, 튜플의 대입

>>> lst2
[1, 2, 3, 4, 5]
>>> lst1=[1,2,3,4,5]
>>> lst2 = lst1
>>> lst1[2]=9


>>> lst2
[1, 2, 9, 4, 5]

리스트 2개 선언하여 서로 대입해도 원소 자체는 복사되지 않는다. 대입 복사되는 것이 아닌 값을 참조하는 것이기 때문이다 (즉, lst1을 lst2가 값을 복사해서 가져온 것 처럼 보이지만 lst1의 리스트 값에 변화가 되었을 때 lst1을 참조한 lst2의 값 역시 변화가 있기 때문이다. 복사해서 가져온 것이라면 lst1의 변화에도 lst2의 변화는 없었을 터)

 

리스트 스캔

#리스트의 모든 원소를 스캔하기(원소 수를 미리 파악)

x = ['john','george','paul','Ringo']

for i in range(len(x)):
    print(f"x[{i}] = {x[i]}")
#리스트의 모든 원소를 enumerate() 함수로 스캔하기


x = ['john','george','paul','Ringo']

for i, name in enumerate(x):
    print(f"x[{i}] = {name}")
📄 enumerate( )함수란? enumerate는 열거하다라는 단어이다. 거기에서는 List, Tuple, String 등 여러가지 자료형을 입력 받으면 그 값을 포함하는 열거를 돌려 준다. 📄
👉 출처: https://hckcksrl.medium.com/python-enumerate-b19ad6b94c00
#리스트의 모든 원소를 enumerate() 함수로 스캔하기(1부터 카운트)


x = ['john','george','paul','Ringo']

for i, name in enumerate(x,1):
    print(f"{i}번째 = {name}")
#리스트의 모든 원소를 스캔하기(인덱스 값을 사용하지 않음)


x = ['john','george','paul','Ringo']

for i in x:
    print(i)

 

튜플의 스캔

x =[ ]로 작성된 리스트를 x = ( )로 수정하는 것만으로 위의 방법으로 스캔 가능하다.

 

이터러블

문자열, 리스트, 튜플, 집합, 딕셔너리 등의 자료형 객체는 모두 이터러블iterable(반복 가능)하다는 공통점이 있다. 이터러블 객체는 원소를 하나씩 꺼내는 구조로 이터러블 객체를 내장함수인 iter( )의 인수로 전달하면 그 객체에 대한 이터레이터iterator(반복자)를 반환합니다. 이터레이터는 데이터의 나열을 표현하는 객체입니다. 이터레이터의 __next__함수를 호출하거나 내장 함수인 next( ) 함수에 이터레이터를 전달하면 원소를 순차적으로 꺼낼 수 있습니다. 꺼낼 원소가 더 이상 없는 경우에는 StopIteration으로 예외 발생을 시킵니다.  

📒위의 글과 함께 참조하여 공부하면 좋을 링크: https://jiminsun.github.io/2018-05-11/Iteration/📒

 


배열 원소를 역순으로 정렬하기

배열 원소를 역순(뒤에서 앞)으로 정렬하는 알고리즘을 생각해 봅시다. 예를 들어 배열 a의 원소가 7개이고 [2,5,1,3,9,6,7]로 저장되어 있다면, 이것을 [7,6,9,3,1,5,2]의 순서로 바꿔 보자. 

 

2 5 1 3 9 6 7

2와 7교환

7 5 1 3 9 6 2

5와 6교환

7 6 1 3 9 5 2

1과 9교환

7 6 9 3 1 5 2

교환 횟수는 원소 수 //2번 7//2 = 3

 

for i in range(n//2):
	a[i]와 a[n-i-1]의 값을 교환

위 코드에서 쉽고 빠르게 이해할 수 있도록 파이썬 명령어와 우리말을 섞어 표현했다. 이와 같이 컴퓨터에서 바로 실행은 안 되지만 알고리즘을 간단하고 분명하게 나타내는 코드를 의사코드(pseudo code)라고 한다.

#뮤터블 시퀀스 원소를 역순으로 정렬

from typing import Any, MutableSequence

def reverse_array(a: MutableSequence) -> None: #뮤터블 시퀀스 a의 원소를 역순으로 정렬

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


if __name__=='__main__':
    print("배열 원소를 역순으로 정렬합니다.")
    nx = int(input('원소 수를 입력하세요.: '))
    x = [None] * nx #원소의 수가 nx인 리스트를 생성

    for i in range(nx):
        x[i] = int(input(f"x[{i}]값을 입력하세요.:"))

    reverse_array(x) #x를 역순으로 정렬


    print("배열 원소를 역순으로 정렬했습니다.")
    for i in range(nx):
        print(f"x[{i}] = {x[i]}")
                    

reverse_array( ) 함수는 인수를 받은 배열 a의 원소를 역순으로 정렬한다. 

 

리스트를 역순으로 정렬하기

파이썬의 표준 라이브러리를 사용하면 리스트를 역순으로 정렬하거나 정렬한 리스트를 새로 생성할 수 있다.

 

리스트를 역순으로 정렬

리스트 자기 자신을(inplace) 역순으로 정렬하려면 list형의 reverse( ) 함수를 사용할 수 있다. 

x. reverse( ) #리스트 x의 원소를 역순으로 정렬

 

역순으로 정렬한 리스트 생성

reversed( ) 함수를 호출하는 reversed(x)는 x의 원소를 역순으로 정렬하는 이터러블 객체를 생성한다. 엄밀하게 말하자면 x의 원소를 역순으로 꺼내는 이터레이터(반복자)를 반환하는 것이다. 따라서 어떤 리스트의 원소를 역순으로 정렬한다면 reversed( ) 함수가 반환하는 이터러블 객체를 list( ) 함수에 넘기고 새로운 리스트를 생성해야 한다. 예를 들어 리스트 x를 역순으로 정렬하여 y에 대입하려면 다음과 같이 작성하면 된다.

y = list(reversed(x))

기수 변환하기(n진수 구하기)

정수값이 임의의 기수로 변환하는 알고리즘을 살펴보도록하자. 10진수 정수를 n진수로 변환하려면 정수를 n으로 나눈 나머지를 구하는 동시에 몫을 반복해서 나눠야 한다. 몫이 0이 될 때까지 이 과정을 반복하고 나머지를 역순으로 늘어 놓으면 기수로 변환한 수가 된다. 

0, 1, 2, 3, 4, 5, 6, 7, 8, 9 A, B, C, D, E, F ----------------------------------------16진수를 표현하는 16개의 문자

기수가 10을 넘는 경우 0~9까지 이어지는 숫자가 끝나면 알파벳 문자인 A, B, C....를 사용합니다. A, B, C,.... 는 10진수인 10, 11, 12...등에 해당한다.(0부터 시작하고 F까지니까 F까지가 15인셈)

 

기수와 서수

기수는 수를 나타내는데 기초가 되며 10진법에서는 0~9까지의 정수를 말한다. 이 10개의 기수를 사용하여 무수히 많은 값을 수로 나타낼 수 있다. 그리고 서수는 사물의 순서를 나타낸다. 간단히 말해서 1, 2, 3...이 기수라면 서수는 첫째, 둘째, 셋째,... 라고 생각하면 된다. 또 숫자는 수를 나타내는 기호로 10진법에서는 10개의 숫자를 사용하고, 수는 숫자를 사용하여 무수히 많은 값을 나타낼 수 있다.

 

기수 살펴보기

n진수는 n을 기수로 하는 수다. 10진수, 8진수, 16진수를 예로 들어 각각의 기수를 간단히 살펴보자

 

10진수

10진수(decimal)는 다음과 같이 숫자 10종류를 사용하여 수를 나타낸다

0, 1, 2, 3, 4, 5, 6, 7, 8, 9

0~9까지의 숫자 10종류를 모두 사용하면 한 자릿수를 올려서 10이 된다. 이를 2자릿수라고 하며 10~99까지 표현할 수 있다. 99부터 또 한 자릿수를 올려서 3자릿수(100)가 된다. 

  • 1자릿수: 0~9까지의 수를 나타낸다.
  • 2자릿수: 10~99까지의 수를 나타낸다.
  • 3자릿수: 100~999까지의 수를 나타낸다

10진수의 각 자리는 아랫자리부터 10^0, 10^1, 10^2...으로 10의 거듭제곱값으로 표현한다. (아래는 예시)

1234 = 10^3 +2 X 10^2 + 3 X 10^1 + 4 X 10^0

 

8진수

8진수(octal)는 다음과 같이 숫자 8종류를 사용하여 수를 나타낸다.

0, 1, 2, 3, 4, 5, 6, 7
  • 1자릿수: 0~7까지의 수를 나타낸다.
  • 2자릿수: 10~77까지의 수를 나타낸다.
  • 3자릿수: 100~777까지의 수를 나타낸다

8진수의 각 자리는 아랫자리부터 8^0, 8^1, 8^2....으로 8의 거듭제곱값을 갖는다. (아래는 예시)

5306 = 5 X 8^3 + 3 X 8^2 + 0 X 8^1 + 6 X 8^0

8진수 5306을 10진수로 나타내면 2,758입니다.

 

16진수

16진수(hexadecimal)는 다음과 같이 숫자 16종류를 사용하여 수를 나타낸다.

0, 1, 2, 3, 4, 5, 6, 7, 8, 9 A, B, C, D, E, F 

0~F는 10진수로 0~15에 해당한다. (A~F까지 표현할 때 소문자도 상관 없다.) 0~F까지 사용했다면 자릿값을 올리면 된다.

  • 1자릿수: 0~F까지의 수를 나타낸다.
  • 2자릿수: 10~FF까지의 수를 나타낸다.
  • 3자릿수: 100~FFF까지의 수를 나타낸다

12A0 = 1 X 16^3 + 2 X 16^2 + A X 16^1 + 0 X 16^0

도입부로 cord_conv() 함수를 수행

#10진수 정수값을 입력받아 2~36진수로 변환하여 출력하기


def card_conv(x:int,r:int) ->  str:
#정숫값 x를 r진수로 변환한 뒤 그 수를 나타내는 문자열을 반환

    d = "" #변환 후의 문자열
    dchar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

    while x > 0:
        d += dchar[x % r] #해당하는 문자를 꺼내 결합
        x //= r

    return d[::-1] #역순으로 변환

메인부로 기수 변환을 실행

if __name__ == '__main__':
    print("10진수를 n진수로 변환합니다.")

    while True: #마지막 행의 계속성을 위해서 while True문을 사용
        while True:
            no = int(input("변환할 값으로 음이 아닌 정수를 입력하세요.: "))
            if no > 0:
                break #변환할 값을 음수로 입력할 때 계속해서 무한루프를 돌고 이때 정수를 입력받으면 break문으로 파괴
        while True:
            cd = int(input("어떤 진수로 변환할까요?: "))
            if 2 <= cd <= 36:
                break

        print(f"{cd}진수로는 {2-7(no, cd)}입니다.") #2-7은 모듈이름

        retry = input("한 번 더 변환할까요?(Y .... 예 / N .... 아니요): ")
        if retry in {'N','n'}: #n N을 입력하면 프로그램 종료.
            break
        
#10진수 정수값을 입력받아 2~36진수로 변환하여 출력하기


def card_conv(x:int,r:int) ->  str:
#정숫값 x를 r진수로 변환한 뒤 그 수를 나타내는 문자열을 반환

    d = "" #변환 후의 문자열
    dchar = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    n = len(str(x)) #변환 전의 자릿수

    print(f"{r:2}|{x:{n}d}")

    
    while x > 0:
        print(" " + (n + 2)*"-")
        if x //r:
            print(f"{r:2}|{x//r:{n}d}...{x%r}")
        else:
            print(f"      {x//r:{n}d}...{x%r}")
        d += dchar [x%r]
        x //= r 
    return d[::-1] #역순으로 변환

함수 사이에 인수 주고받기

#1부터 n까지의 정수의 합 구하기

def sum_1ton(n):
    #1부터 n까지의 정수의 합

    s = 0
    while n > 0:
        s += n
        n -= 1

        #print(f"s = {s}  n = {n}") 어떻게 흘러가는 것인지 흐름을 보기 위해서 print문을 써서 확인했다.  
    return s

x = int(input("x의 값을 입력하세요.: "))

print(f"1부터 {x}까지의 정수의 합은 {sum_1ton(x)}입니다.")

1 + 2 + ..+x까지가 아니고 x + x-1 + x-2 ... x-x로 역순으로 더하는 구조임

sum_1ton( ) 함수의 실행과정에서 매개변수 n값은 5->4->...으로 1씩 감소한다. 함수가 종료할 때 n값은 0이다.

호출하는 곳에서 sum_1ton( ) 함수로 전달하는 실제 인수는 x이며 (n은 매개변수, 실제 인수는 x) 함수에서 돌아온 뒤 "1부터 5까지의 정수의 합은 15입니다." 라고 출력되므로 변수 x의 값(5)은 호출하기 전의 값 그대로인 것을 확인 할 수 있다. 

매개변수 n으로 실제 인수 x의 값이 복사(COPY)되었습니다. X🙅‍♀️🙅‍♂️X

파이썬에서는 매개변수(n)에 실제 인수(x)가 대입된다. (여기서n과 x는 위의 코드를 참조해주세요)

n과 x가 참조하는 곳은 같다. 그러나 매개변수 n의 값을 sum_1ton( )함수 안에서 변경했는데도 실제 인수 x의 값이 변경되지 않은 이유는 int가 이뮤터블 타입이기 때문이다. 이때 변수 n은 업데이트 될 시점에서 다른 객체를 참조하게 되기 때문에 함수를 종료할 때 n이 참조하는 곳은 x와 다르게(x는 이뮤터블타입으로 계속 int 5를 참조) 정수 0을 참조하게 된다.

 

<함수 실행 시작 시점>

 

x가 대입된 n은 5를 참조한다.

 

 

 

 

 

 

 

 

 

<함수 실행 종료 시점>

x = int형 5는 변경 불가하다.

n = n은 0을 참조한다.

 

 

 

 

파이썬에서 인수 전달은 실제 인수인 객체에 대한 참조를 값으로 전달하여 매개변수에 대입되는 방식입니다. 다른 프로그래밍 언어에서는 실제 인수의 값을 매개변수에 복사하는 값에 의한 호출(call by value)을 사용하거나, 실제 인수의 참조를 매개변수에 복사하여 매개변수가 실제 인수와 같아지는 참조에 의한 호출(call by reference)을 사용합니다.

하치만 파이썬에서는 이 2가지 호출의 중간적인 방식으로 참조하는 값을 전달합니다. 파이썬 공식 문서에서는 객체 참조에 의한 전달(call by object reference)이라는 용어를 사용하여 설명하고 있다. 함수 사이의 인수 전달을 정리하면 다음과 같다. 


함수의 실행 시작 시점에서 매개변수는 실제 인수와 같은 객체를 참조한다. 함수에서 매개변수의 값을 변경하면 인수의 형(Type)에 따라 다음과 같이 구분합니다. 

  1. 인수가 이뮤터블일 때(변경 불가): 함수 안에서 매개변수의 값을 변경하면 다른 객체를 생성하고 그 객체에 대한 참조로 업데이트됩니다. 따라서 매개변수의 값을 변경해도 호출하는 쪽의 실제 인수에는 영향을 주지 않습니다.

  2. 인수가 뮤터블일 때(변경 가능): 수 안에서 매개변수의 값을 변경하면 객체 자체를 업데이트합니다. 따라서 매개변수의 값을 변경하면 호출하는 쪽의 실제 인수는 값이 변경됩니다.

호출하는 쪽 = 실제 인수

호출 당하는 쪽 = 매개 변수


소수 나열하기

어떤 정수 이하의 소수(prime number)를 모두 나열하는 알고리즘을 살펴보자.

소수는 자신가 1이외의 정수로 나누어 떨어지지 않는 정수이다. 예를 들어 소수 13은 1과 13을 제외한 어떤 정수로도 나누어 떨어지지 않는다. 그러므로 어떤 정수 n은 다음 조건을 만족하면 소수임을 알 수 있다.

2부터 n-1까지 어떤 정수로도 나누어 떨어지지 않는다.

만약 나누어 떨어지는 정수가 하나 이상 존재하면 그 수는 합성수(composite number)이다. 

#1000이하의 소수를 나열하기

count = 0

for n in range(2, 1001): #2이상 1,000이하의 소수
    for i in range(2,n): 
        count += 1
        if n%i == 0:
            break
        else:
            print(n)

print(f"나눗셈을 실행한 횟수: {count}")
#1000이하의 소수를 나열하기(알고리즘 개선1)

counter = 0 # 나눗셈 횟수 카운트
ptr = 0 # 이미 찾은 소수의 개수
prime = [None] * 500 # 소수를 저장하는 배열

prime[ptr] = 2 # 2는 소수 prime[0] = 2 / 배열값에 저장
ptr += 1

prime[ptr] = 3 # 3는 소수 prime[1] = 2 / 배열값에 저장
ptr += 1



for n in range(5, 1001, 2): #홀수만을 대상으로 설정 5,7,9,,,,,
    i = 1
    while prime[i] * prime[i] <= n:
        counter += 2 #이미 소수 2와 3이 들어가 있기 때문에 counter에 +2
        if n % prime[i] == 0: # 나누어 떨어지는 경우 소수가 아님
            break
        i+=1
        
        else: #소수 배열에 등록 
            prime[ptr] = n 
            ptr += 1
            counter += 1

for i in range(ptr):
    print(prime[i])
print(f"나눗셈을 실행한 횟수: {counter}")

 


리스트의 원소와 복사

파이썬에서 변수는 객체와 연결된 이름에 불과하다. 따라서 리스트, 튜플의 원소 자료형을 미리 정의할 필요가 없다. 

#자료형을 정하지 않은 리스트 원소 확인하기

x = [15,64,7,3.14,[32,55],'ABC']

for i in range(len(x)):
    print(f"x[{i}]={x[i]}")

x[0]=15
x[1]=64
x[2]=7
x[3]=3.14
x[4]=[32, 55]
x[5]=ABC
>>> 

 

리스트를 복사할 때 사용하는 copy( ) 함수는 주의하여 사용해야 한다. 리스트를 복사한 뒤 원솟값을 변경하면 복사된 원솟값까지 변경이 될 수 있기 때문이다. 

iport copy
x = [[1,2,3],[4,5,6]]
y = x.copy() #x를 y로 얕은 복사

x[0][1] = 9

이 경우에 y의 y[0][1]값은 9로 복사 되어 y = [[1,9,3][4,5,6]]라는 값이 나올 것이다 


얕은 복사 : 얕은 복사는 객체가 자료형의 멤버를 포함하는 경우를 말하고 이는 참조값만 복사하는 방식이다.

깊은 복사 : copy.deepcopy(x)를 사용해서 깊은 복사가 가능하고 이때 깊은 복사는 참조값 뿐만 아니라 참조하는 객체 자체를 복사한다. 즉, 객체가 가지고 있는 모든 멤버(값과 참조 형식 모두)를 복사하므로 전체 복사라고도 한다.