ShortURL на свой сайт или как сократить ссылки

Наверняка каждый из нас хоть раз задумывался над идеей сделать подобный сервис для собственного сайта, но не многие понимали как именно это сделать. Меня тоже давно интересовало каким образом работает подобный сервис и само собой очень хотелось иметь свой сервис для сокращения ссылок для последующей публикации их в твиттер.

Предыстория

В один прекрасный момент возник вопрос: «А как это работает?». Сейчас этот вопрос кажется весьма глупым, но на тот момент я реально не понимал как создаются эти циферно-буквенные маркеры что бы каждый был уникален. Вопрос есть, полезли гуглить. Что мне только не попадалось, способов просто помойка, начиная от обработки всяческих хешей, заканчивая жуткими математическими алгоритмами от которых плавился мозг. Казалось бы бери и на основе этих решений делай свое, но так как есть у меня особенность, может быть даже дурацкая, я ненавижу готовые решения если они не изящные и не дают 100% гарантии. В итоге начался мозговой штурм.

Немного лирики

В последнее время стало популярным использование сервисов для сокращения ссылок и само собой очень много желающих сделать себе такой же сервис. Я тоже не стал исключением и очень сильно захотел себе такой же сервис для сокращения ссылок при публикации в твиттер, наивно полагая что из-за коротких ссылок останется больше места для текста. Я, как и многие другие пользователи твиттера, заблуждался поскольку простой опыт, проведенный мной, показал что нет никакой разницы какая длина у ссылки. Вы сами можете проверить это просто скопировав в поле ссылку на результаты поиска в гугл и посмотреть сколько символов она занимает и тоже самое сделать с короткой ссылкой. Обе ссылки займут одно и то же количество символов.

«Почему?» — спросите вы, а все потому, что твиттер автоматически заменяет вашу ссылку на свою короткую, несмотря на то, какая у вас ссылка. Возникает вопрос, а зачем тогда нужны такие сервисы? Все очень просто: такие сервисы облегчают обмен ссылками в как в смс, так и в иных сервисах обмена короткими сообщениями. В случае с смс короткая ссылка помогает экономить деньги, ведь чем больше ссылка, тем больше частей из которых будет состоять ваша смс, за каждую придется платить. В случае с сервисами мгновенного обмена сообщениями, подобные сервисы помогают избежать от загромождения диалога длинными ссылками.

Прежде чем браться за создание такого сервиса стоит определиться для чего он вам нужен, если можно обойтись и без него, то лучше обойтись без него. Я же не отказался от такого сервиса лишь потому, что в перспективе планировал осуществлять рассылку смс-уведомлений со своего сайта (с помощью usb-модема) что, в свою очередь, уже обязывает использовать короткие ссылки.

Пробы и ошибки

Первой мыслью было брать хеш url’а, но эта мысль была сразу отброшена. Использование функции uniqid() так же было поставлено под сомнение, поскольку количество символов в возвращаемой этой функцией строке всего 13. Тут вы конечно скажете что я сошел с ума говоря «всего 13» ведь 13 в степени 33 (23 буквы латинского алфавита + 10 цифр) будет ровняться 1.2646219E+40. Попросту говоря это число с 40 нолями, в принципе этого хватит что бы сервис пахал долгие годы, но меня это не удовлетворяло, поскольку не рнализуется весь потенциал, тем более 13 символов длиннее чем 2, 3 и т. д. К тому же в этих маркерах будут отсутствовать буквы в верхнем регистре. В итоге сформулировав требования к алгоритму приступил к размышлениям.
Требования следующие:
1. Использование букв в обоих регистрах и цифр (с возможностью использования любого сочетания)
2. Контроль длинны маркеров
3. Низкая вероятность совпадения маркеров (желательно 0%)

Первая функция, которая получилась, была такой:

function getMarker($lng) {
	$chrs = «abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789»;
	$marker = «»;
	for($i = 0; $i <= $lng; $i++) {
		$marker .= $chrs[round(0, (str_len($chrs)-1))];
	}
	return $marker;
}

