파이썬(Python) 리스트(list)에서 중복된 원소를 제거하는 방법을 정리


환경

  • Python


중복된 원소들 제거

  • 딕셔너리(dictionary)에 키 값으로 넣었다가 다시 리스트로 변환한다.
  • 아래 방식으로 하면 리스트(list)에 집어 넣은 순서가 유지된다.
  • 버전마다 방식이 달라서 맞게 사용해야 한다. 관련된 내용들은 하단 참고자료에 첨부했다.


Python 2.7 이상

OrderedDict.fromkeys(iterable[, value])

  • 주어진 값들을 키 값으로 사용하는 딕셔너리(dictionary)를 반환한다. value가 있다면 모든 키 값들에 대해 value로 값이 정해지며 없다면 None이 된다.
  • OrderedDict: 파이썬 2.7부터 사용 가능
  • iterable에 추가된 원소들의 순서가 유지

예시

ex_list = [3, 4, 5, 6, 10, 1, 4, 5, 7, 8, 1, 5, 9]
from collections import OrderedDict
result = list(OrderedDict.fromkeys(ex_list))
print(result)
[3, 4, 5, 6, 10, 1, 7, 8, 9]

예시

ex_list = ['e', 'e', 'b', 'c', 'a', 'e', 'd', 'd', 'b', 'a']
from collections import OrderedDict
result = list(OrderedDict.fromkeys(ex_list))
print(result)
['e', 'b', 'c', 'a', 'd']


Python 3.7 이상

dict.fromkeys(iterable[, value])

  • 주어진 값들을 키 값으로 사용하는 딕셔너리(dictionary)를 반환한다. value가 있다면 모든 키 값들에 대해 value로 값이 정해지며 없다면 None이 된다.
  • 파이썬 3.7부터 iterable에 추가된 원소들의 순서가 유지
  • 3.6에서는 일부 구현은 지원하고 일부는 지원하지 않음

예시

ex_list = [3, 4, 5, 6, 10, 1, 4, 5, 7, 8, 1, 5, 9]
result = list(dict.fromkeys(ex_list))
print(result)
[3, 4, 5, 6, 10, 1, 7, 8, 9]

예시

ex_list = ['e', 'e', 'b', 'c', 'a', 'e', 'd', 'd', 'b', 'a']
result = list(dict.fromkeys(ex_list))
print(result)
['e', 'b', 'c', 'a', 'd']


참고자료

Post about removing duplicated elements in Python list


Environment and Prerequisite

  • Python


Remove Duplicated Elements

  • Insert to dictionary as key values and change those to list again.
  • By following below methods inserted order in list is preserved.
  • Different versions have different methods, so they should be used accordingly. Related things are in Reference.


Python 2.7 or higher

OrderedDict.fromkeys(iterable[, value])

  • Returns a dictionary using the given values ​​as key values. If there is a value, the value is set as value for all key values, otherwise it is None.
  • OrderedDict: Available starting with Python 2.7
  • Inserted elements order in iterable is preserved

Example

ex_list = [3, 4, 5, 6, 10, 1, 4, 5, 7, 8, 1, 5, 9]
from collections import OrderedDict
result = list(OrderedDict.fromkeys(ex_list))
print(result)
[3, 4, 5, 6, 10, 1, 7, 8, 9]

Example

ex_list = ['e', 'e', 'b', 'c', 'a', 'e', 'd', 'd', 'b', 'a']
from collections import OrderedDict
result = list(OrderedDict.fromkeys(ex_list))
print(result)
['e', 'b', 'c', 'a', 'd']


Python 3.7 or higher

dict.fromkeys(iterable[, value])

  • Returns a dictionary using the given values ​​as key values. If there is a value, the value is set as value for all key values, otherwise it is None.
  • Inserted elements order in iterable is preserved starting with Python 3.7
  • Partially supported in 3.6 implementation

Example

ex_list = [3, 4, 5, 6, 10, 1, 4, 5, 7, 8, 1, 5, 9]
result = list(dict.fromkeys(ex_list))
print(result)
[3, 4, 5, 6, 10, 1, 7, 8, 9]

Example

ex_list = ['e', 'e', 'b', 'c', 'a', 'e', 'd', 'd', 'b', 'a']
result = list(dict.fromkeys(ex_list))
print(result)
['e', 'b', 'c', 'a', 'd']


Reference

