среда, 21 апреля 2010 г.

Lock: продолжение.

Придумал новый алгоритм для лоченья файлов. Где можно наколоться пока не вижу. Но опыт, конечно, покажет. Также у меня всегда остётся запасной вариант: man 1 flock, man 2 flock.

Допустим, нам надо изменить test.txt

1. Проверяем, нет ли test.txt.lock, изменённого менее 30 секунд назад.

2. Если есть, ждём 100 ms, идём на пункт 1.

3. Стираем лок-файл, изменённый старше 30 секунд:
find . -maxdepth 1 -mmin +0,5 -name test.txt.lock -delete
(Команду надо ещё проверить). Стирать хочется именно одной командой, чтобы во время её выполнения другой скрипт не вставил свой файл.

4. Делаем аппенд к лок-файлу:
(append-file "test.txt.lock" (append id "\n"))

5. Если мы не успели, идём на пункт 1.

6. Мы успели. Делаем необходимые манипуляции с test.txt.

7. Стираем lock-файл.

вторник, 20 апреля 2010 г.

newLISP: самодельная блокировка текстовых файлов

Проблема. Скрипт создаёт по запросу пользователей текстовые заявки с возрастающими номерами. Если два пользователя одновременно нажмут "создать заявку", может создаться две заявки с одинаковым номером. Тогда у одного из пользователей заявка не сохранится (будет затёрта вторым).

Решение.

1. Делаем в папке с заявками файл lock.

2. Убеждаемся, что генератор случайных чисел сеется. Я сею его на основе выбранного порта и системного времени. Подозреваю, в windows может не работать, если там time-of-day возвращается с точностью до секунды, а не до миллисекунды.

(seed (div (time-of-day) (int (env "REMOTE_PORT")) 0.000001))


3. Вот собственно процедура получения номера. Суть простая. Пишем через append в файл "lock" номер, который мы хотим занять и подписываем его случайным числом. Проверяем, что никто не успел вклиниться перед нами. Если мы опоздали (номер уже занят), повторяем попытку.

