Backends PHP

PHP에서 배열이 색인 배열인지 연관 배열인지 구별하기

흔히 배열은 색인 배열(Indexed Array)과 연관 배열(Associative Array)로 구분됩니다. 색인 배열은 키(key)가 0부터 시작하여 순차적으로 증가하는 정수인 배열을 의미합니다. 이 때문에 색인 배열은 순차 배열(Sequential Array)이라고 부르기도 합니다. 그 외의 모든 배열은 연관 배열로 분류할 수 있습니다.

사실 PHP는 내부적으로 색인 배열과 연관 배열을 구분하지 않습니다. 이 점은 PHP Manual의 Arrays 항목에서도 언급하고 있는 내용입니다.

PHP arrays can contain integer and string keys at the same time as PHP does not distinguish between indexed and associative arrays.

그럼에도 종종 어떤 배열이 색인 배열과 같은 형태인지 연관 배열인지를 알아내야 하는 경우가 있습니다. 웹 상에서 코드서칭을 해보면 무척 다양한 해법들을 찾아볼 수 있는데요. 다소 복잡한 방법이 있는가하면, 무릎을 탁치게 하는 방법들도 존재합니다. 금번 포스팅에서는 그 중에 몇 가지를 간추려 소개하고자 합니다.

 

Brute Force

function isAssoc($arr)
{
	$i = 0;
	foreach($arr as $k => $v)
	{
		if($k != $i++)
		{
			return true;
		}
	}
	return false;
}

우리가 생각할 수 있는 가장 간단한 방법은, 반복문으로 하나씩 키를 비교해가는 방법일 것입니다. 물론 이 방법을 소개하려고 이 글을 쓴 것은 아니겠죠!

 

range() 함수를 이용하는 방법

function isAssoc($arr)
{
	return array_keys($arr) !== range(0, sizeof($arr) - 1);
}

range() 함수를 이용하면 0부터 순차적으로 증가하는 정수가 담긴 배열을 만들 수 있습니다. array_keys() 함수로 키로만 구성된 배열을 추출한 후, range()로 생성한 배열과 같은지 확인하는 것이 이 방법의 접근법입니다.

 

array_merge() 함수를 이용하는 방법

function isAssoc($arr)
{
	$b1 = preg_match('/^[0-9]*$/', implode('', array_keys($arr)));
	$b2 = array_merge($arr) === $arr;

	return !($b1 && $b2);
}

$b1에서는 먼저 모든 키가 숫자로만 이루어졌는지를 확인합니다. (출처에 수록된 원래 코드에는 is_numeric() 함수로 이것을 체크했는데, 소수점이 있는 경우에는 잘못 작동할 수도 있기 때문에 이 포스팅에서는 정규표현식으로 수정했습니다.) 모든 키가 숫자로만 이루어진 경우, $b1은 true가 됩니다. 여기서 키에 숫자 외의 문자가 들어간 연관 배열은 걸러지게 됩니다.

그러나 모든 키가 숫자라 하더라도, 숫자가 순차적으로 증가하지 않는 경우에는 역시 색인 배열이 아니겠죠. array_merge() 함수에는 이런 배열들의 키가 색인 배열처럼 순차적으로 증가하도록 다시 부여하는 특성이 있습니다. 따라서 $b2는 색인 배열인 경우와 모든 키가 아닌 문자를 포함하고 있는 연관 배열인 경우에 true가 될 것입니다.

이 2개의 값을 AND 연산 하면, 순수하게 색인 배열일 경우에만 true가 될 것입니다.

 

array_keys() 함수를 2번 이용한 방법

function isAssoc($arr)
{
	$keys = array_keys($arr);
	return array_keys($keys) !== $keys;
}

array_keys() 함수는 인자로 주어진 함수의 키를 모아놓은 색인 배열을 추출합니다. 따라서 array_keys()로 생성된 키를 모아놓은 배열을 한 번 더 array_keys() 함수에 인자로 넣으면, 0부터 순차적으로 증가하는 배열을 얻을 수 있습니다. 이 2개의 배열을 비교했을 때, 같으면 색인 배열이고 다르면 연관 배열이 될 것입니다.

앞서의 방법들에 비하면, 비교적 널리 쓰이는 방법입니다.

 

array_values() 함수를 이용한 방법

function isAssoc($arr)
{
	return ($arr !== array_values($arr));
}

array_values() 함수는 인자로 주어진 함수의 값만 모아놓은 색인 배열을 만들어냅니다. 그런데 이렇게 만들어진 색인 배열을 원래 배열과 비교했을 때 같다면…? 당연히 처음부터 그 배열은 색인 배열이었을 것입니다. 벤치마킹 결과에 따르면, 실행속도 면에서는 대단히 빠른 코드라고 하는군요.

 

사용례: 다시 작성하는 json_encode() 함수

제가 오늘 소개한 문제를 고민하게 된 것은, json_encode() 함수를 직접 작성하면서였습니다. 작업 중이던 서버의 PHP의 버전이 낮아 내장된 json_encode() 함수를 사용할 수 없었던 것이죠. JSON 포맷은 색인 배열일 때는 값을 [ ]로 묶어야 하지만, 연관 배열일 때는 { }로 묶어야만 합니다. 따라서 색인 배열인지 연관 배열인지를 확인하는 부분이 필요합니다.

function json_encode($arr)
{
	// Key
	if($key)
	{
		$key = '"' . str_replace('"', '\"', $key) . '":';
	}

	// Value
	if(is_array($value))
	{
		$res	= array();

		if($value === array_values($value))
		{
			// Indexed Array
			foreach($value as $k => $v)
			{
				$res[] = array2json($v);
			}

			$res = $key . '[' . implode(',', $res) . ']';
		}
		else
		{
			// Associative Array
			foreach($value as $k => $v)
			{
				$res[] = array2json($v, $k);
			}

			$res = $key . '{' . implode(',', $res) . '}';
		}			
	}
	elseif(is_bool($value))
	{
		$res = $key . ($value? 'true': 'false');
	}
	elseif(is_numeric($value))
	{
		$res = $key . $value;
	}
	elseif(is_string($value))
	{	
		$value = strtr($value, array(
			'"'		=> '\"',
			"\\"	=> "\\\\",
			"\/"	=> '\/',
			"\b"	=> '\b',
			"\f"	=> '\f',
			"\n"	=> '\n',
			"\r"	=> '\r',
			"\t"	=> '\t'
		));

		$res = $key . '"' . $value . '"';
	}
	elseif(is_null($value))
	{
		$res = $key . 'null';
	}

	return $res;
}

코드에서 14번째 줄을 보시면, 색인 배열인지 여부에 따라 분기하는 조건문이 있음을 확인할 수 있습니다. 이처럼 색인 배열과 연관 배열을 구분하는 문제는, 주로 PHP 배열로 저장된 데이터를 다른 포맷으로 변환하거나 인코딩해야 할 때에 접하게 됩니다. 알고 계시다면 유용하게 사용하실 수 있을 것입니다.

Leave a Reply

Your email address will not be published. Required fields are marked *