Dockerfile로 도커 이미지를 빌드할 때 다운로드할 파일이 변경되었어도 캐싱된 레이어를 사용해 변경된 파일이 적용되지 않는 경우에 대해서 정리


환경

  • Linux
  • Docker


문제 및 해결 방법

문제 상황

  • Dockerfile를 통해서 도커 이미지를 빌드할 때 RUN에서 curl을 이용해 파일을 다운로드 받는 부분이 있다. 이때, 새로운 이미지 빌드시 파일이 변경되었음에도 처음에 캐싱된 레이어의 파일이 들어가서 이전 파일이 이미지에 포함되는 문제가 발생한다.

문제 상황 예시

  • 파일 확인
$ curl https://twpower.me/test.txt
test1
  • Dockerfile 예시
$ cat Dockerfile
FROM ubuntu:18.04
RUN apt-get update -y
RUN apt-get install curl -y
RUN curl https://twpower.me/test.txt -o test.txt
CMD cat test.txt
  • 도커 이미지 빌드
$ sudo docker build --tag test:1.0 .
  • 빌드된 이미지 실행
$ sudo docker run test:1.0
test1
  • 변경된 파일 확인
$ curl https://twpower.me/test.txt
test2
  • 이미지 다른 버전으로 빌드
  • 하단에 캐시 이용하는거 확인
$ sudo docker build --tag test:2.0 .
Sending build context to Docker daemon  2.048kB
Step 1/5 : FROM ubuntu:18.04
 ---> 56def654ec22
Step 2/5 : RUN apt-get update -y
 ---> Using cache
 ---> 47696d4f4d5f
Step 3/5 : RUN apt-get install curl -y
 ---> Using cache
 ---> 15080e39cb8b
Step 4/5 : RUN curl https://twpower.me/test.txt -o test.txt
 ---> Using cache
 ---> 75bbac2b003f
Step 5/5 : CMD cat test.txt
 ---> Using cache
 ---> 3ce1a5c10a5b
Successfully built 3ce1a5c10a5b
Successfully tagged test:2.0
  • 빌드된 이미지 실행
  • 파일은 변경되었으나 변경된 파일이 적용되지 않은 문제 발생!
$ sudo docker run test:2.0
test1
$ curl https://twpower.me/test.txt
test2


원인

  • Dockerfile에서는 명령어 한줄 한줄을 레이어(Layer)로 판단하며 해당 레이어(Layer)별로 캐싱을 진행하기 때문에 만약 특정줄에 해당하는 명령어와 이전 명령어들이 동일하다면 동일한 레이어(Layer)로 판단하기 때문에 캐싱되어 있는 부분을 가져오기 때문이다.
  • 위의 예제에서는 파일이 변경되었어도 명령어 RUN curl https://twpower.me/test.txt -o test.txt는 동일하다.
  • 공식 홈페이지 문서 https://docs.docker.com/develop/develop-images/dockerfile_best-practices/에 따르면 “A Docker image consists of read-only layers each of which represents a Dockerfile instruction. The layers are stacked and each one is a delta of the changes from the previous layer.”라고 나와있다.


해결 방법

방법 1

  • --no-cache를 이용해 도커 이미지 빌드시 캐시를 사용하지 않도록 설정
$ sudo docker build --no-cache --tag test:3.0 .

방법 2

  • (파일 제공자측) 사용하는 파일명 뒤에 버전을 태깅


참고자료

Post about the case that changed file is not applied due to a cached layer even though the file to be downloaded is changed when building a docker image using Dockerfile


Environment and Prerequisite

  • Linux
  • Docker


Problem and Solution

Issue

  • When building a docker image through a Dockerfile, there is a part to download a file using curl in RUN. At this time, even though the file is changed when a new image is built, the file of the initially cached layer is included so the previous file is included in the image.

Issue Case Example

  • Check file
$ curl https://twpower.me/test.txt
test1
  • Dockerfile example
$ cat Dockerfile
FROM ubuntu:18.04
RUN apt-get update -y
RUN apt-get install curl -y
RUN curl https://twpower.me/test.txt -o test.txt
CMD cat test.txt
  • Build docker image
$ sudo docker build --tag test:1.0 .
  • Run the built image
$ sudo docker run test:1.0
test1
  • Check updated file
$ curl https://twpower.me/test.txt
test2
  • Build docker image with another version
  • Check that the cache is used in below
$ sudo docker build --tag test:2.0 .
Sending build context to Docker daemon  2.048kB
Step 1/5 : FROM ubuntu:18.04
 ---> 56def654ec22
