파이썬에서는 2진수, 8진수, 10진수, 16진수로 쉽게 변환할 수 있도록 내장함수가 포함되어 있습니다. 하지만 3진수나 4진수와 같이 다른 진수로 변환하고자 하면 직접 구현을 해야 합니다. (조사를 했지만 파이썬에서는 따로 제공하지 않는 것으로 확인)

이러한 요구사항 때문에 아래에 2진수부터 16진수까지 변환하는 코드를 작성했습니다.

NOTATION = '0123456789ABCDEF'


def numeral_system(number, base):
    q, r = divmod(number, base)
    n = NOTATION[r]
    return numeral_system(q, base) + n if q else n


# 2진수
result = numeral_system(18, 2)
# 4진수
result1 = numeral_system(18, 2)
# 11진수
result2 = numeral_system(18, 11)

print(result)
print(result1)
print(result2)


# 10010
# 102
# 17


만약 16진수보다 더 큰 범위를 표현하고자 하면 아래와 같이 알파벳을 늘려주면 됩니다. (다만 16진수 이후 알파벳 표현식이 맞는지는 확실치 않음) 숫자 0~9까지 10개와 알파벳 26개를 더해 총 36진수까지 표현할 수 있습니다.

import string NOTATION = string.digits + string.ascii_uppercase def numeral_system(number, base): q, r = divmod(number, base) n = NOTATION[r] return numeral_system(q, base) + n if q else n result = numeral_system(18, 16) result1 = numeral_system(17, 18) result2 = numeral_system(36, 36) print(result) print(result1) print(result2) # 12 # H # 10


먼저 파이썬은 기본 10진수이기 때문에 다른 진수는 아래와 같이 접두어가 붙습니다.

  • 2진수: 0b
  • 8진수: 0o
  • 16진수: 0x

10진수에서 2진수, 8진수, 16진수 변환

bin(), oct(), hex() 내장함수 사용

파이썬에서 제공하는 내장함수를 사용하면 쉽게 변환 할 수 있습니다.

value = 60

b = bin(value)
o = oct(value)
h = hex(value)

print(b)
print(o)
print(h)


# 0b111100
# 0o74
# 0x3c


결과는 전부 문자열 타입입니다.

format() 내장함수 사용

forrmat() 내장함수를 사용하여 위 결과와 마찬가지로 변환할 수 있습니다.

value = 60


b = format(value, '#b')
o = format(value, '#o')
h = format(value, '#x')

print(b)
print(o)
print(h)


# 0b111100
# 0o74
# 0x3c


두 번째 인자에서 #을 제거하면 접두어가 빠진 결과로 나오게 됩니다. 변환된 값 그 자체만 필요하게 될 경우, 유용하게 사용할 수 있습니다.

value = 60


b = format(value, 'b')
o = format(value, 'o')
h = format(value, 'x')

print(b)
print(o)
print(h)


# 111100
# 74
# 3c

다른 진수 형태에서 10진수로 변환

2진수, 8진수, 16진수에서 10진수로 변환하는 방법입니다. 여기서 변환하고자 하는 진수의 타입은 문자열이며 반환되는 10진수 결과는 정수 타입입니다.

b = int('0b111100', 2)
o = int('0o74', 8)
h = int('0x3c', 16)

print(b)
print(o)
print(h)


# 60
# 60
# 60


여기서 int 함수의 첫 번째 인자는 변환하고자 하는 진수이고 두 번째 인자는 첫 번째 인자의 진수 형태입니다. 만약 진수 형태를 잘못 입력하면 에러가 발생합니다.

다른 진수 형태에서 다른 진수로 변환

2진수에서 8진수로 변환하는 것과 같이 변환하는 방법입니다. 여기서 변환하고자 하는 진수의 타입은 정수이며 반환되는 타입은 문자열입니다.

o=oct(0b111100)
h=hex(0b111100)
s=str(0b111100)

print(o)
print(h)
print(s)

# 0o74
# 0x3c
# 60

문자열.format() 를 사용한 진수 변환

문자열 타입에서 제공하는 format() 메소드를 사용하여 변환하는 방법입니다.

s = "2진수: {0:#b}, 8진수: {0:#o}, 10진수: {0:#d}, 16진수: {0:#x}".format(60)

