React Native CLI로 시작하기

해당 글은 Mac을 기준으로 작성했습니다.

공식 문서 상에서 iOS를 개발하기 위해서는 Expo Go로 생성을 하거나 macOS가 필요하다고 작성되어 있습니다.

Mac OS로 Android 개발 시작하기

  1. 개발 툴 설치 (IDE)
    • InteliJ, Visual Studio Code 등과 같이 Code를 작성할 수 있는 IDE를 설치합니다.
    • 저는 VSCode를 기반으로 작성하겠습니다.
    • https://code.visualstudio.com/
  2. Hombrew 설치
    • Node, Watchman, JDK 등 패키지 설치를 하기위해 Homebrew를 설치합니다.
    • https://brew.sh/ko/
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
  3. Hombrew를 사용하여 Node, Watchman 설치
    • Node 18이상 설치.
    • Watchman
      • 파일 시스템의 변경 사항을 감시하기 위한 Facebook의 도구입니다. 파일이나 디렉토리의 변경을 감지하고, 변경 사항에 따라 사용자가 지정한 행동을 수행하는 데 사용합니다.
      • 파일의 변경을 감지하면, 변경된 파일만을 대상으로 변경사항이 즉시 반영됩니다.
brew install node brew install watchman
  1. JDK 설치
brew tap homebrew/cask-versions
brew install --cask zulu17

# Get path to where cask was installed to double-click installer
brew info --cask zulu17
  1. Android Studio 설치
  2. Android Studio SDK 설치
    • 위에서 Android SDK를 선택하지 않으셨다면 Android Studio SDK Manager -> SDK Tools 탭에서 설치하실 수 있습니다.

  1. Android_HOME 환경 변수 구성
    • 애뮬레이터를 실행할 수 있도록 아래의 환경변수를 구성합니다.
    • 이때, $HOME은 사용자의 컴퓨터 환경에 맞게 변경해줍니다.
      • SDK Manager의 Android SDK Location에 나와있는 /Library이전의 경로로 변경하여 주시면됩니다.
    • nano ~/.zprofile 또는 ~/.zshrc 또는 ~/.bash_profile 또는 ~/.bashrc 파일 중 한개에서 설정을 변경합니다.
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/emulator
export PATH=$PATH:$ANDROID_HOME/platform-tools
  1. npx로 새 애플리케이션 생성
    • 이전에 react-native-cli를 설치한 적이 있으시다면 예상치 못한 에러를 발생할 수 있기때문에 제거합니다.
npm uninstall -g react-native-cli @react-native-community/cli
  • react-native-cli를 설치한 적이 없으시다면 바로 아래 명령어를 통해 생성해주시면 됩니다.
# 최신버전
npx react-native@latest init AwesomeProject

# 특정버전
npx react-native@X.XX.X init AwesomeProject --version X.XX.X
  1. React Native 애플리케이션 실행
    1. 실행하게 되면 아래 사진과 같은 화면이 나오게 되며 첫 애플리케이션이 실행되게 됩니다.
npm start

# 해당 명령어를 실행하다보면 아래와 같은 선택지중 한가지를 선택하시면 됩니다. 
i - run on iOS 
a - run on Android 
d - open Dev Menu 
r - reload app

RN android studio 애물레이터 실행

https://www.acmicpc.net/problem/2331

 

2331번: 반복수열

첫째 줄에 반복되는 부분을 제외했을 때, 수열에 남게 되는 수들의 개수를 출력한다.

www.acmicpc.net

문제

다음과 같이 정의된 수열이 있다.

  • D[1] = A
  • D[n] = D[n-1]의 각 자리의 숫자를 P번 곱한 수들의 합

예를 들어 A=57, P=2일 때, 수열 D는 [57, 74(=52+72=25+49), 65, 61, 37, 58, 89, 145, 42, 20, 4, 16, 37, …]이 된다. 그 뒤에는 앞서 나온 수들(57부터가 아니라 58부터)이 반복된다.

이와 같은 수열을 계속 구하다 보면 언젠가 이와 같은 반복수열이 된다. 이때, 반복되는 부분을 제외했을 때, 수열에 남게 되는 수들의 개수를 구하는 프로그램을 작성하시오. 위의 예에서는 [57, 74, 65, 61]의 네 개의 수가 남게 된다.