Step 2/5 : RUN apt-get update -y
 ---> Using cache
 ---> 47696d4f4d5f
Step 3/5 : RUN apt-get install curl -y
 ---> Using cache
 ---> 15080e39cb8b
Step 4/5 : RUN curl https://twpower.me/test.txt -o test.txt
 ---> Using cache
 ---> 75bbac2b003f
Step 5/5 : CMD cat test.txt
 ---> Using cache
 ---> 3ce1a5c10a5b
Successfully built 3ce1a5c10a5b
Successfully tagged test:2.0
  • Run the built image
  • The file is changed but the changed file is not applied!
$ sudo docker run test:2.0
test1
$ curl https://twpower.me/test.txt
test2


Cause

  • In Dockerfile, each instruction line is considered as layer and cache is created per that layer. So if specific line and previous lines are same then, it is considered to be a same layer so it load layer from cache if it exists.
  • In above example, RUN curl https://twpower.me/test.txt -o test.txt is same but file is updated.
  • According to official document https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ it says “A Docker image consists of read-only layers each of which represents a Dockerfile instruction. The layers are stacked and each one is a delta of the changes from the previous layer.”.


Solution Methods

Solution 1

  • Use --no-cache to disable the cache using when building docker images
$ sudo docker build --no-cache --tag test:3.0 .

Solution 2

  • (File server side) Tag version to file


Reference

임시 변수를 사용하지 않고 값을 교체할 수 있는 XOR 교체 알고리즘에 대해 알아보자


환경

  • XOR
  • 논리 회로
  • 부울 대수


XOR 연산

XOR

  • XOR: XOR 연산이란 bit단위에서는 아래의 예시처럼 서로의 비트가 다르면 1 같으면 0의 결과를 나타내는 연산자입니다.
1^1 = 0
1^0 = 1
0^1 = 1
0^0 = 0
// Example in binary
10010101
XOR
10111100

==> 00101001
// Example in decimal
149
XOR
188

==> 41


XOR 교체 알고리즘

XOR 교체 알고리즘이란?

  • XOR 교체 알고리즘이란 임시 변수를 사용하지 않고 서로 다른 두 개의 변수를 XOR 비트 연산을 이용해 값을 교체하는 알고리즘이다.


XOR 교체 알고리즘 코드 및 예시

예시

의사코드

X := X XOR Y
Y := Y XOR X
X := X XOR Y

C++ 코드

#include <iostream>
#include <stdio.h>

using namespace std;

int main(){

    int x = 1;
    int y = 2;

    printf("x: %d y: %d\n", x, y);

    if (x != y){
        x = x^y;
        y = y^x;
        x = x^y;    
    }

    printf("x: %d y: %d\n", x, y);

    return 0;
}


증명

사용되는 법칙

  • 교환법칙: A ⊕ B = B ⊕ A
  • 결합법칙: (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)

증명

  • 아래의 식을 사용
1. X := X XOR Y
2. Y := Y XOR X
3. X := X XOR Y
R1, R2 are registers

0. R1 = X, R2 = Y

1. R1 <- R1 ⊕ R2 = X ⊕ Y
=> R1 = X ⊕ Y, R2 = Y

2. R2 <- R2 ⊕ R1 = Y ⊕ (X ⊕ Y) = Y ⊕ (Y ⊕ X) = (Y ⊕ Y) ⊕ X = 0 ⊕ X = X
=> R1 = X ⊕ Y, R2 = X

3. R1 <- R1 ⊕ R2 = X ⊕ Y ⊕ X = Y ⊕ X ⊕ X = Y ⊕ (X ⊕ X) = Y ⊕ 0 = Y
=> R1 = Y, R2 = X


사용 사례 및 범위

  • RAM이 매우 한정적인 임베디드 환경
  • Register pressure가 높은 곳


사용하지 않는 이유

  • 컴파일러 수준에서 최적화가 진행되기 때문에 임시 변수를 사용하는 방법이 더 빠르다.
  • 사용법을 모르는 사람에게는 모호하다.
  • 최신 컴퓨터 아키텍처는 명령어 파이프라인에 의해 명령어 수준의 병렬처리가 가능해서 임시 변수를 이용한 교체가 더 빠르다. XOR 교체 알고리즘은 반드시 순서대로 진행되어야 하기에 병렬처리가 어렵다.


참고자료