print(s)

# 2진수: 0b111100, 8진수: 0o74, 10진수: 60, 16진수: 0x3c


위에서 설명한 내장함수 format()과 마찬가지로 #을 제거하면 접두어가 빠진 형태로 반환됩니다.

s = "2진수: {0:b}, 8진수: {0:o}, 10진수: {0:d}, 16진수: {0:x}".format(60)

print(s)

# 2진수: 111100, 8진수: 74, 10진수: 60, 16진수: 3c


장고의 DRF를 사용하여 viewset을 구현하면 제목과 같은 기능이 필요할 때가 있습니다. 기본 ORM을 사용한다면 쉽지만 유효성 검사를 직접 구현해줘야 합니다. DRF의 serializer를 사용하여 처리를 하는 방식을 아래에서 설명합니다.

bulk create

bulk create는 큰 구현이 필요하지 않습니다. viewset에서 def list() 함수를 상속 받은 다음, 요청 데이터 타입이 list일 때만 serializer의 파라미터에 {'many': True}를 주면 끝납니다. 

class AdminViewSet(viewsets.ModelViewSet):
	def create(self, request, *args, **kwargs):
    	kwargs["many"] = isinstance(request.data, list)
	    serializer = self.get_serializer(data=request.data, **kwargs)
	    serializer.is_valid(raise_exception=True)
	    serializer.save()
	    return Response(serializer.data, status=status.HTTP_201_CREATED)


이후 해당 serializer 에서 특별한 작업없이 동작합니다.

bulk update

bulk update의 경우는 해당 serializer에 작업이 필요합니다. 아래는 create 부분과 같이 viewset의 def partial_update()를 수정합니다. patch 메소드를 사용하므로 {'partial': True} 이 들어가 있지만 put 메소드를 사용하여 def update()를 구현할 경우에는 필요없는 파라미터입니다. 

class AdminViewSet(viewsets.ModelViewSet):
	def partial_update(self, request, *args, **kwargs):
    	kwargs["partial"] = True
	    if kwargs.pop("pk", None):
    	    serializer = self.get_serializer(
        	    instance=self.get_object(), data=request.data, **kwargs
	        )
    	else:
        	kwargs["many"] = isinstance(request.data, list)
	        serializer = self.get_serializer(
    	        self.get_queryset(), data=request.data, **kwargs
	        )
    	serializer.is_valid(raise_exception=True)


/admin/ 과 같은 API가 있다고 가정할 때 위 코드는 /admin/4/ 와 같이 단일 수정 건이나 /admin/ 경로의 body에 다중 업데이트 건이 들어올 경우 수용할 수 있도록 처리한 코드입니다. 

예를 들어 아래와 같은 형식입니다.

단일 수정

PATCH: /admin/4/

{
"admin_name": "lee"
}


복수개 수정

PATCH: /admin/

[{
"admin_pk": "4",
"admin_name": "lee"
},
{
"admin_pk": "5",
"admin_name": "park"
},
{
"admin_pk": "7",
"admin_name": "kim"
}]


다음은 해당 serializer파일에 아래와 같은 코드를 추가합니다.

import inspect
from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import NotFound


class BulkListSerializer(serializers.ListSerializer):
    def update(self, queryset, validated_data):
        id_attr = getattr(self.child.Meta, 'update_lookup_field')
        update_data = {i.get(id_attr): i for i in validated_data}

        if not all((bool(i) and not inspect.isclass(i) for i in update_data.keys())):
            raise NotFound(_('Could not find all objects to update.'))

        objects = queryset.filter(**{'{}__in'.format(id_attr): update_data.keys()})

        if len(update_data) != objects.count():
            raise NotFound(_('Could not find all objects to update.'))

        ret = []
        for id, data in update_data.items():
            for obj in objects:
                if str(getattr(obj, id_attr)) == str(id):
                    ret.append(self.child.update(obj, data))

        return ret


다음 다중 업데이트를 처리할 serializer의 class Meta: 부분에 아래를 추가합니다.

class AdminSerializer(serializers.ModelSerializer):
    admin_pk = serializers.IntegerField()
    
    class Meta:
        model = Admin
        fields = '__all__'
        update_lookup_field = 'admin_pk'
        list_serializer_class = BulkListSerializer