(define (occupy-number) (local (id num res)
(set 'id (rand-string))
(set 'num (last (or (sort (map int (directory "." "txt$"))) '(0))))
(do-until (= (or (find (s-a num ":" id) res 0) "no") (find (s-a num ":.*") res 0))
(inc num)
(append-file "lock" (s-a num ":" id "\n"))
(set 'res (read-tail "lock" 100)))
num))


Я тестировал на одновременных 100 подключениях. Работало с двойным запасом.

4. Вот воспомогательные процедуры:

(define (string-append) (apply append (map string (args))))
(set 's-a string-append)

; В Windows реальный rand (насколько я помню) имеет диапазон в 32768,
; поэтому я его для надёжности "утроил".
(define (rand-string)
(join (map string (list (rand 65536) (rand 65536) (rand 65536))) "_"))

; Возвращает последние 'lines строк файла в виде списка.
; Работает, по понятным причинам, только под *nix
(define (read-tail fname (lines 10))
(exec (append "tail " fname " -n " (string lines))))


5. Замечания.

5.1. Лог может сильно разрастись, надо как-то его стирать изредка.

5.2. Если мы стёрли файл, а в lock он остался, в нумерации будут пропуски. Это скорее фича, но тем не менее.

5.3. Процедура блокировки файлов для изменения не проработана. Если мы хотим обезопаситься ещё и от изменения, нужно думать дальше.

5.4. Схема "велосипедна". То есть, лучше бы, вообще, пользоваться базами данных.

JavaScript: закрывать псевдопопап по Escape

Как закрывать псевдопопапы по нажатию Esc.

1. Ставим обработчик в body:

<body onkeydown="if (event.keyCode == 27) closePopup();">


2. Создаём массив, в котором будет храниться список открытых окон:

// Список открытых окон
var popupList = new Array();


3. Запиливаем вот эти функции:

// Закрывает открытое окно
function closePopup() {
if (popupList.length == 0) return;
document.getElementById(popupList.pop()).style.display = 'none';
}

// Удаляет элемент из массива
function popAssoc(arr,itm) {
var arr1 = new Array();
for (i=0;i<arr.length;i++) {
if (arr[i] != itm) arr1.push(arr[i]);
}
return arr1;
}

// Показывает всплывающее окно по центру экрана рядом с курсором мыши
function showWin(id, event, firstField) {
// Если окно уже показано, прячем его
if (document.getElementById(id).style.display == 'block') {
popupList = popAssoc(popupList,id);
document.getElementById(id).style.display = 'none';
} else {
// Запоминаем окно в списке открытых
popupList.push(id);
// Вычисляем координаты
thisWin = document.getElementById(id);
thisWin.style.top = getYCoord(parseInt(thisWin.style.height), event);
thisWin.style.left = (document.body.clientWidth - parseInt(thisWin.style.width)) / 2;
// Показываем окно
thisWin.style.display = 'block';
// Фокусируемся на первом поле
if (firstField) setFocus(firstField);
}
}


Про вычисление координат для окна я уже писал раньше:
http://hilocomod.blogspot.com/2010/03/js.html

суббота, 17 апреля 2010 г.

SICP: малая теорема Ферма

Кусочек кода с распознаванием простых чисел по Малой Теореме Ферма.

; Разное встроенное
(define (even? x) (= (% x 2)))
(define (square x) (* x x))
(define (prime? p) (= (first (factor p)) p))

; Вычисляет (% (pow a b) p)
(define (expmod a b p)
(cond ((= b) 1)
((even? b) (% (square (expmod a (/ b 2) p)) p))
(true (% (* a (expmod a (dec b) p)) p))))

; Проверяет число на простоту 1 раз
(define (fermat-test p)
(let (n (+ (rand (- p 1)) 1))
(= (expmod n p p) n)))

; Проверяет число на простоту много раз
(define (fast-prime p (n 1))
(local (f)
(while (and (set 'f (fermat-test p)) (!= (dec n))))
f))

; Проверяет число на принадлежность к Кармайкловым Числам.
(define (carmaicl? x)
(and (not (prime? x))
(apply and (map (fn (n) (= (expmod n x x) n)) (sequence 1 (- x 1))))))


Функция carmaicl? работает довольно медленно. Чтобы прогнать её на числах от 2 до 5 000 потребовалось несколько минут. При этом на проверку 2х функция затрачивает примерно в 2 раза больше времени, чем на проверку х. То есть, если я правильно понимаю, на проверку диапазона 5k-10k уйдёт в три раза больше времени, чем на проверку диапазона 0k-5k.

Uptime в среде Windows

Как известно, uptime в среде Windows можно получить, набрав "net stats server".

Чтобы перевести выдаваемый текст в секунды аптайма, пришлось написать вот такую функцию:

; Показывает Uptime в среде Windows
(define (uptime)
(let (wt (regex {(\d+)/(\d+)/(\d{4}) (\d+):(\d+) ([AP]M)} (join (exec "net stats server"))))
(if (= (wt 18) "PM") (setf (wt 12) (+ (int (wt 12)) 12)))
(- (date-value)
(* (% (+ 24 (int ((regex {\d+(?=:)} (date)) 0)) (- ((now) 3))) 24) -3600)
(apply date-value (append (select (map int wt) 9 3 6 12 15) '(0))))))


Комментарии.

1. parse-date не работает в Win32.
2. (date-value параметры) возвращает время GMT. В моём случае разница составила 4 часа. Чтобы скорректировать пришлось отнять часы в (date) от часов в (now).
3. Время выполнения функции — примерно 125 миллисекунд.

пятница, 16 апреля 2010 г.

SICP 1.19: Фибоначчи по логарифму

Вот эта гнусная функция:

(define (even? x) (if (= (% x 2))))

(define (fib n (a 1) (b 0) (p 0) (q 1))
(cond ((= n) b)
((even? n) (fib (/ n 2)
a
b
(+ (* p p) (* q q))
(+ (* 2 p q) (* q q))))
(true (fib (dec n)
(+ (* b q) (* a q) (* a p))
(+ (* b p) (* a q))
p
q))))


Программирования, тут, на мой взгляд, гораздо меньше, чем математики. Думаю, это неправильно.

Чётные числа

Проверил, как быстрее считать чётность.

; Вариант 1, хитропопый
(define (even-1 x)
(if (find (last (string x)) '("0" "2" "4" "6" "8")) true))

; Вариант 2, "в лоб"
(define (even-2 x)
(if (= (% x 2))))


Как я и ожидал, вариант "в лоб" оказался примерно на порядок быстрее, да.

вторник, 13 апреля 2010 г.

SICP: итеративное возведение в степень

Долго думал, как сделать итеративный процесс возведения в степень (SICP, упражнение 1.16). Я сразу догадался, что брать за основу надо число в двоичной системе счисления, но мне было непонятно, как переводить это число в алгоритм.

Вот например, как выглядели алгоритмы и двоичная запись для некоторых чисел:

7 *+*+ 111
8 *** 1000
9 ***+ 1001
10 **+* 1010

В итоге оказалось всё просто. Надо отбрасывать самый первый знак, а дальше трактовать единицу как "возвести в квадрат и умножить", а ноль — как "просто возвести в квадрат".

Также я впервые использовал здесь функцию cons, которая объединяет first и rest.

; Возвести число b в степень p (p должно быть больше 1)
(define (pow-hm b p)
(apply (fn (x y) (if (= y) (* x x) (* x x b)))
(cons b (map int (1 (explode (bits p)))))
2))


А вот то же самое, только по "итеративному алгоритму" предложенному в SICP:

; Возвести b в степень n
(define (pow-sicp-1 b n)
(cond ((= n 1) b)
((= (% n 2)) (pow-sicp-1 (* b b) (/ n 2)))
(true (* (pow-sicp-1 (* b b) (/ n 2)) b))))


Вроде бы как, ошибиться тут просто негде. Однако… цитирую SICP:

«Храните, помимо значения степени n и основания b, дополнительную переменную состояния a, и определите переход между состояниями так, чтобы произведение abn от шага к шагу не менялось».

Спрашивается, зачем козе баян? Разве что, перехерачить алгоритм вот так:

; То же самое, но с инвариантом
(define (pow-sicp-2 b n (a 1))
(if (= n 1)
(* b a)
(* a (pow-sicp-2 (* b b) (/ n 2) (if (= (% n 2)) 1 b)))))


Согласно тестам, кстати, самый быстрый алгоритм — это рекурсия без инварианта. Алгоритмы с инвариантом и с разложением на биты немного медленнее. Впрочем, скорости там одного порядка, существенной разницы нет.

Дополнение.

Блджад. Только перейдя к упражнению 1.17 я понял, в чём была соль. По условиям задачи, у нас есть вот такие функции:

(define (double x) (* x 2))
(define (halve x) (/ x 2))
(define (even? x) (if (= (% x 2))))


Из них надо склепать умножение. Вот труе итеративный вариант:

; Умножить а на b
(define (mul-sicp-i a b (p 0))
(let (p 0)
(until (= b 1)
(set 'p (+ p (if (even? b) 0 a)))
(set 'a (double a))
(set 'b (halve b)))
(+ a p)))


А вот то же самое, но через жопу концевую рекурсию:

; Умножить a на b
(define (mul-sicp-t a b (p 0))
(if (= b 1)
(+ a p)
(mul-sicp-t (double a) (halve b) (+ p (if (even? b) 0 a)))))

воскресенье, 11 апреля 2010 г.

Способы разменять монеты

Хитроумный алгоритм по размену монет из первой главы SICP. Мне потребовалось значительное время, чтобы понять как он работает и воспроизвести его:

#
# Число способов разменять n центов монетами по 1, 5, 10, 25 и 50 центов.
#

; Доступные монеты
(set 'монеты '(1 5 10 25 50))

; Число разменов n центов с помощью c монет.
(define (размены n (c 5))
(cond ((< n) 0)
((= n) 1)
((= c 1) 1)
(true (+ (размены n (- c 1))
(размены (- n (монеты (- c 1))) c)))))


Это воссозданный мной алгоритм из книжки. Вначале же я сделал иначе: считал все возможные методы размена, потом через unique отбирал уникальные, потом через memoize добивался терпимой скорости. Тоже работало, плюс кроме числа способов размена я видел в списке ещё и сами способы.

Но решение из книжки, конечно, красивее.

Кстати, в самой книжке алгоритм реализован, на первый взгляд, примерно так же, но там есть слова: «на то, чтобы получить ответ 292 уйдёт какое-то время». В реальности время выполнения процедуры на моём компьютере — 0,3 миллисекунды.

То ли эта фраза писалась в семидесятые года, то ли newLISP гораздо круче чем Scheme.

PS: Разменять доллар можно 292 способами. Способов разменять 10 долларов уже 800 тысяч.

PPS: Те же яйца, но с печатью дерева.

(define (размены n (c 5))
(print "Р" n " (" c "): ")
(cond ((< n) (println 0) 0)
((= n) (println 1) 1)
((= c 1) (println 1) 1)
(true (println "Р" n " (" (- c 1) ") + Р" (- n (монеты (- c 1))) " (" c ")")
(+ (размены n (- c 1)) (размены (- n (монеты (- c 1))) c)))))

пятница, 9 апреля 2010 г.

Хвостовая рекурсия

Немного доброты по ссылке с форума newLISP (Ссылка)

Подводя итог, эта точка зрения на хвостовую рекурсию основана на следующих идеях:

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

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

* Любой код, который зависит от хвостовой рекурсии, легко может быть переписан в циклах. Такая перепись является тривиальной местной трансформацией.

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

Фриддл, ДКА и НКА

Закончил читать книгу Фриддла про регулярные выражения.

Фриддл, конечно, молодец, сказать нечего. Но про автоматы и про разницу между ними он написал крайне непонятно. Гораздо понятнее вот здесь:

http://www.rsdn.ru/article/alg/nka.xml

Если бы я сначала потратил 10 минут на чтение этого текста, а только потом принялся бы за Фриддла, полагаю, я бы вынес из книги Фриддла гораздо больше пользы.

четверг, 8 апреля 2010 г.

SICP

Начал читать волшебную книгу: SICP

Вот моя программка получения квадратного корня:

(define (more-exact x pre)
(let (next-pre (div (add (div x pre) pre) 2))
(if (= pre next-pre) pre (more-exact x next-pre))))

(define (squa x) (more-exact x 1))


Забавно, что программа находит, например, корень из двух всего за шесть итераций. Я раньше думал, будто нахождение корня — сложный и медленный алгоритм.

Алсо, в newLISP есть функция constant. Если хочется переопределить часть символов, чтобы использовать + вместо add и - вместо div, надо использовать её.

А вот функция для куба.

(define (cube x)
(let (more-exact (fn (pre)
(let (next-pre (div (add (div x pre pre) pre pre) 3))
(if (= pre next-pre) pre (more-exact next-pre)))))
(more-exact 1)))


Что характерно, на этот раз функция more-exact сделана локальной и, следовательно, не только не будет занимать ценное имя, но и сможет использовать родительский "х".

понедельник, 5 апреля 2010 г.

JavaScript: навигация стрелками и Enter

Убил восемь (!) часов на скорбный труд по созданию этого монстрика.

/**
*
* Keyboard Form Navigation
* Использование: в конце документа исполните KFN.init ('form1');
*
**/

// Фокусируется на элементе
function setFocus(id) {
document.getElementById(id).focus();
setTimeout("document.getElementById('"+id+"').select();", 100);
}

// Возвращает случайный ID, не присутствующий ещё в документе
function randomId (){
do {
var newId = "rnd" + Math.floor((Math.random() * 1000000));
}
while (document.getElementById(newId) != null);
return newId;
}

// Набор функций, обеспечивающий навигацию по стрелкам и Enter
var KFN = {

// Список полей формы, по которым надо перемещаться стрелками
_elements : [],

// Форма субмиттится только если _flag == true
_allow_submit : false,

// Субмитит форму. Нужен, чтобы форма не субмитилась по каждому энтеру
_submit : function () {
if (this._allow_submit)
document.getElementById(this._elements[0]).form.submit();
},

// Инициализирует форму для работы со стрелками и Enter
// Меняет onFocus у всех полей формы.
init : function (formId) {
var thisForm = document.getElementById(formId);
thisForm.setAttribute("autocomplete", "off")
if (thisForm.addEventListener) {
thisForm.addEventListener("submit", function(e) { e.preventDefault(); }, false);
thisForm.addEventListener("keydown", function(e) { KFN.next(e); }, false);
}
else if (thisForm.attachEvent) {
// Ослик вместо preventDefault использует event.returnValue = false;
thisForm.attachEvent("onsubmit", function(e) { e.returnValue = false; });
thisForm.attachEvent("onkeydown", function(e) { KFN.next(e); });
}
for (var i = 0 , e = thisForm.elements, len = e.length; i < len; i++ ) {
if (/text|password|file|checkbox|radio|select/.test (e[i].type)) {
// Если у поля нет id, ставим какой-нибудь
if (!e[i].id) e[i].id = randomId();
this._elements.push (e[i].id);
// Автокомплект должен быть отключён как на форме, так и на всех полях.
e[i].setAttribute("autocomplete", "off")
// Без этих самодельных обработок мы бы не узнали, на каком элементе фокус
e[i].hasFocus=false;
e[i].onfocus=function(){this.hasFocus=true;};
e[i].onblur =function(){this.hasFocus=false;};
} else if (e[i].type == "submit") {
// Обработка нажатия мышью на обычную субмит-кнопку
e[i].onfocus=function(){KFN._allow_submit=true;}
e[i].onblur=function(){KFN._allow_submit=false;}
if (e[i].addEventListener) {
e[i].addEventListener("click", function() { KFN._submit(); }, false);
} else if (e[i].attachEvent) {
e[i].attachEvent("onclick", function() { KFN._submit(); });
}
}
}
// Фокусируемся на первом поле формы
setFocus (this._elements[0]);
},

// Переходит к следующему полю формы
next: function (input) {
// Обрабатываем только три клавиши. 13: Enter, 38: Up, 40: Down
if ((input.keyCode != 13) && (input.keyCode != 38) && (input.keyCode != 40))
return true;
// Находим текущий элемент (по фокусу)
for (var i=0; i<this._elements.length; i++)
if (document.getElementById(this._elements[i]).hasFocus)
var current = i;
if (current === undefined) return true;
// Перемещаем фокус
switch (input.keyCode) {
case 13: // Enter
if ((current + 1) < this._elements.length) {
setFocus (this._elements[1+current]);
} else {
document.getElementById(this._elements[current]).form.submit();
}
break;
case 38: // Вверх
current--;
if (current < 0) current = 0;
setFocus (this._elements[current]);
break;
case 40: // Вниз
current++;
if (current >= this._elements.length) current = this._elements.length - 1;
setFocus (this._elements[current]);
break;
}
return false;
}
}

Windows -> Linux: ошибка sh: ./index.cgi: not found

Перенёс скрипт из Windows в Linux, поменял первую строчку с #newlisp на #/usr/bin/newlisp.

Всё равно http-сервер пишет "not found". Через несколько минут догадался открыть файл в vim.

Оказывается, у меня было написано не #/usr/bin/newlisp, а #/usr/bin/newlisp^M.

Убрал с конца Windows-перевод строки и всё заработало.

Перекодировка также сработала нормально:
http://hilocomod.blogspot.com/2010/04/php-windows-linux.html

воскресенье, 4 апреля 2010 г.

Обработка html в newLISP

Лутц таки убрал "анафорическую" переменную $it из replace. Теперь моя функция обработки html выглядит вот так:

; Отображает несколько html-файлов с вкраплениями кода на newLISP
(define (process-html)
(let (html (join (map read-file (args))))
(do-while (> $0)
(replace "<@((?!<@).)+?@>" html
(if (catch (eval-string (2 -2 $0)) 'result) (string result) "") 4))
(print "Content-type: text/html\r\n\r\n" html)))

CSS: отцентровать весь документ

По центру страницы идёт полоса шириной 800 пикселей. Как это сделать через CSS.

1. Прописываем стили:

<style>
body { font-family: Tahoma; text-align: center;} /* для Ослика */
.wrapper { width: 800; margin: 0 auto; } /* для остальных браузеров */
</style>


2. Обёртываем данные в дивы:

<body>
<div class="wrapper">
Наши данные.
</div>
<div class="wrapper">
...

пятница, 2 апреля 2010 г.

Три добродетели программиста

Источник: http://en.wikipedia.org/wiki/Larry_Wall

Во время работы над второй редакцией книги "Программируем на Перл" Ларри Волл, вместе с Рэндаллом Л. Шварцем и Томом Христиансеном, сформулировал три добродетели программиста. Вот они.

1. Лень — качество, которое заставляет тебя прикладывать большие усилия, чтобы уменьшить общие затраты энергии. Оно заставляет тебя писать трудосберегающие программы, которые другие люди находят полезными, и документировать эти программы, чтобы тебе не приходилось отвечать на кучу вопросов пользователей.

2. Нетерпеливость — гнев, который ты испытываешь, когда компьютер ленится. Этот гнев заставляет тебя писать программы, которые не только выполняют твои команды, но и предвидят их. Или, по меньшей мере, притязают на это.

3. Высокомерие — непомерная гордость, что-то из разряда тех пороков, за которые Зевс поражает молнией. Также это качество, которое заставляет тебя писать (и поддерживать) программы, которыми другие люди будут восхщаться.

четверг, 1 апреля 2010 г.

PuTTY и слепой синий цвет

В PuTTY из-под Windows синий цвет на чёрном фоне практически неразличим. Решение нашёл вот здесь:

1. Переходим в Window > Colours.
2. Ставим ANSI Blue в Red:74 Green:74 Blue:255.
3. Ставим ANSI Blue Bold в Red:140 Green:140 Blue:255.


Ещё хорошая идея сменить шрифт.

Я поставил себе Lucida 12.

Миграция PHP программы с Windows на Linux

Переводил один небольшой проект с Windows на Ubuntu. Проект был написан на быдло-PHP: аккуратно, но весьма малоопытным программистом. Вот несколько встретившихся мелочей.

1. PHP минут 15 упорно не хотел запускать index.php, предлагая вместо этого скачать phtml-файл. Дело оказалось в кэше браузера.

2. Проект хранил свои настройки в файлах с русскими именами и с русскими переменными внутри. После переконвертации всех программных и настроечных файлов из cp1251 в utf8 проект замечательно заработал. Переконвертировал я так:

find . -name "*php" -exec iconv -f cp1251 -t utf8 {} -o {}.utf8 \;
find . -iregex ".+.php$" -exec rm {} \;
find . -name '.utf8' -prune -o -exec rename 's/\.utf8$//i' {} +

3. Русские файлы прикреплялись с потерей кусков имени. Проблема оказалась в некорректной работе функции basename. Чтобы нормально заработало, пришлось добавить строку

setlocale(LC_ALL, 'ru_RU.UTF8');

4. Выяснилось, что IE, получая файлы в UTF8 через скрипт getfile.php?filename=xxx, присваивает им безумные имена. Переписал функцию, чтобы ссылка вела сразу прямо на файл, всё заработало.