입력

첫째 줄에 A(1 ≤ A ≤ 9999), P(1 ≤ P ≤ 5)가 주어진다.

출력

첫째 줄에 반복되는 부분을 제외했을 때, 수열에 남게 되는 수들의 개수를 출력한다.

예제 입력 1 복사

57 2

예제 출력 1 복사

4

 

문제풀이(1)

주어진 수 A부터 한자리수씩 A^P로 계산하여 더한 값을 배열에 추가한 뒤 배열에 한번이라도 나왔던 수라면 반복을 정지한다.
[57, 74(=5^2+7^2=25+49), 65(=7^2+4^2=49+16), 61, 37, 58, 89, 145, 42, 20, 4, 16, 37]

그 후 한번이라도 나왔던 수가 몇번째 인덱스에 추가되었는지 확인하면 반복수열을 제거했을 때 남는 수열의 개수를 구할 수 있다.

57, 74, 65, 61, 37, 58, 89, 145, 42, 20, 4, 16, 37 일 경우 37이 두번등장 하기 때문에 그 사이는 반복수열이다.
반복수열을 제거하면 57, 74, 65, 61 이므로 4개가 남는데 이때, 37이 처음으로 나온 index값은 4이므로 4를 출력하면 반복수열을 제외한 나머지 수열의 개수를 알 수 있다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const [A, P] = require("fs").readFileSync(filePath).toString().trim().split(" ").map(Number);

const arr = [A];

const answer = [];

while (true) {
  const prevNum = String(arr.at(-1));
  const nextNum = prevNum.split("").reduce((acc, cur) => acc + Number(cur) ** P, 0);

  if (arr.includes(nextNum)) {
    console.log(arr.indexOf(nextNum));
    break;
  }

  arr.push(nextNum);
}

https://www.acmicpc.net/problem/2776

 

2776번: 암기왕

연종이는 엄청난 기억력을 가지고 있다. 그래서 하루 동안 본 정수들을 모두 기억 할 수 있다. 하지만 이를 믿을 수 없는 동규는 그의 기억력을 시험해 보기로 한다. 동규는 연종을 따라 다니며,

www.acmicpc.net

문제

연종이는 엄청난 기억력을 가지고 있다. 그래서 하루 동안 본 정수들을 모두 기억 할 수 있다. 하지만 이를 믿을 수 없는 동규는 그의 기억력을 시험해 보기로 한다. 동규는 연종을 따라 다니며, 연종이 하루 동안 본 정수들을 모두 ‘수첩1’에 적어 놓았다. 그것을 바탕으로 그가 진짜 암기왕인지 알아보기 위해, 동규는 연종에게 M개의 질문을 던졌다. 질문의 내용은 “X라는 정수를 오늘 본 적이 있는가?” 이다. 연종은 막힘없이 모두 대답을 했고, 동규는 연종이 봤다고 주장하는 수 들을 ‘수첩2’에 적어 두었다. 집에 돌아온 동규는 답이 맞는지 확인하려 하지만, 연종을 따라다니느라 너무 힘들어서 여러분에게 도움을 요청했다. 동규를 도와주기 위해 ‘수첩2’에 적혀있는 순서대로, 각각의 수에 대하여, ‘수첩1’에 있으면 1을, 없으면 0을 출력하는 프로그램을 작성해보자.

입력

첫째 줄에 테스트케이스의 개수 T가 들어온다. 다음 줄에는 ‘수첩 1’에 적어 놓은 정수의 개수 N(1 ≤ N ≤ 1,000,000)이 입력으로 들어온다. 그 다음 줄에  ‘수첩 1’에 적혀 있는 정수들이 N개 들어온다. 그 다음 줄에는 ‘수첩 2’에 적어 놓은 정수의 개수 M(1 ≤ M ≤ 1,000,000) 이 주어지고, 다음 줄에 ‘수첩 2’에 적어 놓은 정수들이 입력으로 M개 들어온다. 모든 정수들의 범위는 int 로 한다.

출력