class Meta: 하위의 update_lookup_field는 pk 변수명을 작성합니다. 만약 API에서 pk가 아닌 다른 유니크한 키를 기준으로 업데이트를 하려고 하면 해당되는 변수명을 작성하면 됩니다.

그 아래의 list_serializer_class에는 위에서 정의한 클래스 명을 작성합니다. 


bulk update는 위와 같은 순서만 따라하면 쉽게 구현할 수 있습니다. bulk update의 동작은 리스트 내의 데이터들을 한 번에 전부 처리하는 것이 아닌 반복문을 돌면서 단 건으로 처리합니다. 

$ python3 manage.py --help

를 입력하면 django에서 제공하는 command를 확인할 수 있습니다. 이 때, 사용자가 임의로 어떤 명령어를 만들고 해당 명령어를 통해 일련의 작업을 수행하도록 하기 위해선 커스텀 커맨드를 만들어야 합니다.

개요

먼저 프로젝트의 구성이 아래와 같다고 가정합니다.

polls/
    __init__.py
    models.py
    management/
        commands/
            _private.py
            closepoll.py
    tests.py
    views.py


커스텀 커맨드를 만들기 위해선 polls와 같이 app이 지정되어야 하고 해당 app 하위에 management/commands/ 폴더가 생성되어야 합니다. 해당 폴더의 이름은 고정이며 다른 이름으로 변경할 시, 커스텀 커맨드가 작동하지 않습니다. commands 폴더 아래에 _private.py는 커스텀 커맨드로 만들 수 없으며 closepoll.py만 만들 수 있는 상태입니다. 


위 폴더 및 파일 구조가 되었다면 closepoll.py의 내부를 아래와 같이 선언합니다.

from polls.models import Question as Poll


class Command(BaseCommand):
    help = 'Closes the specified poll for voting'

    def add_arguments(self, parser):
        # Positional arguments
        parser.add_argument('poll_ids', type=int)

        # Named (optional) arguments
        parser.add_argument(
            '--delete',
            type=bool,
            help='Delete poll instead of closing it',
        )

    def handle(self, *args, **options):
        for poll_id in options['poll_ids']:
            try:
                poll = Poll.objects.get(pk=poll_id)
            except Poll.DoesNotExist:
                raise CommandError('Poll "%s" does not exist' % poll_id)
    
            poll.opened = False
            poll.save()
    
            self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))


먼저 클래스인 Command의 이름을 변경하면 실행이 되지 않습니다. 커스텀 커맨드를 실행하게 되면 Command 클래스의 handle() 함수가 자동으로 실행되어 해당 함수 하위에 작성된 코드를 실행 시킵니다.


만약 커스텀 커맨드에 인수값을 추가하고 싶은 경우, add_arguments 함수를 정의해주면 됩니다. 추가할 때, 인수의 이름 앞에 --가 없으면 필수값이고 위치에 기반한 파라미터가 됩니다. --가 붙으면 이름에 기반한 optional 인수가 됩니다. ( add_argument() 에 대한 설명은 https://brownbears.tistory.com/413에서 확인할 수 있습니다.)


즉 위와 같이 작성할 경우 실행 방법은 아래와 같습니다.

$ python3 manage.py closepoll 10 --delete=false 
// 성공


$ python3 manage.py closepoll --delete=false 10
// 성공


$ python3 manage.py closepoll --delete=false
// 필수값인 poll_ids가 없으므로 오류


$ python3 manage.py closepoll poll_ids=10 --delete=false
// 이름 기반의 인수가 아니므로 오류


만약 콘솔 아웃풋을 찍어서 확인을 하고 싶다면 self.stdout.write()를 사용하여 확인할 수 있습니다.

from polls.models import Question as Poll


class Command(BaseCommand):
    def handle(self, *args, **options):
            # 터미널에서 실행 시, 색깔있는 글씨로 표현
            self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))
            # 줄바꿈이 없으므로 다음 출력이 붙어서 출력됨
            self.stdout.write("Unterminated line", ending='')


