вторник, 18 мая 2010 г.
История строки User-Agent в браузерах
Любопытная статья История строки User-Agent в браузерах повествует о том, почему некоторые браузеры притворяются друг другом и почему все они притворяются браузером Mozilla.
понедельник, 17 мая 2010 г.
Common Lisp. Цикл против рекурсии
Пробуем писать простейшие циклы с помощью do. Циклы в большинстве случаев могут выполнять полезную работу только вместе с переменными, поэтому переменные, изученные в прошлых примерах, для освоения цикла будут как нельзя кстати.
1. Вычисления Факториала в цикле
2. Вычисление чисел Фибоначчи
1. Вычисления Факториала в цикле
;; Рекурсивное вычисление факториала (defun fact (n) (if (= 1 n) 1 (* n (fact (- n 1))))) ;; Рекурсивное вычисление факториала ;; с использованием хвостовой рекурсии (defun fact2 (counter &optional (val 1)) (if (= counter 0) val (fact2 (- counter 1) (* val counter)))) ;; Вычисление факториала с помощью цикла do (defun fact3 (n) (let ((f 1)) (do ((i 2 (+ i 1))) ((> i n) f) (setf f (* f i))))) ;;; Сравниваем результат вычислений и ;;; ресурсоёмкость трёх функций (= (time (fact 20000)) (time (fact2 20000)) (time (fact3 20000)))Результат выполнения:
Evaluation took: 0.426 seconds of real time 0.420026 seconds of total run time (0.400025 user, 0.020001 system) [ Run times consist of 0.064 seconds GC time, and 0.357 seconds non-GC time. ] 98.59% CPU 855,372,860 processor cycles 303,248,336 bytes consed Evaluation took: 0.437 seconds of real time 0.436027 seconds of total run time (0.436027 user, 0.000000 system) [ Run times consist of 0.044 seconds GC time, and 0.393 seconds non-GC time. ] 99.77% CPU 877,747,980 processor cycles 339,314,808 bytes consed Evaluation took: 0.402 seconds of real time 0.400025 seconds of total run time (0.380023 user, 0.020002 system) [ Run times consist of 0.076 seconds GC time, and 0.325 seconds non-GC time. ] 99.50% CPU 807,685,920 processor cycles 303,254,160 bytes consed TВариант вычисления при помощи цикла самый быстрый, на втором месте идёт рекурсивная функция, на третьем - рекурсивная с хвостовой рекурсией.
2. Вычисление чисел Фибоначчи
;; Вычисление чисел Фибоначчи рекурсией (defun fib (num &key (a 1) (b 1)) (if (= num 1) a (fib (- num 1) :a b :b (+ a b)))) ;; Вычисление чисел Фибоначчи с использованием цикла do (defun fib2 (n &key (a 1) (b 1)) (cond ((= n 1) a) ((= n 2) b) (T (do ((i 3 (+ i 1))) ((= i n) (+ a b)) (shiftf a b b (+ a b)))))) ;;; Сравниваем результат вычислений и ;;; ресурсоёмкость функций (= (time (fib 20000)) (time (fib2 20000)))Результат выполнения программы:
Evaluation took: 0.030 seconds of real time 0.032002 seconds of total run time (0.032002 user, 0.000000 system) [ Run times consist of 0.012 seconds GC time, and 0.021 seconds non-GC time. ] 106.67% CPU 60,564,250 processor cycles 17,588,392 bytes consed Evaluation took: 0.025 seconds of real time 0.024001 seconds of total run time (0.024001 user, 0.000000 system) [ Run times consist of 0.004 seconds GC time, and 0.021 seconds non-GC time. ] 96.00% CPU 48,908,280 processor cycles 17,597,480 bytes consed TВычисление с помощью циклов оказалось быстрее. Хотя, если производительность - не главное, то я выбрал бы рекурсивный вариант - он легче читается и красивее выглядит.
пятница, 14 мая 2010 г.
Продолжаем мучить Common Lisp
Продолжаю мучить Common Lisp, хотя ему всё равно - он железный, а вот себя я мучаю гораздо сильнее. Уж слишком головоломный язык, даже небольшой изученной мной частичке Common Lisp трудно уместиться в моей голове, это что-то находящееся за гранью моего предыдущего опыта.
1. Пересчёт секунд, минут и часов
Закрепляем уже пройденный материал, пытаясь применить уже известное в чуть-чуть более практической, приземлённой задаче.
Трудно объяснить всю нижеследующую магию простыми словами. Здесь используются анонимные функции (они же lambda), передача ссылки на функцию - это ещё можно понять. Но вот переменные в Common Lisp довольно необычны.
Я толком не усвоил терминологию, но своими словами могу сказать следующее: существуют глобальные переменные и локальные переменные. Глобальные переменные доступны из любого места программы, локальные - только в пределах области видимости.
Интересная особенность заключается в том, что можно в любом месте программы создать дополнительную область видимости, внутри которой переопределить значение переменной. Это делается с помощью оператора let. Все обращения к этой переменной внутри блока будут происходить к локальному же значению этой переменной. Вне оператора let переменная восстанавливает своё прежнее значение. Но при этом, в нижеследующем примере, анонимная функция сохраняет внутри себя переопределённую с помощью оператора let переменную и её значение. Это и называется замыканием. Очень необычно.
3. Чуть более сложный пример использования замыканий
Здесь уже используются ассоциативные массивы, а функция get-counter-methods возвращает в ассоциативном массиве три функции, замкнутые на одной переменной.
1. Пересчёт секунд, минут и часов
Закрепляем уже пройденный материал, пытаясь применить уже известное в чуть-чуть более практической, приземлённой задаче.
;; Переводит часы, минуты и секунды в секунды (defun hms-to-seconds (h m s) (+ (* h 3600) (* m 60) s)) ;; Переводит секунды в часы, минуты и секунды (defun seconds-to-hms (sec) (list (floor (/ sec 3600)) (floor (/ (mod sec 3600) 60)) (mod sec 60))) ;; Проверка правильности показаний часов: ;; часы не показывают отрицательные числа ;; а минуты и секунды не могут быть больше 59 (defun hms-ok (h m s) (and (>= h 0) (>= m 0) (>= s 0) (< m 60) (< s 60))) ;; Нормализовать часы, минуты и секунды: ;; перевести их в вид, пригодный для ;; отображения на часах (defun normalize-hms (h m s) (seconds-to-hms (hms-to-seconds h m s))) ;; Перевести секунды в минуты, ;; возвращается количество полных минут (defun seconds-to-minutes (sec) (floor (/ sec 60))) ;; Перевести секунды в часы, ;; возвращается количество полных часов (defun seconds-to-hours (sec) (floor (/ sec 3600))) ;; Получить показания секундной стрелки часов (defun s-from-seconds (sec) (mod sec 60)) ;; Получить показания минутной стрелки часов (defun m-from-seconds (sec) (mod (floor (/ sec 60)) 60)) ;; другой вариант одноимённой функции ;(defun m-from-seconds (sec) ; (floor (/ (mod sec 3600) 60))) ;; Получить показания часовой стрелки часов (defun h-from-seconds (sec) (floor (/ sec 3600))) ;; другой вариант одноимённой функции ;(defun seconds-to-hms (sec) ; (list (h-from-seconds sec) ; (m-from-seconds sec) ; (s-from-seconds sec)))2. Пример использования замыканий
Трудно объяснить всю нижеследующую магию простыми словами. Здесь используются анонимные функции (они же lambda), передача ссылки на функцию - это ещё можно понять. Но вот переменные в Common Lisp довольно необычны.
Я толком не усвоил терминологию, но своими словами могу сказать следующее: существуют глобальные переменные и локальные переменные. Глобальные переменные доступны из любого места программы, локальные - только в пределах области видимости.
Интересная особенность заключается в том, что можно в любом месте программы создать дополнительную область видимости, внутри которой переопределить значение переменной. Это делается с помощью оператора let. Все обращения к этой переменной внутри блока будут происходить к локальному же значению этой переменной. Вне оператора let переменная восстанавливает своё прежнее значение. Но при этом, в нижеследующем примере, анонимная функция сохраняет внутри себя переопределённую с помощью оператора let переменную и её значение. Это и называется замыканием. Очень необычно.
;;;; Пример использования замыканий ;; Функция, возвращающая фукцию-счётчик ;; Начальное значение счётчика - 0 (defun get-counter () (let ((n 0)) (function (lambda () (setf n (+ n 1)))))) ;; Более лаконичный аналог предыдущей функции ;(defun get-counter () ; (let ((n 0)) ; #'(lambda () ; (incf n)))) ;; Переменная с первой функцией-счётчиком (setf counter1 (get-counter)) ;; Переменная со второй функцией-счётчиком (setf counter2 (get-counter)) ;; Более лаконичный вариант двух предыдущих присваиваний ;(setf counter1 (get-counter) ; counter2 (get-counter)) ;;; Попеременно вызываем счётчики ;;; Счётчики при каждом вызове увеличиваются на 1 (funcall counter1) (funcall counter2) (funcall counter1) (funcall counter1) (funcall counter1) (funcall counter2)В этом примере функция counter1 будет наращивать значение своего счётчика, а функция counter2 - значение своего.
3. Чуть более сложный пример использования замыканий
Здесь уже используются ассоциативные массивы, а функция get-counter-methods возвращает в ассоциативном массиве три функции, замкнутые на одной переменной.
;;;; Ещё один пример использования замыканий ;; Функция возвращает ассоциативный список ;; из трёх функций, замкнутых на одну переменную (defun get-counter-methods () (let ((n 0)) (list :increase #'(lambda () (incf n)) :decrease #'(lambda () (decf n)) :value #'(lambda () n)))) ;; Создаём счётчик, содержащий ассоциативный ;; список трёх функций (setf counter (get-counter-methods)) ;;; Далее вызываем функции и следим ;;; за состоянием счётчика (funcall (getf counter :increase)) (funcall (getf counter :value)) (funcall (getf counter :decrease)) (funcall (getf counter :value))
среда, 12 мая 2010 г.
Продолжаем упражняться в Common Lisp
На этот раз я пробую освоить переменное количество аргументов функций, именованные аргументы и значения по умолчанию.
1. Вычисление квадратного корня с необязательным указанием точности и приближённого значения корня
Этот вариант программы плох тем, что нельзя указать при вычислении корня необходимую точность результата. Поэтому перепишем программу в следующем виде:
2. Вычисление квадратного корня с необязательным указанием точности и приближённого значения корня
По-моему гораздо удобнее. Однако если мы хотим указать точность, то необходимо также указать и приближённое значение корня. Исправим это при помощи именованных аргументов.
3. Вычисление квадратного корня с необязательным указанием точности или приближённого значения корня
Вот так-то. Теперь можно просто вычислить корень, можно вычислить с указанием необходимой точности, можно вычислить с указанием приближённого значения корня и, наконец, можно вычислить корень указав и необходимую точность и приближённое значение корня.
4. Вычисление квадратных корней списка чисел
Дополним программу из предыдущего пункта следующей функцией:
Или можно записать немного короче, заменив оператор function на его сокращённую запись:
5. Вычисление чисел Фибоначчи
Именованные аргументы со значением по умолчанию можно использовать и для сокращения других программ. Например, уже рассмотренной ранее программы для вычисления чисел из ряда Фибоначчи:
6. Вычисление числа Фи ("Золотого сечения")
7. Вычисление факториала
И даже вычисление факториала, написанное в расчёте на оптимизацию хвостовой рекурсии, можно переписать в подобном же виде:
1. Вычисление квадратного корня с необязательным указанием точности и приближённого значения корня
;;;; Приближённое вычисление квадратного корня
;; Модуль числа
(defun abs-value (num)
(if (> num 0.0)
num
(- 0.0 num)))
;; Логическая функция, возвращающая истину,
;; если числа a и b отличаются друг от друга
;; менее, чем на precision
(defun precision-ok (a b &optional (precision 0.001))
(< (abs-value (- a b)) precision))
;; Рекурсивная функция, использующая root
;; как приближённое значение корня num.
;; Функция вызывает сама себя до тех пор,
;; пока точность результата не достигнет precision
;; По умолчанию использует 1.0 в качестве приближённого
;; значения корня и 0.001 как значение точности
(defun square-root (num &optional (root 1.0))
(if (precision-ok num (* root root))
root
(square-root num (/ (+ (/ num root) root) 2))))
;; Вычисление квадратного корня 25
(square-root 25.0)
;; Вычисление квадратного корня с указанием его
;; приближённого значения
(square-root 25.0 3.0)
Этот вариант программы плох тем, что нельзя указать при вычислении корня необходимую точность результата. Поэтому перепишем программу в следующем виде:
2. Вычисление квадратного корня с необязательным указанием точности и приближённого значения корня
;;;; Приближённое вычисление квадратного корня
;; Модуль числа
(defun abs-value (num)
(if (> num 0.0)
num
(- 0.0 num)))
;; Логическая функция, возвращающая истину,
;; если числа a и b отличаются друг от друга
;; менее, чем на precision
(defun precision-ok (a b precision)
(< (abs-value (- a b)) precision))
;; Рекурсивная функция, использующая root
;; как приближённое значение корня num.
;; Функция вызывает сама себя до тех пор,
;; пока точность результата не достигнет precision
;; По умолчанию использует 1.0 в качестве приближённого
;; значения корня и 0.001 как значение точности
(defun square-root (num &optional (root 1.0) (precision 0.001))
(if (precision-ok num (* root root) precision)
root
(square-root num (/ (+ (/ num root) root) 2) precision)))
;; Вычисление квадратного корня 25
(square-root 25.0)
;; Вычисление квадратного корня с указанием его
;; приближённого значения
(square-root 25.0 3.0)
;; Вычисление квадратного корня с указанием его
;; приближённого значения и необходимой точности
(square-root 25.0 3.0 0.00001)
По-моему гораздо удобнее. Однако если мы хотим указать точность, то необходимо также указать и приближённое значение корня. Исправим это при помощи именованных аргументов.
3. Вычисление квадратного корня с необязательным указанием точности или приближённого значения корня
;;;; Приближённое вычисление квадратного корня
;; Модуль числа
(defun abs-value (num)
(if (> num 0.0)
num
(- 0.0 num)))
;; Логическая функция, возвращающая истину,
;; если числа a и b отличаются друг от друга
;; менее, чем на precision
(defun precision-ok (a b precision)
(< (abs-value (- a b)) precision))
;; Рекурсивная функция, использующая root
;; как приближённое значение корня num.
;; Функция вызывает сама себя до тех пор,
;; пока точность результата не достигнет precision
;; По умолчанию использует 1.0 в качестве приближённого
;; значения корня и 0.001 как значение точности
(defun square-root (num &key (root 1.0) (precision 0.001))
(if (precision-ok num (* root root) precision)
root
(square-root num
:root (/ (+ (/ num root) root) 2)
:precision precision)))
;; Вычисление квадратного корня 25
(square-root 25.0)
;; Вычисление квадратного корня с указанием его
;; приближённого значения
(square-root 25.0 :root 3.0)
;; Вычисление квадратного корня с указанием
;; необходимой точности
(square-root 25.0 :precision 0.000001)
;; Вычисление квадратного корня с указанием его
;; приближённого значения и необходимой точности
(square-root 25.0 :precision 0.000001 :root 25.0)
Вот так-то. Теперь можно просто вычислить корень, можно вычислить с указанием необходимой точности, можно вычислить с указанием приближённого значения корня и, наконец, можно вычислить корень указав и необходимую точность и приближённое значение корня.
4. Вычисление квадратных корней списка чисел
Дополним программу из предыдущего пункта следующей функцией:
;;Вычисление квадратных корней списка чисел
(defun square-roots (&rest rest)
(if (eql rest nil)
nil
(cons (square-root (car rest))
(apply (function square-roots) (cdr rest)))))
;; Вычисление корней 25, 64 и 121
(square-roots 25.0 64.0 121.0)
Или можно записать немного короче, заменив оператор function на его сокращённую запись:
;; То же самое, но с использованием краткой записи
;; оператора function
(defun square-roots (&rest rest)
(if (eql rest nil)
nil
(cons (square-root (car rest))
(apply #'square-roots (cdr rest)))))
;; Вычисление корней 25, 64 и 121
(square-roots 25.0 64.0 121.0)
5. Вычисление чисел Фибоначчи
Именованные аргументы со значением по умолчанию можно использовать и для сокращения других программ. Например, уже рассмотренной ранее программы для вычисления чисел из ряда Фибоначчи:
;;;; Вычисление 20000-го числа в ряду Фибоначчи
;; Рекурсивная функция, вычисляющая число Фибоначчи
;; Вызывает себя num раз, используя a и b в качестве
;; двух предыдущих чисел в ряду
(defun fib (num &key (a 1) (b 1))
(if (eql num 1)
a
(fib (- num 1) :a b :b (+ a b))))
;; Вычисление 20000-го числа в ряду Фибоначчи
(fib 20000)
;; То же самое
(fib 20000 :a 1 :b 1)
6. Вычисление числа Фи ("Золотого сечения")
;; Вычисление числа Фи
;; Совмещённое вычисление двух последовательных
;; чисел в ряду Фибоначчи с последующим делением
;; следующего числа на предыдущее
(defun phi (&key (num 200) (a 1) (b 1))
(if (eql num 1)
(* 1.0 (/ b a))
(phi :num (- num 1) :a b :b (+ a b))))
(phi)
7. Вычисление факториала
И даже вычисление факториала, написанное в расчёте на оптимизацию хвостовой рекурсии, можно переписать в подобном же виде:
;;;; Вычисление факториала 20000
;; Вычисление факториала с применением хвостовой рекурсии
(defun fact (counter &optional (val 1))
(if (eql counter 0)
val
(fact (- counter 1) (* val counter))))
;; Вычисляем факториал 20000
(fact 20000)
вторник, 11 мая 2010 г.
Немножко упражнений на Common Lisp
Решил поупражняться тут в Lisp'е, пописать математических функций в функциональном же стиле.
1. Вычисление квадратного корня
Я специально определил собственный вариант функции abs, мне так интереснее :)
2. Вычисление числа из ряда Фибоначчи
Для примера я решил вычислить двдацатитысячное по порядку число в ряду Фибоначчи.
3. Вычисление числа Фи ("Золотого сечения") двумя способами
Сначала я воспользовался для вычислений уже полученной в прошлом примере функцией для вычисления чисел Фибоначчи, а затем написал второй вариант функции, более оптимизированный.
А вот и результаты работы программы:
Первая функция не только отработала медленнее, но и затребовала для своего выполнения почти в два раза больше памяти.
1. Вычисление квадратного корня
Я специально определил собственный вариант функции abs, мне так интереснее :)
;;;; Приближённое вычисление квадратного корня
;; Модуль числа
(defun abs-value (num)
(if (> num 0.0)
num
(- 0.0 num)))
;; Логическая функция, возвращающая истину,
;; если числа a и b отличаются друг от друга
;; менее, чем на precision
(defun precision-ok (a b precision)
(< (abs-value (- a b)) precision))
;; Рекурсивная функция, использующая root
;; как приближённое значение корня num.
;; Функция вызывает сама себя до тех пор,
;; пока точность результата не достигнет precision
(defun square-root-iter (num root precision)
(if (precision-ok num (* root root) precision)
root
(square-root-iter num (/ (+ (/ num root) root) 2) precision)))
;; "Лицо" функции вычисления квадратного корня
;; Использует 1.0 в качестве приближённого значения корня
;; и точность 0.001
(defun square-root (num)
(square-root-iter num 1.0 0.001))
;; Вычисление квадратного корня 25
(square-root 25.0)
2. Вычисление числа из ряда Фибоначчи
Для примера я решил вычислить двдацатитысячное по порядку число в ряду Фибоначчи.
;;;; Вычисление 20000-го числа в ряду Фибоначчи
;; Рекурсивная функция, вычисляющая число Фибоначчи
;; Вызывает себя num раз, используя a и b в качестве
;; двух предыдущих чисел в ряду
(defun fib-iter (a b num)
(if (eql num 1)
a
(fib-iter b (+ a b) (- num 1))))
;; "Лицо" функции вычисления числа Фибоначчи
(defun fib (num)
(fib-iter 1 1 num))
;; Вычисление 20000-го числа в ряду Фибоначчи
(fib 20000)
3. Вычисление числа Фи ("Золотого сечения") двумя способами
Сначала я воспользовался для вычислений уже полученной в прошлом примере функцией для вычисления чисел Фибоначчи, а затем написал второй вариант функции, более оптимизированный.
;;;; Сравнение двух способов вычисления числа Фи
;;;; - "Золотого сечения"
;; Рекурсивная функция, вычисляющая число Фибоначчи
;; Вызывает себя num раз, используя a и b в качестве
;; двух предыдущих чисел в ряду
(defun fib-iter (a b num)
(if (eql num 1)
a
(fib-iter b (+ a b) (- num 1))))
;; "Лицо" функции вычисления числа Фибоначчи
(defun fib (num)
(fib-iter 1 1 num))
;; Вычисление числа Фи через два вызова функции,
;; вычисляющей число в ряду Фибоначчи
(defun phi ()
(* 1.0 (/ (fib 2000) (fib 1999))))
;; Вычисление числа Фи
;; Совмещённое вычисление двух последовательных
;; чисел в ряду Фибоначчи с последующим делением
;; следующего числа на предыдущее
(defun phi2-iter (a b num)
(if (eql num 1)
(* 1.0 (/ b a))
(phi2-iter b (+ a b) (- num 1))))
;; "Лицо" функции вычисления числа Фи
(defun phi2 ()
(phi2-iter 1 1 2000))
;; Сравниваем результаты вычисления обеих функций,
;; попутно измеряя время, потраченное на вычисления
;; Первая функция должна быть медленнее в 2 раза
;; второй
(eql (time (phi)) (time (phi2)))
А вот и результаты работы программы:
Evaluation took:
0.001 seconds of real time
0.000000 seconds of total run time (0.000000 user, 0.000000 system)
0.00% CPU
2,279,699 processor cycles
400,944 bytes consed
Evaluation took:
0.001 seconds of real time
0.000000 seconds of total run time (0.000000 user, 0.000000 system)
0.00% CPU
1,470,207 processor cycles
204,584 bytes consed
T
Первая функция не только отработала медленнее, но и затребовала для своего выполнения почти в два раза больше памяти.
Сравнение скорости вычисления факториала на Scheme и Common Lisp
Scheme
Рекурсивное вычисление факториала на Scheme:
Итеративное вычисление факториала на Scheme:
Теперь создаём две программы, в первой из которых определяется рекурсивный вариант функции вычисления факториала, а во второй - итеративный. Добавляем в каждый из файлов вычисление факториала 20000:
Я намеренно добавил сравнение результата вычисления с единицей, дабы избежать потерь времени на перевод числа в десятичную систему и вывод результата на экран.
Запускаем обе программы следующим образом и получаем время выполнения каждой из них:
То было на MIT'овской реализации, а теперь попробуем на PLT:
Рекурсивный код оказался немного быстрее в обоих случаях. Реализация PLT обгоняет MIT'овскую на этой задаче.
Common Lisp
Рекурсивное вычисление факториала на Common Lisp:
Итеративное вычисление факториала в Common Lisp. Процесс вычисления факториала происходит итеративно за счёт применения в компиляторе оптимизации хвостовой рекурсии:
Программа для сравнения рекурсивного и итеративного вариантов вычисления факториала:
Здесь сравнивая результаты вычисления между собой я одновременно выполняю правильность обеих функций и избегаю перевода результатов вычислений в десятичную систему и вывода результатов на экран.
Результат выполнения программы:
Рекурсивный вариант оказался немного быстрее. Код тестировался на реализации SBCL, которая компилирует программу в машинный код платформы x86. Есть ещё реализация CMUCL, являющаяся предком SBCL, она компилирует программу в код собственной виртуальной машины. Не стоит говорить, что SBCL оказался быстрее, чем CMUCL.
Какие из всего этого можно сделать выводы? Либо в этих компиляторах оптимизация хвостовой рекурсии не выполняется, и поэтому итеративные функции показывают сравнимое и даже большее время вычисления, либо оптимизация выполняется настолько качественно, что компилятору удаётся свести рекурсивные функции к итеративным.
Рекурсивное вычисление факториала на Scheme:
(define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) (fact 20000)
Итеративное вычисление факториала на Scheme:
(define (fact n) (fact-iter 1 n)) (define (fact-iter val counter) (if (= counter 0) val (fact-iter (* val counter) (- counter 1))))
Теперь создаём две программы, в первой из которых определяется рекурсивный вариант функции вычисления факториала, а во второй - итеративный. Добавляем в каждый из файлов вычисление факториала 20000:
(= 1 (fact 20000))
Я намеренно добавил сравнение результата вычисления с единицей, дабы избежать потерь времени на перевод числа в десятичную систему и вывод результата на экран.
Запускаем обе программы следующим образом и получаем время выполнения каждой из них:
time mit-scheme < fact.scheme > /dev/null ; time mit-scheme < fact2.scheme > /dev/null real 0m3.760s user 0m3.712s sys 0m0.048s real 0m3.379s user 0m3.344s sys 0m0.036s
То было на MIT'овской реализации, а теперь попробуем на PLT:
time plt-r5rs < fact.scheme > /dev/null ; time plt-r5rs < fact2.scheme > /dev/null real 0m1.363s user 0m1.256s sys 0m0.108s real 0m1.235s user 0m1.152s sys 0m0.080s
Рекурсивный код оказался немного быстрее в обоих случаях. Реализация PLT обгоняет MIT'овскую на этой задаче.
Common Lisp
Рекурсивное вычисление факториала на Common Lisp:
(defun fact (n) (if (eql n 0) 1 (* n (fact (- n 1))))) (fact 20000)
Итеративное вычисление факториала в Common Lisp. Процесс вычисления факториала происходит итеративно за счёт применения в компиляторе оптимизации хвостовой рекурсии:
(defun fact (n) (fact-iter 1 n)) (defun fact-iter (val counter) (if (eql counter 0) val (fact-iter (* val counter) (- counter 1)))) (fact 20000)
Программа для сравнения рекурсивного и итеративного вариантов вычисления факториала:
;;; Итеративное вычисление факториала. ;;; Ожидается именно итеративное вычисление за счёт возможности ;;; применить хвостовую рекурсию (defun fact (n) (fact-iter 1 n)) (defun fact-iter (val counter) (if (eql counter 0) val (fact-iter (* val counter) (- counter 1)))) ;;; Рекурсивное вычисление факториала. (defun fact2 (n) (if (eql n 0) 1 (* n (fact2 (- n 1))))) ;;;; Пробуем сравнить оба варианта вычисления по идентичности полученного ;;;; результата и времени выполнения (eql (time (fact 20000)) (time (fact2 20000))) (quit)
Здесь сравнивая результаты вычисления между собой я одновременно выполняю правильность обеих функций и избегаю перевода результатов вычислений в десятичную систему и вывода результатов на экран.
Результат выполнения программы:
Evaluation took: 0.408 seconds of real time 0.408026 seconds of total run time (0.392025 user, 0.016001 system) [ Run times consist of 0.040 seconds GC time, and 0.369 seconds non-GC time. ] 100.00% CPU 819,948,580 processor cycles 339,303,792 bytes consed Evaluation took: 0.383 seconds of real time 0.380023 seconds of total run time (0.376023 user, 0.004000 system) [ Run times consist of 0.072 seconds GC time, and 0.309 seconds non-GC time. ] 99.22% CPU 769,179,220 processor cycles 303,251,800 bytes consed T
Рекурсивный вариант оказался немного быстрее. Код тестировался на реализации SBCL, которая компилирует программу в машинный код платформы x86. Есть ещё реализация CMUCL, являющаяся предком SBCL, она компилирует программу в код собственной виртуальной машины. Не стоит говорить, что SBCL оказался быстрее, чем CMUCL.
Какие из всего этого можно сделать выводы? Либо в этих компиляторах оптимизация хвостовой рекурсии не выполняется, и поэтому итеративные функции показывают сравнимое и даже большее время вычисления, либо оптимизация выполняется настолько качественно, что компилятору удаётся свести рекурсивные функции к итеративным.
Как (не) работает рыночная экономика
Несколько недель назад наткнулся на такой вот сайт http://knukim-edu.kiev.ua/. Настолько элементарного изложения проблем современной рыночной экономики я ещё не видел. Это букварь макроэкономики для дошкольников. Обязательно прочитайте цитату. Вы поймёте, почему общепризнанная экономика не является наукой: микроэкономика и макроэкономика противоречат друг другу и вообще не могут создать непротиворечивую картину мира. Они не могут даже объяснить происходящего, а потому и речи быть не может о том, чтобы с их помощью вывести рецепты достижения равновесного положения экономики.
И вот тут уже неизбежна пролетарская революция. Опять повторится 1917 год. Вы понимаете? Если законными способами не забирать деньги у самого богатого и не возвращать их снова в обращение, то остаётся только революция.
Почитайте статью по ссылке, там есть ещё очень много интересного. Читать, правда, тяжело и долго. Возможно я со временем ещё выкину в свой блог несколько самых ёмких цитат.
Элементарнейший пример:
Представьте себе "остров", в котором существует замкнутая экономика в виде одной деревни (Наша земля это тот же остров, только побольше), которая на заводике производит некую "еду" и продает ее в своем "сельпо". Вся наша деревня работает (возможно, сама с себя собирает налог, с которого кормит этой "едой" десяток пенсионеров, одного инвалида, детский садик и сторожа возле леса).
Работающие получают зарплату (часть ее уходит в виде налогов на содержание вышеперечисленных "нетрудоспособных" и "армии"), еда раскупается, все довольны.
Если кто-то из работающих "недоедает", - остается после работы на сверхурочные... производит больше "еды", получает больше зарплаты (и соответственно больше налогов и больше тратит в магазине). Ест и сам больше и больше остается другим (нетрудоспособным).
Идиллия. Деньги вращаются по кругу. Каждый работает ровно на столько, чтобы ему хватало.
А теперь представим, что у нашего заводика появился "хозяин".
Заводик за месяц произвел необходимое количество еды. Цена ее известна и устоялась, налоги уплачены, но... тут "хозяин" накидывает в цену 20% своей законной(!) прибыли, и выставляет "еду" на продажу в магазине.
Что происходит дальше?
Правильно. Продано будет только около ~ 80%. (Строго говоря, сам хозяин также является потребителем. Но весь избыток он все равно не съест. Лопнет.)
Потому что только на эту сумму выплачено зарплат и налогов. На остальные 20% деревня просто будет недоедать. Не потому что «еды» нет. Есть. Но купить ее, - нет денег. На их возмущение, он им порекомендует не лениться, а лучше и больше работать.
В следующем месяце хозяин произведет только 80% от количества необходимой еды. (Зачем больше? У него и те 20% остались нераспроданными.) Соответственно и его работающие будут заняты на 20% меньше времени, и естественно получат настолько же меньшую зарплату...
С каждым циклом производство будет сворачиваться.
В пределе этой сходящейся последовательности мы получим:
1. Остановившееся производство.
2. Полный склад товара ("еды"). (А мы еще удивлялись в Советском Союзе, откуда такое изобилие на витринах капитализма? Да просто денег у населения меньше, чем суммарная стоимость товара. Недальновидный Госкомцен, следивший за соответствием суммарной заработной платы – товарной массе, мог организовать изобилие витрин одним росчерком пера. Заодно и стимулы к интенсивной работе.)
3. Все деньги стекшиеся к "хозяину".
4. Голодную, безработную деревню, которой конечно можно посоветовать побольше работать, чтобы заработать денег, но особого смысла в этом нет. В нашей модели экономики просто не осталось денег. Они все выведены из нее и сосредоточились на одном из полюсов.
... пропущено ...
Можно ли что-то сделать, чтобы наша экономическая система не сколлапсировала а продолжала работать?
Да, можно.
1. Если хозяин, к примеру, купит у самого голодного «сарай с курями». Тот получит немного денег. Отдаст долги, отправит перевод матери, даст сынишке на карманные расходы… То есть в обороте нашей экономики снова появились деньги. В магазине купят еды. Значит, появился спрос. Раз есть спрос, - хозяин срочно наймет работников, раскрутит производство, появятся зарплаты и экономика оживет… - но!
Денег за сарай надолго не хватит. Через несколько циклов производства, чтобы его не останавливать, нужно будет покупать сарай уже у другого. Потом дома, землю… и так далее. Через какое-то количество времени, все снова вернется к состоянию «Великой Депрессии», но ни у кого уже не будет ни земли, ни недвижимости, ни имущества.
Можно ли в этой ситуации что-то сделать, чтобы продолжить производство еды?
Да, можно.
2. Можно еще взять денег в долг у хозяина, но взамен предложить ему свою долговую расписку.
Это снова оживит экономику, на какое-то время. Пока хозяину не надоест давать в долг. Он назовет всех дармоедами, лентяями, вечными должниками и остановит «кредит».
Вот тут уже сделать ничего нельзя. Если это остров, - то все. Можно считать, что пришел пушистый северный зверек. Даже натуральное хозяйство здесь уже невозможно, потому что у большинства не осталось ни товаров, ни собственности для обмена.
И вот тут уже неизбежна пролетарская революция. Опять повторится 1917 год. Вы понимаете? Если законными способами не забирать деньги у самого богатого и не возвращать их снова в обращение, то остаётся только революция.
Почитайте статью по ссылке, там есть ещё очень много интересного. Читать, правда, тяжело и долго. Возможно я со временем ещё выкину в свой блог несколько самых ёмких цитат.
понедельник, 10 мая 2010 г.
Роботы
Хочу поделиться несколькими интересными новостями из мира робототехники.
1. Робот PR2 складывает полотенца. Это первый в мире робот, оперирующий нежёсткими объектами, меняющими свою форму прямо в процессе работы робота.
Подробнее тут: В Беркли научили робота складывать полотенца
2. Робот, балансирующий на шаре для боулинга. Это не первый такой робот. Новинка именно этого робота заключается в том, что он умеет не только балансировать на шаре и кататься на нём по полу в любом направлении, а ещё и поворачиваться на шаре вокруг своей оси. Робот работает в одном из двух режимов:
1. удержание позиции - при толчке робота, он стремится вернуться на прежнюю точку,
2. подчинение воздействию - при небольшом усилии, приложенном к роботу, робот подчиняется ему и проезжает небольшое расстояние в сторону действия усилия. Второй режим работы может быть полезен для транспортировки предметов - робот может выполнять роль примитивной тачки :)
Подробнее здесь: Новый японский робот ездит на шаре для боулинга
3. 11 роботов модели PR2 стоимостью 4,4 миллиона долларов каждый, подарены 11 исследовательским группам в различных странах. Подарок всё же не бесплатен: исследовательские группы обязуются открыть исходный код своих наработок для подаренных роботов.
Подробнее тут: Исследователям бесплатно раздали персональных роботов
1. Робот PR2 складывает полотенца. Это первый в мире робот, оперирующий нежёсткими объектами, меняющими свою форму прямо в процессе работы робота.
Подробнее тут: В Беркли научили робота складывать полотенца
2. Робот, балансирующий на шаре для боулинга. Это не первый такой робот. Новинка именно этого робота заключается в том, что он умеет не только балансировать на шаре и кататься на нём по полу в любом направлении, а ещё и поворачиваться на шаре вокруг своей оси. Робот работает в одном из двух режимов:
1. удержание позиции - при толчке робота, он стремится вернуться на прежнюю точку,
2. подчинение воздействию - при небольшом усилии, приложенном к роботу, робот подчиняется ему и проезжает небольшое расстояние в сторону действия усилия. Второй режим работы может быть полезен для транспортировки предметов - робот может выполнять роль примитивной тачки :)
Подробнее здесь: Новый японский робот ездит на шаре для боулинга
3. 11 роботов модели PR2 стоимостью 4,4 миллиона долларов каждый, подарены 11 исследовательским группам в различных странах. Подарок всё же не бесплатен: исследовательские группы обязуются открыть исходный код своих наработок для подаренных роботов.
Подробнее тут: Исследователям бесплатно раздали персональных роботов
"Пикник на обочине" и "Отель "У погибшего Альпиниста""
Пикник на обочине
Идея романа очень интересна. Наивныечукотские школьники писатели-фантасты представляют себе первый контакт самым разным образом, но почти всегда объединяет их одна идея - первый контакт будет встречей равных, или по крайней мере не очень различающихся по уровню развития, цивилизаций.
А вот не тут то было. Стругацкие подумали и решили разорвать шаблон. Мы всё ждём первого контакта, ждём братьев по разуму, но не тут то было. Братья по разуму оказываются богами по разуму и просто не замечают нас. Им даже не интересно исследовать какую-то необычную форму жизни, что было бы безусловно интересно сделать людям. У них свои цели, которых мы просто не можем представить, а уж тем более - понять. Их возможности настолько велики, что то, о чём мы иногда думаем - для них обыденность, а то, о чём мы даже не можем помыслить - давно привычные вещи.
Летят они по своим делам и высаживаются на Землю. Высадились, отдохнули, и полетели дальше, не заметив тысяч погибших по их вине живых существ, и оставив за собой горы высокотехнологического мусора. Если перенести аналогию на наш уровень - это как если бы мы поехали в соседний город, остановившись где-то на полянке вдоль дороги, чтобы поесть. Раздавили колёсами машины муравейник, примяли длинные полосы травы, уронили крошки хлеба, колбасы, капнули машинным маслом. Отдохнули и поехали дальше.
И вот мы, как жители муравейника, не поняв толком произошедшего, пытаемся восстановить муравейник, попадая в лужицы машинного масла, наталкиваясь на трупы своих соплеменников. В конце концов мы понимаем, что восстанавить муравейник совершенно невозможно, т.к. каждого, отправившегося в Зону, ждёт практически неминуемая гибель. Самые смелые из нас, плюя на все запреты, пытаются попасть в Зону и унести из неё какой-нибудь артефакт. Самые любопытные из нас изучают артефакты и иногда находят им применение, не понимая принципов, по которым они работают. Нарушение законов термодинамики, физические силы неизвестной природы - учёных это вводит в ужас. А сталкеры боятся "комариных плешей" - областей повышенной гравитации, мгновенно расплющивающих в лужи металла вертолёты, и "ведьминых студней" - артефактов, проникающих сквозь стены, превращающих твёрдые предметы в желеобразные.
И те и другие испытывают жуткий страх, поняв насколько мало они на самом деле знают. И только для обывателей, которых никак не затронуло посещение, всё остаётся по-прежнему. Они посмеиваются над рассказами бывших в Зоне сталкеров и учёных, испытывают усталость от надоедливой истерии журналистов. Для них никакого посещения не было и жизнь как шла, так и идёт.
С другой стороны, сюжет у романа не прослеживается, его как будто нет. Есть просто отдельные эпизоды из жизни нескольких людей, проживающих рядом с Зоной, и живущих ею.
Отель "У погибшего Альпиниста"
Этот роман хорош своим юмором, закрученным детективным сюжетом. Интересна идея того, что не опытные инопланетяне, не достаточно изучившие людей, могут попасть в лапы преступников. Недостаточное изучение тонкостей земной цивилизации и неосторожное, поспешное внедрение на Землю грозит обернуться межпланетным скандалом и последующим разрывом всяких дипломатических отношений. В этом романе, наконец, объясняется каким образом инопланетяне могут спокойно обитать на чужой планете незамеченными. Наконец-то мы видим не непонятно каким образом взявшихся на разных планетах абсолютно одинаковых людей, а применение скафандров, имеющих полное внешнее сходство местными обитателями планеты.
Идея романа очень интересна. Наивные
А вот не тут то было. Стругацкие подумали и решили разорвать шаблон. Мы всё ждём первого контакта, ждём братьев по разуму, но не тут то было. Братья по разуму оказываются богами по разуму и просто не замечают нас. Им даже не интересно исследовать какую-то необычную форму жизни, что было бы безусловно интересно сделать людям. У них свои цели, которых мы просто не можем представить, а уж тем более - понять. Их возможности настолько велики, что то, о чём мы иногда думаем - для них обыденность, а то, о чём мы даже не можем помыслить - давно привычные вещи.
Летят они по своим делам и высаживаются на Землю. Высадились, отдохнули, и полетели дальше, не заметив тысяч погибших по их вине живых существ, и оставив за собой горы высокотехнологического мусора. Если перенести аналогию на наш уровень - это как если бы мы поехали в соседний город, остановившись где-то на полянке вдоль дороги, чтобы поесть. Раздавили колёсами машины муравейник, примяли длинные полосы травы, уронили крошки хлеба, колбасы, капнули машинным маслом. Отдохнули и поехали дальше.
И вот мы, как жители муравейника, не поняв толком произошедшего, пытаемся восстановить муравейник, попадая в лужицы машинного масла, наталкиваясь на трупы своих соплеменников. В конце концов мы понимаем, что восстанавить муравейник совершенно невозможно, т.к. каждого, отправившегося в Зону, ждёт практически неминуемая гибель. Самые смелые из нас, плюя на все запреты, пытаются попасть в Зону и унести из неё какой-нибудь артефакт. Самые любопытные из нас изучают артефакты и иногда находят им применение, не понимая принципов, по которым они работают. Нарушение законов термодинамики, физические силы неизвестной природы - учёных это вводит в ужас. А сталкеры боятся "комариных плешей" - областей повышенной гравитации, мгновенно расплющивающих в лужи металла вертолёты, и "ведьминых студней" - артефактов, проникающих сквозь стены, превращающих твёрдые предметы в желеобразные.
И те и другие испытывают жуткий страх, поняв насколько мало они на самом деле знают. И только для обывателей, которых никак не затронуло посещение, всё остаётся по-прежнему. Они посмеиваются над рассказами бывших в Зоне сталкеров и учёных, испытывают усталость от надоедливой истерии журналистов. Для них никакого посещения не было и жизнь как шла, так и идёт.
С другой стороны, сюжет у романа не прослеживается, его как будто нет. Есть просто отдельные эпизоды из жизни нескольких людей, проживающих рядом с Зоной, и живущих ею.
Отель "У погибшего Альпиниста"
Этот роман хорош своим юмором, закрученным детективным сюжетом. Интересна идея того, что не опытные инопланетяне, не достаточно изучившие людей, могут попасть в лапы преступников. Недостаточное изучение тонкостей земной цивилизации и неосторожное, поспешное внедрение на Землю грозит обернуться межпланетным скандалом и последующим разрывом всяких дипломатических отношений. В этом романе, наконец, объясняется каким образом инопланетяне могут спокойно обитать на чужой планете незамеченными. Наконец-то мы видим не непонятно каким образом взявшихся на разных планетах абсолютно одинаковых людей, а применение скафандров, имеющих полное внешнее сходство местными обитателями планеты.
Подписаться на:
Сообщения (Atom)