‘수첩2’에 적혀있는 M개의 숫자 순서대로, ‘수첩1’에 있으면 1을, 없으면 0을 출력한다.

예제 입력 1 복사

1
5
4 1 5 2 3
5
1 3 7 9 5

예제 출력 1 복사

1
1
0
0
1

 

문제풀이(1)

기억하고 있는 수첩2에 있는 수들이 주어진 수첩2에 있는 수들중에 있다면 1, 없다면 0을 출력하는 문제이다.
수첩 1의 수들을 오름차순으로 정렬한다.
2진탐색으로 수첩2에 있는 수들을 하나씩 탐색하여 있다면 1, 없다면 0을 출력한다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const input = require("fs").readFileSync(filePath).toString().trim().split("\n");

const T = Number(input.shift());

const answer = [];

const binarySearch = (arr, targetNum) => {
  let low = 0;
  let high = arr.length - 1;
  let result = 0;

  while (low <= high) {
    let mid = Math.floor((low + high) / 2);

    if (arr[mid] < targetNum) {
      low = mid + 1;
    } else if (arr[mid] > targetNum) {
      high = mid - 1;
    } else {
      result = 1;
      break;
    }
  }

  return result;
};

for (let i = 0; i < T; i++) {
  input.shift();
  const note1 = input
    .shift()
    .split(" ")
    .map(Number)
    .sort((a, b) => a - b);
  input.shift();
  const note2 = input.shift().split(" ").map(Number);

  note2.forEach((num) => {
    answer.push(binarySearch(note1, num));
  });
}

console.log(answer.join("\n"));

 

문제풀이(2)

Set을 사용한 풀이

이진 탐색을 사용했을 때보다 코드의 가독성은 올라갔지만 실행시간, 메모리 측면에서 좋지 않은 성적을 보여줬다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const input = require("fs").readFileSync(filePath).toString().trim().split("\n");

const T = Number(input.shift());

const answer = [];

for (let i = 0; i < T; i++) {
  input.shift();
  const note1 = input
    .shift()
    .split(" ")
    .map(Number)
    .sort((a, b) => a - b);
  const note1Set = new Set(note1);

  input.shift();
  const note2 = input.shift().split(" ").map(Number);

  note2.forEach((num) => {
    answer.push(note1Set.has(num) ? 1 : 0);
  });
}

console.log(answer.join("\n"));

https://www.acmicpc.net/problem/2485

 

2485번: 가로수

첫째 줄에는 이미 심어져 있는 가로수의 수를 나타내는 하나의 정수 N이 주어진다(3 ≤ N ≤ 100,000). 둘째 줄부터 N개의 줄에는 각 줄마다 심어져 있는 가로수의 위치가 양의 정수로 주어지며, 가

www.acmicpc.net

문제

직선으로 되어있는 도로의 한 편에 가로수가 임의의 간격으로 심어져있다. KOI 시에서는 가로수들이 모두 같은 간격이 되도록 가로수를 추가로 심는 사업을 추진하고 있다. KOI 시에서는 예산문제로 가능한 한 가장 적은 수의 나무를 심고 싶다.

편의상 가로수의 위치는 기준점으로 부터 떨어져 있는 거리로 표현되며, 가로수의 위치는 모두 양의 정수이다.

예를 들어, 가로수가 (1, 3, 7, 13)의 위치에 있다면 (5, 9, 11)의 위치에 가로수를 더 심으면 모든 가로수들의 간격이 같게 된다. 또한, 가로수가 (2, 6, 12, 18)에 있다면 (4, 8, 10, 14, 16)에 가로수를 더 심어야 한다.

심어져 있는 가로수의 위치가 주어질 때, 모든 가로수가 같은 간격이 되도록 새로 심어야 하는 가로수의 최소수를 구하는 프로그램을 작성하라. 단, 추가되는 나무는 기존의 나무들 사이에만 심을 수 있다.

입력