크루스칼 알고리즘의 이론은 https://brownbears.tistory.com/396에 설명이 되어 있습니다. 아래는 파이썬으로 기본적인 크루스칼 알고리즘을 어떻게 구현했는지에 대한 내용입니다.


코드에 앞서 크루스칼 알고리즘의 중요한 점은 아래와 같습니다.

  1. edge가 가장 작은 순으로 선택해 나감
  2. edge 연결 후, cycle이 생기면 안됨


즉, 가장 작은 edge를 선택할 때마다, 그동안 연결한 vertices끼리 cycle이 생기는지 확인을 해야 합니다. 이러한 cycle이 생겼는지 확인하는 것은 union-find (disjoint-set) 알고리즘을 사용합니다. union-find (disjoint-set) 알고리즘의 설명은 https://brownbears.tistory.com/460에 정리되어 있습니다.

아래는 kruskal 알고리즘 코드의 예시입니다.

class DisjointSet:
    def __init__(self, n):
        self.parent = {}
        self.rank = {}
        for i in range(n):
            self.parent[i] = i
            self.rank[i] = 0

    def find(self, v):
        if self.parent[v] != v:
            self.parent[v] = self.find(self.parent[v])
        return self.parent[v]

    def union(self, root1, root2):
        # 연결 정보를 갱신할 때는 작은 값을 기준으로 갱신
        if self.rank[root1] > self.rank[root2]:
            self.parent[root2] = root1
        else:
            self.parent[root1] = root2
            # self.rank[root2]가 self.rank[root1]보다 크다고 봤으므로 두 값이 같은 경우, self.rank[root2]의 값을 1 올려준다.
            if self.rank[root1] == self.rank[root2]:
                self.rank[root2] += 1


def kruskal(n, info):
    minimum_weight = 0
    disjointset = DisjointSet(n)
    result = []
    for data in sorted(info, key=lambda cost: cost[2]):
        v, u, weight = data
        root1 = disjointset.find(v)
        root2 = disjointset.find(u)
        # root1과 root2가 같은데 연결을 하면 사이클이 생김
        if root1 != root2:
            disjointset.union(root1, root2)
            result.append(data)
            minimum_weight += data[2]

    return result, minimum_weight




kruskal(6, [[0, 1, 5], [0, 3, 2], [0, 4, 3], [1, 4, 1], [3, 4, 10], [1, 2, 2], [2, 5, 3], [4, 5, 4]])


# 결과
[[1, 4, 1], [0, 3, 2], [1, 2, 2], [0, 4, 3], [2, 5, 3]], 11 


위 코드 kruskal() 함수에서 첫 번째 인자은 n은 vertices의 총 개수이고 info는 각 vertex 간 연결 정보와 link의 weight를 나타냅니다. 즉 예시에서 6은 vertex가 6개 있다는 의미이며 두 번째 인자에서 [0, 1, 5] 은 vertex 0과 1이 연결되어 있고 이 link의 weight은 5라는 의미입니다.

이러한 예시를 진행하면 최소 weight는 11이 나오게 됩니다.


union find (disjoint-set) 이란?

서로 중복되지 않는 부분 집합들로 나눠진 원소들에 대한 정보를 저장하고 조작하는 자료 구조입니다. 간단하게 다수의 노드들 중에 연결된 노드를 찾거나 노드들을 합칠때 사용하는 알고리즘입니다. 아래 예시 그림을 보면 어떤 개념인지 바로 이해가 됩니다.

원소(노드)가 1~10까지 있다고 할 때, 해당 원소들은 아래와 같은 리스트의 구조를 갖습니다. 첫 번째 행은 원소의 번호고 두 번째 행은 원소의 관계로 보면 됩니다. 

12345678910
12345678910


이러한 원소들은 아래와 같은 연결 구조를 갖고 있다고 가정합니다.



위 그림은 원소 1,2,3,4 / 5,6,7,8 / 9,10 과 같이 3개의 그룹으로 묶여 있습니다. 

이를 리스트로 표현하면 아래와 같습니다.

12345678910
1123556799