Функция вернет маркер длинной $lng состоящий из цифр и букв в разных регистрах, все бы ничего, но остается вероятность совпадения маркеров, к тому же смущает явное указание длины маркера. Вероятность совпадения примерно 2%, мелочь, но уже не приятно. Эту мелочь надо было исправить. Первая и самая дикая(имея в виду последствия) мысль была заранее записать генерируемые маркеры в базу исключая совпадения, что бы затем просто выбирать свободные маркеры из базы. Написал скрипт, запустил и пошел спать. Проснувшись я был весьма шокирован объемом базы, 40 000 000+ маркеров забили больше 2Гб. Понятно что идея была, мягко говоря, не совсем удачная. При всем при этом смущало две вещи: 1) Явная длинна маркера 2) Вероятность совпадения. Решив больше не заниматься ерундой, задумался над тем, как устранить недостатки. Первое, что я решил исправить, — это явное указание длинны маркера, иными словами атрибутом функция должна принимать в качестве аргумента строку из ранее сгенерированного маркера, при отсутствии аргумента функция должна вернуть первый символ из набора, который заранее определен в самой функции.

Конечный результат

В конечном итоге получилась вот такая функция:

function getNextMarker($marker="") {
	$stock = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
	$row = strlen($marker);
	$i_ar = array();
	if ($row > 0) {
		for ($i = 0; $i < $row; $i++) {
			$i_ar[$i] = strpos($stock, $str[$i]);
		}
		$row = count($i_ar)-1;
		$i_ar[$row]++;
	} else {
		$i_ar[0] = 0;
	}
	for ($c = $row; $c >= 0; $c--) {
		if ($i_ar[$c] > 51) {
			$i_ar[$c] = 0;
			if (isset($i_ar[$c-1])) {
				$i_ar[$c-1]++;
			} else {
				$i_ar[count($i_ar)] = 0;
			}
		}
	}
	$str = "";
	for ($c = 0; $c < count($i_ar); $c++) {
		$str .= $stock[$i_ar[$c]];
	}
	return $str;
}

Логика работы такова, что когда функция получит в качестве аргумента маркер вида «a», то вернет уже «b», а если отдать функции отдать в качестве аргумента маркер вида «abZ», то функция вернет «aca». Обратите внимание что функция производит, скажем так, инкремент строки основываясь на наборе символов в переменной $stock. Когда последний символ маркера буква Z, то меняется предыдущий символ на следующий после него в наборе символов. При желании можно слегка доработать эту функцию, добавив в неё возможность самостоятельного контроля за последним маркером, на основе которого будет генерироваться следующий.

function getNextMarker() {
	$marker = @file_get_contents($_SERVER['DOCUMENT_ROOT']."/.last_marker");
	$stock = 'abcdefghijklmnopqrstuvwxyz'
		.'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
	$row = mb_strlen($marker, "UTF-8");
	$i_ar = array();
	if ($row > 0) {
		for ($i = 0; $i < $row; $i++) {
			$i_ar[$i] = strpos($stock, $marker[$i]);
		}
		$row = count($i_ar)-1;
		$i_ar[$row]++;
	} else {
		$i_ar[0] = 0;
	}
	for ($c = $row; $c >= 0; $c--) {
		if ($i_ar[$c] > 51) {
			$i_ar[$c] = 0;
			if (isset($i_ar[$c-1])) {
				$i_ar[$c-1]++;
			} else {
				$i_ar[count($i_ar)] = 0;
			}
		}
	}
	$newMarker = "";
	for ($c = 0; $c < count($i_ar); $c++) {
		$newMarker .= $stock[$i_ar[$c]];
	}
	file_put_contents($_SERVER['DOCUMENT_ROOT']."/.last_marker", $newMarker);
	return $newMarker;
}

Как вы видите функция была дополнена чтением и записью файла в котором хранится последний генерируемый маркер. Таким образом функция приобретает большую автономность. Для ещё большей гибкости можно создавать файлы с различными именами для тех или иных моментов.

Пожалуйста, оцените статью

Полная фигняУзнал немного новогоНормальная статьяХорошая статьяСупер! (Пока оценок нет)
Загрузка...

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Подпишитесь на рассылку и получайте новые статьи на почту