첫째 줄에는 이미 심어져 있는 가로수의 수를 나타내는 하나의 정수 N이 주어진다(3 ≤ N ≤ 100,000). 둘째 줄부터 N개의 줄에는 각 줄마다 심어져 있는 가로수의 위치가 양의 정수로 주어지며, 가로수의 위치를 나타내는 정수는 1,000,000,000 이하이다. 가로수의 위치를 나타내는 정수는 모두 다르고, N개의 가로수는 기준점으로부터 떨어진 거리가 가까운 순서대로 주어진다.

출력

모든 가로수가 같은 간격이 되도록 새로 심어야 하는 가로수의 최소수를 첫 번째 줄에 출력한다.

예제 입력 1 복사

4
1
3
7
13

예제 출력 1 복사

3

예제 입력 2 복사

4
2
6
12
18

예제 출력 2 복사

5

 

문제풀이(1) - 72%에서 실패

임의로 세우진 간격 사이에 최소한의 가로수를 일정한 간격으로 심었을 때 가로수의 개수를 출력하는 문제이다.
가로수 간의 간격을 구한다음에 간격들의 최대공약수를 구한뒤 일정한 간격으로 세워지지 않은 간격을 찾아서 최대공약수 마다 나무를 심어주면된다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const [N, ...arr] = require("fs").readFileSync(filePath).toString().trim().split("\n").map(Number);

const interval = [];

let answer = 0;

for (let i = 0; i < N - 1; i++) {
  interval[i] = Math.abs(arr[i] - arr[i + 1]);
}

// 유클리드 호제법 최대공약수 구하기
const getGcd = (a, b) => {
  if (a % b === 0) {
    return b;
  }
  return getGcd(b, a % b);
};

let gcd = interval[0];

for (let i = 0; i < N - 2; i++) {
  let temp = getGcd(interval[i + 1], interval[i]);
  if (temp < gcd) gcd = temp;
}

for (let i = 0; i < N - 1; i++) {
  const temp = interval[i];

  if (temp !== gcd) {
    answer += temp / gcd - 1;
  }
}

console.log(answer);

 

문제풀이(2) - 성공

문제풀이(1)에서 최대공약수를 잘 못 구하고 있었다. 해당 부분을 확인하여 수정하여 통과하였다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const [N, ...arr] = require("fs").readFileSync(filePath).toString().trim().split("\n").map(Number);

const interval = [];

let answer = 0;

for (let i = 1; i < N; i++) {
  interval[i - 1] = arr[i] - arr[i - 1];
}

// 유클리드 호제법 최대공약수 구하기
const getGcd = (a, b) => {
  if (b === 0) {
    return a;
  }
  return getGcd(b, a % b);
};

let gcd = interval[0];

for (let i = 1; i < N - 1; i++) {
  gcd = getGcd(gcd, interval[i]);
}

interval.forEach((value) => {
  answer += value / gcd - 1;
});

console.log(answer);

https://www.acmicpc.net/problem/2578

 

2578번: 빙고

첫째 줄부터 다섯째 줄까지 빙고판에 쓰여진 수가 가장 위 가로줄부터 차례대로 한 줄에 다섯 개씩 빈 칸을 사이에 두고 주어진다. 여섯째 줄부터 열째 줄까지 사회자가 부르는 수가 차례대로

www.acmicpc.net

문제

빙고 게임은 다음과 같은 방식으로 이루어진다.

먼저 아래와 같이 25개의 칸으로 이루어진 빙고판에 1부터 25까지 자연수를 한 칸에 하나씩 쓴다

 

다음은 사회자가 부르는 수를 차례로 지워나간다. 예를 들어 5, 10, 7이 불렸다면 이 세 수를 지운 뒤 빙고판의 모습은 다음과 같다.

 

차례로 수를 지워가다가 같은 가로줄, 세로줄 또는 대각선 위에 있는 5개의 모든 수가 지워지는 경우 그 줄에 선을 긋는다.

 

이러한 선이 세 개 이상 그어지는 순간 "빙고"라고 외치는데, 가장 먼저 외치는 사람이 게임의 승자가 된다.

 

철수는 친구들과 빙고 게임을 하고 있다. 철수가 빙고판에 쓴 수들과 사회자가 부르는 수의 순서가 주어질 때, 사회자가 몇 번째 수를 부른 후 철수가 "빙고"를 외치게 되는지를 출력하는 프로그램을 작성하시오.