위와 같이 두 번째 행인 연결정보를 갱신할 때는 작은 값을 기준으로 갱신합니다. 예를 들어, 원소 1과 2가 연결되었다고 할 때는 원소 2의 정보를 1로 바꿉니다. 1,2,3,4 모두 연결되어 있어 같은 집단에 속하지만 위의 테이블에서는 원소 3과 4의 연결 정보는 1이 아닙니다. 이를 처리하기 위해선 순차적으로 진행이 되어야 합니다.

  1. 원소 3과 2는 연결 되어 있음
  2. 원소 3의 연결 정보를 2로 변경
  3. 여기서 원소 2와 1은 연결되어 있음
  4. 삼단논법으로 인해 원소 3의 연결정보 2는 원소 1과 연결되어 있음을 알 수 있음
  5. 원소 3의 연결정보를 1로 변경


위와 같은 순서를 진행하면 결국 아래와 같은 테이블이 결과로 나오게 됩니다.

12345678910
1111555599


구현

union find(disjoint-set)의 핵심은 아래 3가지 입니다.

  • 초기화 : N 개의 원소가 각각의 집합에 포함되어 있도록 초기화

  • Union 연산 : 두 원소 a, b 가 주어질 때, 이들이 속한 두 집합을 하나로 합침

  • Find 연산 : 어떤 원소 a 가 주어질 때, 이 원소가 속한 집합을 반환


이 알고리즘을 구현하는 방식은 배열을 사용하는 것과 트리구조를 사용하는 방식 두 가지가 있습니다. 보통은 효율성 때문에 배열로 사용하지 않고 트리구조를 사용합니다.

1. 배열을 사용하는 방식

배열을 사용하여 disjoint-set을 구현하는 방법은 아래와 같습니다.

  1. 먼저 원소의 크기만큼 배열을 초기화(생성)해줍니다.
  2. 그 다음 두 원소를 합치기 위해 배열의 모든 원소를 순회하면서 하나의 번호를 나머지 하나로 교체합니다. (두 원소들을 합칠 때, 해당 원소의 연결정보를 찾는 연산은 한 번 만에 알 수 있습니다.)


위에서도 설명했듯이 합치는 연산(union)에서 배열의 모든 원소를 순회하게 되기 때문에 시간복잡도가 O(N)입니다. 이를 해결하기 위해 보통 트리구조를 많이 사용합니다.

예제

disjoint-set을 검색하면 보통 재귀함수를 사용한 예제코드를 많이 볼 수 있는데, 해당 코드의 문제점은 연결 정보 순서가 바뀌면 동작에 버그가 있다는 점입니다.

  • disjoint(1,2), disjoiint(2,3)은 정상으로 동작하지만 이 순서를 바꾸면 오류가 발생

따라서 아래 예제는 재귀 함수를 지양하고 반복문을 사용한 코드입니다.

class DisjointSet:
    def __init__(self, n):
        self.data = list(range(n))
        self.size = n

    def find(self, index):
        return self.data[index]


	def union(self, x, y):
    	x, y = self.find(x), self.find(y)

	    if x == y:
    	    return

	    for i in range(self.size):
    	    if self.find(i) == y:
        	    self.data[i] = x


    @property
    def length(self):
        return len(set(self.data))




disjoint = DisjointSet(10)

disjoint.union(0, 1)
disjoint.union(1, 2)
disjoint.union(2, 3)
disjoint.union(4, 5)
disjoint.union(5, 6)
disjoint.union(6, 7)
disjoint.union(8, 9)

print(disjoint.data)
print(disjoint.length)


# [0, 0, 0, 0, 4, 4, 4, 4, 8, 8]
# 3

2. 트리구조를 사용하는 방식

트리 구조에서도 union-by-size, union-by-height, path comprehension 과 같이 3가지 방식이 있습니다.

union-by-size, union-by-height

임의의 두 집합을 합칠 때는 원소의 수가 적은 집합을 원소가 많은 집합의 하위 트리로 합치는 것이 효율적입니다. 이러한 방식이 union-by-size입니다.  이와 마찬가지로 트리의 높이가 작은 집합을 높이가 더 큰 집합의 서브트리로 합치는 방식을 union-by-height라고 합니다.

union-by-size의 방법은 아래와 같습니다. (union-by-height는 3번을 제외하고 동일합니다.)

  1. 주어진 원소의 개수만큼 사용하지 않을 값 (예제에서는 -1)을 생성
  2. 루트노드의 인덱스를 찾음
  3. 루트노드의 인덱스가 다르다면 리스트의 값이 더 낮은(size가 더 큰) 것을 찾아서 큰 것에 더해줌
  4. 작은건 큰것의 인덱스로 바꿔준다.


트리의 경우, 높이는 logn이므로 시간복잡도는 O(logn)을 가집니다.

예제

union-by-size
class DisjointSet:
    def __init__(self, n):
        self.data = [-1 for _ in range(n)]
        self.size = n

    def find(self, index):
        value = self.data[index]
        if value < 0:
            return index

        return self.find(value)

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.data[x] < self.data[y]:
            self.data[x] += self.data[y]
            self.data[y] = x
        else:
            self.data[y] += self.data[x]
            self.data[x] = y

        self.size -= 1


disjoint = DisjointSet(10)

disjoint.union(0, 1)
disjoint.union(1, 2)
disjoint.union(2, 3)
disjoint.union(4, 5)
disjoint.union(5, 6)
disjoint.union(6, 7)
disjoint.union(8, 9)

print(disjoint.data)
print(disjoint.size)


# [1, -4, 1, 1, 5, -4, 5, 5, 9, -2]
# 3
union-by-height
class DisjointSet:
    def __init__(self, n):
        self.data = [-1] * n
        self.size = n

    def find(self, index):
        value = self.data[index]
        if value < 0:
            return index

        return self.find(value)

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.data[x] < self.data[y]:
            self.data[y] = x
        elif self.data[x] > self.data[y]:
            self.data[x] = y
        else:
            self.data[x] -= 1
            self.data[y] = x

        self.size -= 1


disjoint = DisjointSet(10)

disjoint.union(0, 1)
disjoint.union(1, 2)
disjoint.union(2, 3)
disjoint.union(4, 5)
disjoint.union(5, 6)
disjoint.union(6, 7)
disjoint.union(8, 9)

print(disjoint.data)
print(disjoint.size)



# -2의 경우 루트 인덱스이므로 세지 않음
# [-2, 0, 0, 0, -2, 4, 4, 4, -2, 8]
# 3

path comprehension

위에서 설명한 union-by-size나 union-by-height는 find() 연산을 수행할 때 트리의 높이만큼 올라가 루트를 찾을 수 있는데, 이러한 비효율성을 해결하고자 나온 개념입니다. path compression을 수행하면 루트를 찾는 find() 연산 비용을 낮출 수 있습니다. 이는 find()를 실행한 뒤에 다음 find()에서 효과적으로 찾을 수 있도록 트리를 재구성 하는 방식입니다.


방식은 아래와 같습니다.

  1. find(x)를 실행
  2. x가 루트가 아니라면 임시로 저장
  3. 루트노드를 찾을 때 까지 재귀함수 반복
  4. 루트노드를 찾으면 x를 루트노드의 자식으로 표시

예제

class DisjointSet:
    def __init__(self, n):
        self.data = [-1 for _ in range(n)]
        self.size = n

    def upward(self, change_list, index):
        value = self.data[index]
        if value < 0:
            return index

        change_list.append(index)
        return self.upward(change_list, value)

    def find(self, index):
        change_list = []
        result = self.upward(change_list, index)

        for i in change_list:
            self.data[i] = result

        return result

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.data[x] < self.data[y]:
            self.data[y] = x
        elif self.data[x] > self.data[y]:
            self.data[x] = y
        else:
            self.data[x] -= 1
            self.data[y] = x

        self.size -= 1


disjoint = DisjointSet(10)

disjoint.union(0, 1)
disjoint.union(1, 2)
disjoint.union(2, 3)
disjoint.union(4, 5)
disjoint.union(5, 6)
disjoint.union(6, 7)
disjoint.union(8, 9)

print(disjoint.data)
print(disjoint.size)




# [-2, 0, 0, 0, -2, 4, 4, 4, -2, 8]
# 3



만약 직접 구현을 할 필요가 없고 (실무에서 사용하기 위해) 기능 자체만 사용하고 싶으면 PyPi에서 모듈을 직접 받아서 사용할 수 있습니다. (https://github.com/mrapacz/disjoint-set)

+ Random Posts