입력

첫째 줄부터 다섯째 줄까지 빙고판에 쓰여진 수가 가장 위 가로줄부터 차례대로 한 줄에 다섯 개씩 빈 칸을 사이에 두고 주어진다. 여섯째 줄부터 열째 줄까지 사회자가 부르는 수가 차례대로 한 줄에 다섯 개씩 빈 칸을 사이에 두고 주어진다. 빙고판에 쓰여진 수와 사회자가 부르는 수는 각각 1부터 25까지의 수가 한 번씩 사용된다.

출력

첫째 줄에 사회자가 몇 번째 수를 부른 후 철수가 "빙고"를 외치게 되는지 출력한다.

예제 입력 1 복사

11 12 2 24 10
16 1 13 3 25
6 20 5 21 17
19 4 8 14 9
22 15 7 23 18
5 10 7 16 2
4 22 8 17 13
3 18 1 6 25
12 19 23 14 21
11 24 9 20 15

예제 출력 1 복사

15

 

문제풀이(1)

사회자가 부르는 수의 순서에 맞게 철수의 빙고판에 'O'로 변경한다.
빙고가 3개가 되는 시점이 사회자가 몇번 수를 불렀는지를 판단하는 로직을 구현한다.
사회자가 수를 불러서 철수의 빙고판에 칠하는 로직과 빙고 여부를 알 수 있는 로직을 구현한다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const input = require("fs").readFileSync(filePath).toString().trim().split("\n");

const chulsoo = input.slice(0, 5).map((numbers) => numbers.split(" ").map(Number));
const moderator = input.slice(5).map((numbers) => numbers.split(" ").map(Number));

const N = 5;

let answer = 0;

// 빙고판 숫자 색칠하기
const bingoPainting = (targetNum) => {
  for (let i = 0; i < N; i++) {
    for (let j = 0; j < N; j++) {
      if (chulsoo[i][j] === targetNum) {
        chulsoo[i][j] = "O";
      }
    }
  }
};

// 빙고 확인하기
const bingoCheck = () => {
  let totalBingoCount = 0;
  // 가로줄 빙고 확인
  for (let x = 0; x < N; x++) {
    if (chulsoo[x].filter((x) => x === "O").length === 5) {
      totalBingoCount++;
    }
  }

  // 세로줄 빙고 확인
  for (let x = 0; x < N; x++) {
    let yBingoCount = 0;
    for (let y = 0; y < N; y++) {
      if (chulsoo[y][x] === "O") {
        yBingoCount++;
      }
    }
    if (yBingoCount === 5) {
      totalBingoCount++;
    }
  }

  // 대각선 빙고 확인(대각선인 경우는 엑스자 일 경우 밖에 없기 때문에 2번만 확인하면 됨)
  // [[0,0], [1,1], [2,2], [3,3], [4,4]]
  // [[0,4], [1,3], [2,2], [3,1], [4,0]]
  let leftDiagonalBingoCount = 0;
  let rightDiagonalBingoCount = 0;
  for (let i = 0; i < N; i++) {
    if (chulsoo[i][i] === "O") {
      leftDiagonalBingoCount++;
    }

    if (chulsoo[i][N - 1 - i] === "O") {
      rightDiagonalBingoCount++;
    }
  }

  if (leftDiagonalBingoCount === N) {
    totalBingoCount++;
  }

  if (rightDiagonalBingoCount === N) {
    totalBingoCount++;
  }

  return totalBingoCount;
};

for (let i = 0; i < N; i++) {
  let bingoCount = 0;
  for (let j = 0; j < N; j++) {
    answer++;
    bingoPainting(moderator[i][j]);
    bingoCount = bingoCheck();
    if (bingoCount >= 3) {
      break;
    }
  }

  if (bingoCount >= 3) {
    break;
  }
}

console.log(answer);

 

문제풀이(2) - 리팩토링

1. 중복된 코드 최소화
2. 빙고확인할 때 every 메서드 사용
3. for문 중단문 2개를 process.exit(0);를 사용하여 종료

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const input = require("fs").readFileSync(filePath).toString().trim().split("\n");

const chulsoo = input.slice(0, 5).map((numbers) => numbers.split(" ").map(Number));
const moderator = input.slice(5).map((numbers) => numbers.split(" ").map(Number));

const N = 5;

let answer = 0;

// 빙고판 숫자 색칠하기
const bingoPainting = (targetNum) => {
  for (let i = 0; i < N; i++) {
    for (let j = 0; j < N; j++) {
      if (chulsoo[i][j] === targetNum) {
        chulsoo[i][j] = "O";
      }
    }
  }
};

// 빙고 확인하기
const bingoCheck = () => {
  let count = 0;

  // 가로, 세로, 대각선 빙고 확인
  for (let i = 0; i < N; i++) {
    if (chulsoo[i].every((val) => val === "O")) count++; // 가로 빙고 확인
    if (chulsoo.every((row) => row[i] === "O")) count++; // 세로 빙고 확인
  }

  // 대각선 빙고 확인
  if (chulsoo.every((row, idx) => row[idx] === "O")) count++;
  if (chulsoo.every((row, idx) => row[N - idx - 1] === "O")) count++;

  return count;
};

for (let i = 0; i < N; i++) {
  for (let j = 0; j < N; j++) {
    answer++;
    bingoPainting(moderator[i][j]);
    if (bingoCheck() >= 3) {
      console.log(answer);
      process.exit(0);
    }
  }
}

https://www.acmicpc.net/problem/2847

 

2847번: 게임을 만든 동준이

학교에서 그래픽스 수업을 들은 동준이는 수업시간에 들은 내용을 바탕으로 스마트폰 게임을 만들었다. 게임에는 총 N개의 레벨이 있고, 각 레벨을 클리어할 때 마다 점수가 주어진다. 플레이어

www.acmicpc.net

문제

학교에서 그래픽스 수업을 들은 동준이는 수업시간에 들은 내용을 바탕으로 스마트폰 게임을 만들었다. 게임에는 총 N개의 레벨이 있고, 각 레벨을 클리어할 때 마다 점수가 주어진다. 플레이어의 점수는 레벨을 클리어하면서 얻은 점수의 합으로, 이 점수를 바탕으로 온라인 순위를 매긴다. 동준이는 레벨을 난이도 순으로 배치했다. 하지만, 실수로 쉬운 레벨이 어려운 레벨보다 점수를 많이 받는 경우를 만들었다.

이 문제를 해결하기 위해 동준이는 특정 레벨의 점수를 감소시키려고 한다. 이렇게해서 각 레벨을 클리어할 때 주는 점수가 증가하게 만들려고 한다.

각 레벨을 클리어할 때 얻는 점수가 주어졌을 때, 몇 번 감소시키면 되는지 구하는 프로그램을 작성하시오. 점수는 항상 양수이어야 하고, 1만큼 감소시키는 것이 1번이다. 항상 답이 존재하는 경우만 주어진다. 정답이 여러 가지인 경우에는 점수를 내리는 것을 최소한으로 하는 방법을 찾아야 한다.

입력

첫째 줄에 레벨의 수 N이 주어진다. (1 ≤ N ≤ 100) 다음 N개 줄에는 각 레벨을 클리어하면 얻는 점수가 첫 번째 레벨부터 마지막 레벨까지 순서대로 주어진다. 점수는 20,000보다 작은 양의 정수이다.

출력

첫째 줄에 점수를 몇 번 감소시키면 되는지 출력한다.

예제 입력 1 복사

3
5
5
5

예제 출력 1 복사

3

 

문제풀이(1)

레벨을 클리어 할 떄마다 주어지는 점수를 배열에 넣고, 뒤에서 부터 순회하면서 arr[i-1]이 arr[i] 값보다 크거나 같다면 arr[i-1]을 arr[i]보다 1작게 한다.

const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
const [N, ...arr] = require("fs").readFileSync(filePath).toString().trim().split("\n").map(Number);

let answer = 0;

for (let i = N - 1; i > 0; i--) {
  if (arr[i - 1] >= arr[i]) {
    answer += arr[i - 1] - arr[i] + 1;
    arr[i - 1] = arr[i] - 1;
  }
}

console.log(answer);

+ Recent posts