Архив за месяц: Январь 2015

Немного о каррировании в Haskell

Читая М. Липовача "Изучай Haskell во имя добра!", я некоторое время не понимал, чем частичное применение отличается от каррирования. Потратил некоторое время на разбор данного вопроса и набросал себе "шпаргалку" по обозначенной теме.


В Haskell функции без параметров называются определениями (definition) или именами (name).

func :: String
func = "Haskell"

Функции не могут принимать более одного параметра. Если функция, принимает несколько параметров, то на самом деле она является функцией с одним параметром и возвращает другую функцию, которая так же принимает лишь один параметр и возвращает некоторый результат (др. функцию или конкретное значение).Т.е. если для функции указана такая сигнатура:

func :: Int -> Double -> Char -> Bool

то Haskell воспринимает её следующим образом:

func :: Int -> (Double -> (Char -> Bool))

Т.е. функция func принимает параметр типа Int и возвращает новую функцию, которая принимает очередной параметр - типа Double и возвращает другую новую функцию, принимающую параметр типа Char и возвращающую значение типа Bool.
Преобразование функции от многих аргументов в функцию, берущую свои аргументы по одному называется каррированием. Haskell автоматически выполняет каррирование всех функций, принимающих более одного параметра. Именно благодаря каррированию становится возможным частичное применение функций, а так же создание сечений. В свою очередь, частичное применение делает возможным существование бесточечной нотации.

Примечание

В Haskell не существует такого понятия, как частичное применение функции. Существует применение функции (без "частично"). Если мы говорим (для удобства восприятия), что функция f :: Int -> Int -> Int имеет два аргумента, (что с технической точки зрения не является корректным), то мы можем так же сказать (снова для удобства восприятия), что f 5 - это частично применённая функция (что так же не будет корректно технически).

Пример

func :: (Num a) => a -> a -> a -> a
func a b c d = a + b + c + d

ghci

Частичное применение:
λ: let n = func 2 3
λ: let m = n 10
λ: let g = m 7
λ: g
22
Сечения:
λ: let a = (/2)
λ: a 10
5.0
λ: let b = (15/)
λ: b 5
3.0
Бесточечная нотация:
odd' :: Integral a => a -> Bool
odd' = odd
ghci:
λ: odd' 5
True
λ: odd' 4
False

Каррирование и декаррирование

В стандартном модуле Data.Tuple определены, помимо прочего, следующие функции:
curry :: ((a, b) -> c) -> a -> b -> c
uncurry :: (a -> b -> c) -> (a, b) -> c
Функция curry преобразовывает некаррированную функцию в каррированную.
Функция uncurry преобразовывает каррированную функцию в некаррированную.

Пример


msg :: Int -> Bool -> String
msg n True = show $ n `div` 2
msg n _ = show $ n * 2

ghci


λ: let n = msg
λ: let m = uncurry n
λ: :t n
n :: Int -> Bool -> String
λ: :t m
m :: (Int, Bool) -> String
λ: n 5 True
"2"
λ: m (5,True)
"2"
λ: let k = curry m
λ: :t k
k :: Int -> Bool -> String
λ: k 5 True
"2"

Добавляем приложение в автозапуск в инсталяторах на WIX Toolset

Это совсем маленькая заметка, даже не ожидал, что все так просто. Для того, чтобы добавить приложение в автозапуск, достаточно в компонент отвечающий за добавление exe файла добавить RegistryKey:
<Component Id="ProductComponent">
  <File Source="$(var.EyeOfSauron.TargetPath)" />
  <RegistryKey
  Root="HKLM"
  Key="SoftwareMicrosoftWindowsCurrentVersionRun">
    <RegistryValue Type="string" Name="RunEyeOfSauron" Value="[INSTALLFOLDER]EyeOfSauron.exe"/>
  </RegistryKey>
</Component>

Работа с файлами в инсталяторах на WiX Toolset

В прошлой статье про проекты инсталяторов в Visual Studio я привел маленький пример, позволяющий добавить в MSI только exe файл нашего проекта. Сегодня предлагаю посмотреть как добавить другие файлы из проекта.


В прошлый раз мы остановились на том, что задали вот такую настройку для добавления файлов в проект (привожу текстом, т.к. в прошлый раз была картинка):
<Fragment>
  <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
      <Component Id="ProductComponent">
        <File Source="$(var.EyeOfSauron.TargetPath)" />
      </Component>
  </ComponentGroup>

</Fragment>
Теперь добавим копирование файла конфигурации и, например, dll которая используется в этом проекте и распространяется методом копирования. Для этого, в ту же ComponentGroup добавляем еще два компонента:
<Component Id="EyeOfSauron.exe.config">
  <File Source="$(var.EyeOfSauron.TargetDir)EyeOfSauron.exe.config" />
</Component>
<Component Id="Hardcodet.Wpf.TaskbarNotification.dll">
  <File Source="$(var.EyeOfSauron.TargetDir)Hardcodet.Wpf.TaskbarNotification.dll" />
</Component>
Обратите внимание, что вместо TargetPath (указывает на exe файл) используется TargetDir (папка куда выполнялось построение).
Ну и весьма полезный список поддерживаемых переменных (в скобках примеры того, что там в ней может быть):
$(var.MyProject.Configuration)  - конфигурация проекта (Debug или Release)
$(var.MyProject.FullConfiguration) - кроме конфигурации содержит еще и платформу под которую идет компиляция (Debug|AnyCPU)
$(var.MyProject.Platform) - только платформа (AnyCPU, Win32, x64 or ia64)
$(var.MyProject.ProjectDir) - путь до папки с проектом (C:usersmyusernameDocumentsVisual Studio 2013ProjectsMyProject)
$(var.MyProject.ProjectExt) - спорная переменная, которая позволяет получить расширение файла с проектом (.csproj)
$(var.MyProject.ProjectFileName) - имя файла проекта (MyProject.csproj)
$(var.MyProject.ProjectName) - аналогично предыдущему, но без расширения (MyProject)
$(var.MyProject.ProjectPath) - путь к файлу проекта (C:usersmyusernameDocumentsVisual Studio 2013ProjectsMyProjectMyApp.csproj)
$(var.MyProject.TargetDir) - как раз эту переменную я использовал в примере выше (C:usersmyusernameDocumentsVisual Studio 2013ProjectsMyProjectbinDebug)
$(var.MyProject.TargetExt) - расширение файла собираемого на основе проекта (.exe)
$(var.MyProject.TargetFileName) - имя собираемого файла (MyProject.exe)
$(var.MyProject.TargetName) - аналогично предыдущему, но без расширения (MyProject)
$(var.MyProject.TargetPath) - тоже есть в примере, это полный путь до результата сборки (C:usersmyusernameDocumentsVisual Studio 2013ProjectsMyProjectbinDebugMyProject.exe)
$(var.SolutionDir) - папка в которой лежит решение (C:usersmyusernameDocumentsVisual Studio 2013ProjectsMySolution)
$(var.SolutionExt) - расширение решения (.sln)
$(var.SolutionFileName) - имя файла решения (MySolution.sln)
$(var.SolutionName) - аналогично предыдущему, но без расширения (MySolution)
$(var.SolutionPath) - Полное имя решения (C:usersmyusernameDocumentsVisual Studio 2013ProjectsMySolutionMySolution.sln)

Воспользоваться этим можно, например, вот так:
if $(var.EyeOfSauron.Configuration) = "Debug" ?>
  <Component Id="EyeOfSauron.exe.config">
    <File Name="EyeOfSauron.exe.config" Source="$(var.EyeOfSauron.ProjectDir)App.Debug.config" />
  </Component>
else?>
  <Component Id="EyeOfSauron.exe.config">
    <File Source="$(var.EyeOfSauron.TargetDir)EyeOfSauron.exe.config" />
  </Component>
endif?>
Что здесь интересного:
1. Применяем if $(var.EyeOfSauron.Configuration) = "Debug" ?>, для того, чтобы проанализировать в какой конфигурации у нас идет Build.
2. Строим путь до файла в папке проекта: Source="$(var.EyeOfSauron.ProjectDir)App.Debug.config"
3. Меняем имя файла при копировании его в инсталятор:

Ну и заканчивая с файлами, как создавать вложенные папки при развертывании и как поместить в них файлы. Допустим, у нашего проекта есть папка Images и в ней лежит иконка, которую мы хотим скопировать. Вот так:
В файле Product.wxd кроме использованного выше фрагмента (Fragment) есть, если помните из предыдущей статьи и вот такой фрагмент:
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLFOLDER" Name="EyeOfSauron" />
</Directory>
</Directory>    
</Fragment>
Правим его следующим образом:
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
      <Directory Id="INSTALLFOLDER" Name="EyeOfSauron">
        <Directory Id="Images" Name="Images" />
      </Directory>
</Directory>
</Directory>    
</Fragment>
Добавляем в информацию о продукте новую ComponentGroupRef:
<Feature Id="ProductFeature" Title="EyeOfSauron" Level="1">
<ComponentGroupRef Id="ProductComponents" />
  <ComponentGroupRef Id="Icons" />
</Feature>  
Ну и в уже знакомый нам фрагмент добавляем новый ComponentGroup:
<ComponentGroup Id="Icons" Directory="Images">
  <Component Id="fire_eye_alien.ico" >
    <File Id="fire_eye_alien.ico" Source="$(var.EyeOfSauron.ProjectDir)Imagesfire_eye_alien.ico" KeyPath="yes"/>  </Component>
</ComponentGroup>
Все, папка с файлом будут доступны в развернутом приложении.
Ну и весь пример, а то вдруг чего забыл в процессе:
xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="EyeOfSauron" Language="1033" Version="1.0.0.0" Manufacturer="Life" UpgradeCode="fb4e9c90-2572-4d75-a219-02713e72ad0e">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />

<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />

    <Feature Id="ProductFeature" Title="EyeOfSauron" Level="1">
    <ComponentGroupRef Id="ProductComponents" />
      <ComponentGroupRef Id="Icons" />
    </Feature>    
</Product>

  <Fragment>
  <Directory Id="TARGETDIR" Name="SourceDir">
  <Directory Id="ProgramFilesFolder">
        <Directory Id="INSTALLFOLDER" Name="EyeOfSauron">
          <Directory Id="Images" Name="Images" />
        </Directory>
  </Directory>
  </Directory>    
  </Fragment>

  <Fragment>
  <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ProductComponent">
        <File Source="$(var.EyeOfSauron.TargetPath)" />
      </Component>      
      if $(var.EyeOfSauron.Configuration) = "Debug" ?>
        <Component Id="EyeOfSauron.exe.config">
          <File Name="EyeOfSauron.exe.config" Source="$(var.EyeOfSauron.ProjectDir)App.Debug.config" />
        </Component>
      else?>
        <Component Id="EyeOfSauron.exe.config">
          <File Source="$(var.EyeOfSauron.TargetDir)EyeOfSauron.exe.config" />
        </Component>
      endif?>
      <Component Id="Hardcodet.Wpf.TaskbarNotification.dll">
        <File Source="$(var.EyeOfSauron.TargetDir)Hardcodet.Wpf.TaskbarNotification.dll" />
      </Component>      
    </ComponentGroup>
    <ComponentGroup Id="Icons" Directory="Images">
      <Component Id="fire_eye_alien.ico" >
        <File Id="fire_eye_alien.ico" Source="$(var.EyeOfSauron.ProjectDir)Imagesfire_eye_alien.ico" KeyPath="yes"/>
      </Component>
    </ComponentGroup>
  </Fragment>
</Wix>

Об именах в Haskell

Имя любого идентификатора в Haskell начинается с буквы, за которой следует ноль или более букв, цифр, символов подчёркивания _ и одинарной кавычки '. В качестве буквы рассматриваются только латинские символы в интервалах a..z и A..Z. Символ _ принято считать буквой, в следствии чего имя функции может начинаться с этого символа, но не может состоять только из него, в виду того, что в образцах Haskell он обозначает любое значение. Имена функций, составленные не из символов набора ascSymbol, обязательно должны начинаться со строчной буквы или символа _. Имена пространств имён, типов данных, конструкторов данных и классов типов составленные не из символов набора ascSymbol должны начинаться с прописной буквы. В данной заметке даётся некоторая информация об использовании символов набора ascSymbol в идентификаторах Haskell.

"Специальные" символы

Согласно § 2.2 стандарта Haskell 2010 специальными (special) считаются следующие символы:
( ) , ; [ ] ` { }
Там же отдельно определён и следующий набор символов, именованный как ascSymbol:
! # $ % & * + . / < = > ? @ \ ˆ | - ˜ :
Буквенно-цифровые символы в стандарте выделены в отдельные наборы: ascSmall, ascLarge, uniSmall, uniLarge, ascDigit, uniDigit и т.д.
Иногда специальными ошибочно называют символы, входящие в набор ascSymbol. Например, давая определение оператору порой говорят, что их имена состоят только из специальных символов. На самом же деле специальные символы вовсе не могут использоваться в составе имён операторов (да и вообще в составе любых имён).
Из символов набора ascSymbol разрешено формировать любые имена за исключением следующих, зарезервированных:
.. : :: = \ | <- -> @ ~ =>

Функция или оператор?

Оператором в Haskell называется любая функция, вызванная в инфиксной форме, либо частично применённая посредством сечений. Т.о. можно ли назвать ту или иную функцию оператором зависит от контекста её использования. В следующих примерах функции elem и * являются операторами:
λ: 5 `elem` [0..10]
True
λ: 4 * 10
40
Некоторая пользовательская функция для использования её в качестве сечений:
mySomeFunc :: Integral a => a -> a -> a
_ `mySomeFunc` 0 = error "Zero division."
a `mySomeFunc` b = a `div` b
Используем функцию mySomeFunc в качестве сечений, т.е. в данном контексте она является оператором:
λ: let n = (8 `mySomeFunc`)
λ: let m = (`mySomeFunc` 2)
λ: n 2
4
λ: m 10
5
Функция вызванная в префиксной форме записи не является оператором в данном контексте использования. В следующих примерах функции elem и * не являются операторами:
λ: elem 5 [0..10]
True
λ: (*) 4 10
40

Инфиксная и префиксная формы

Если имя функции состоит из символов набора ascSymbol, то при указании её сигнатуры такое имя необходимо заключать в круглые скобки.
(###) :: Int -> Int -> Int -- Сигнатура функции
Если имя функции состоит из символов набора ascSymbol, и её определение даётся в префиксной форме записи, то это имя так же необходимо задавать в круглых скобках.
(@@@) a = (a +) -- Определение функции в префиксной форме
Если имя функции состоит не из символов набора ascSymbol, и её определение даётся в инфиксной форме записи, то имя необходимо обосабливать символами обратной кавычки `.
a `myFunc` b = a * b -- Определение функции в инфиксной форме
Примеры использование инфиксной и префиксной форм записей в коде определения функций:
(###) :: Int -> Int -> Int -- Сигнатура функции
a ### b = a * b -- Определение функции в инфиксной форме

(@@@) :: Int -> Int -> Int -- Сигнатура функции
(@@@) a = (a +) -- Определение функции в префиксной форме

myFunc :: Int -> Int -> Int -- Сигнатура функции
a `myFunc` b = a * b -- Определение функции в инфиксной форме

myFunc' :: Int -> Int -> Int -- Сигнатура функции
myFunc' a = (a -) -- Определение функции в префиксной форме

В инфиксной форме можно вызывать любую функцию, количество параметров которой больше одного, например:
λ: ((+) `foldr` 0) [1..10]
55
Или вот к примеру функция, принимающая четыре параметра:
someFunc :: Int -> Int -> Int -> Int -> Int
someFunc a b c d = a + b + c + d
Префиксный и инфиксный варианты её вызова:
λ: someFunc 1 2 3 4
10
λ: (1 `someFunc` 2) 3 4
10

Имена конструкторов данных

Как уже отмечалось выше - имена конструкторов данных начинаются с прописной буквы, состоят из буквенно-цифровых символов, а так же символов _ и ' (при необходимости). Однако допускается и система наименований, схожая с той, которая применяется по отношению к функциям...
Стандартом Haskell разрешается формировать имена конструкторов данных из символов набора ascSymbol. Подобно обычным функциям, такие конструкторы могут использоваться как в инфиксной, так и в префиксной форме. Кроме того, их имена обязательно должны начинаться с символа : (двоеточие). Т.е. если вы где-то в коде увидели нечто вроде 123 :#$% "ASDF", то сразу же можете быть уверенными в том, что перед вами вызов конструктора :#$% с параметрами 123 и "ASDF".

data Symbolic n
= Constant n
| Variable String
| Symbolic n :=> Symbolic n
| Symbolic n :<= Symbolic n
| (:<=>) (Symbolic n) (Symbolic n)
deriving Show
Cоздадим в ghci несколько экземпляров типа Symbolic, используя разные конструкторы данных:
λ: let a = Constant 10; b = Variable "Hello"
λ: let n = a :=> b; m = a :<= b; k = a :<=> b
λ: n
Constant 10 :=> Variable "Hello"
λ: m
Constant 10 :<= Variable "Hello"
λ: k
(:<=>) (Constant 10) (Variable "Hello")

Имена конструкторов типов

По умолчанию, имена конструкторов типов не могут состоять из символов набора ascSymbol. Однако можно принудительно разрешить использование таких имён для конструкторов типов. Это делается либо путём указания опции -XTypeOperators при вызове ghc или ghci, либо путём добавления в начало hs-файла следующей строки:
{-# LANGUAGE TypeOperators #-}
В отличие от конструктора данных, имя конструктора типа не обязано начинаться с символа : (двоеточие).

{-# LANGUAGE TypeOperators #-}
data a @# b -- Конструктор типа
-- Конструкторы данных:
= XLeft a
| XRight b
| (a @# b) :$% (a @# b)
| (a @# b) :!~ (a @# b)
| (:!~>) (a @# b) (a @# b) (a @# b)
deriving Show
Пробуем создать экземпляры нашего типа данных:
λ: let a = XLeft 10; b = XRight "ABCD"
λ: let c = a :$% b; d = b :!~ a;
λ: let e = (:!~>) a a a
λ: c
XLeft 10 :$% XRight "ABCD"
λ: d
XRight "ABCD" :!~ XLeft 10
λ: e
(:!~>) (XLeft 10) (XLeft 10) (XLeft 10)

Имена образцов

Имена образцов так же могут состоять из символов набора ascSymbol.
λ: let ($%%) = (*)
λ: :t ($%%)
($%%) :: Num a => a -> a -> a
λ: 5 $%% 2
10
λ: ($%%) 3 4
12
λ: let (###) = (3 +)
λ: (###) 2
5

UPD
Имена функциям можно назначать используя и другие символы Unicode. Например, в книгах порой можно встретить в качестве имён функций символы математических операторов. Например, можно написать такую функцию:

(∀) :: (a -> b) -> [a] -> [b]
f
[] = []
f
(x:xs) = f x : f xs
Эта функция успешно загрузится в ghci, но вызвать её будет проблематично, в виду того, что в cmd.exe и powershell.exe может оказаться затруднительным ввести в командную строку символ . У меня не получилось сделать это ни через буфер обмена (соответствующий пункт в контекстном меню), ни через комбинацию клавиш (Alt + 8704). Не помогает и использование шрифта Lucida console, и предварительный вызов команды chcp.com 65001.

Тем не менее, в некоторых книгам можно увидеть весьма активное использование математических символов в качестве имён функций в исходном коде Haskell. Например, в книге Ричарда Бёрда "Жемчужины проектирования алгоритмов. Функциональный подход. С примерами на Haskell".

Подводя итоги

Если речь идёт не о функциях, то вы вряд ли будете в именах идентификаторов использовать символы набора ascSymbol. Однако знание того, как они могут применяться за рамками имён функций поможет вам понимать код, в котором они по каким-либо причинам всё же используются.

Как изменить поведение сотрудников

Эта статья подготовлена для блогов журнала PCWeek.
Какой замечательной ни была бы ваша команда, и как хорошо ни был бы выстроен процесс, все равно вы столкнетесь с сотрудниками, которые будут нарушать существующие правила или не будут принимать новое и будут саботировать изменение процессов. Сегодня предлагаю остановиться больше на первой части: на изменении нежелательного поведения.

Человеческий мозг принято делить на творческое (правое) и рациональное (левое) полушария. Но кроме творческого и рационального начала, правое полушарие отвечает за потребности, а левое -за стратегии по их удовлетворению. То есть, именно правое полушарие получает сигнал от желудка и вырабатывает потребность : «Хочу есть». Причем потребности эти могут быть как физиологические, так и расположенные выше по пирамиде Маслоу. Именно это полушарие сообщает нам о потребности в безопасности, быть признанным и т.д. Все эти потребности распределяются правым полушарием по приоритетам, после чего в процесс вступает левое полушарие. За работу со стратегиями отвечают лобные и теменные доли. Лобные - это то, что принято называть разумом. Мы прибегаем к нему, когда, попадая в новую ситуацию, придумываем новые стратегии. Но в большинстве случаев лобные доли ленятся, и работу на себя берет теменная часть мозга. А она отвечает за использование уже имеющихся стратегий. И, так же, как правое полушарие расставляет приоритеты потребностям, левое полушарие приоритезирует стратегии: «В прошлый раз я опоздал на работу и сказал, что попал в пробку. Меня не ругали, значит, эту стратегию с придумыванием отговорок можно применить и в следующий раз».
Исходя из всего выше сказанного, для того, чтобы изменить поведение, обусловленное стратегиями, достаточно понизить приоритет стратегий контрпродуктивных и повысить приоритет стратегий продуктивных. Воспользуемся любимым инструментом многих менеджеров - матрицей два на два - и проведем с ее помощью группировку всех методов, позволяющих влиять на приоритет стратегий:
Зеленым цветом выделены так называемые «добрые методы воздействия», они нацелены на то, чтобы осуществление полезного деяния человеком получало подкрепление в виде положительных эмоций. «Злые методы», выделенные на рисунке красным, заключаются в намеренном применении страданий с целью изменить нежелательное поведение.  По большому счету, «злые методы» - это менеджерские методы. Добрые – лидерские. Видимо, благодаря работам Беррес Фредерик Скиннера и предложенной им теории оперантного научения, и закрепилась слава именно лидерских подходов. Беррес одним из первых выдвинул тезис, что стабильные изменения поведения достигаются только с помощью положительных стимулов. Но нужно понимать, что лидерские методы работают не всегда. Бывают ситуации, когда необходимо избавиться от нежелательного поведения, в которых приходится применять злые-менеджерские методы. Например, сотрудник постоянно опаздывает на работу, не сообщает вовремя о проблемах, критикует всю команду, обесценивает чужую работу и т.д. Во всех этих случаях нет полезного поведения, которое необходимо подкреплять. А есть деструктивное, которое необходимо искоренять.
К злым методам относят:
  1. Угасание
  2. Отрицательное подкрепление
  3. Ограничение
  4. Наказание

Угасаниебазируется на отказе человеку в реакции. Поведение, не получающее ни положительного, ни отрицательного подкрепления, со временем угаснет. Особенно этот метод хорошо работает с поведением, имеющим словесное выражение. К примеру, если на постоянную критику команды одним из ее членов никто не будет реагировать, то, не получая подкрепления, эта стратегия сойдет на нет. Ведь человек критикует не просто так, а потому, что у него есть потребность, например, в получении статуса. И он эту потребность пытался удовлетворить, «опуская» всех остальных. Нет реакции - нет удовлетворения потребности, стратегия откладывается, как не приносящая результата.
Отрицательное подкрепление заключается в любом неприятном событии или стимуле, пусть даже весьма слабом, который следует немедленно после действия или во время действия. В отличие от наказания, его нельзя применить через час, день, неделю. Достаточно часто отрицательное подкрепление может быть минимальным. Опоздал сотрудник на работу? В большинстве случаев, чтобы поведение изменилось, может быть достаточно хмурого взгляда или раздраженного приветствия от человека, которого опаздывающий уважает. Не нужно забывать, что у того же опоздания могут быть и объективные причины, например, заболел член семьи, отводящий ребенка в детский сад, и сейчас у сотрудника нет возможности прийти вовремя. Как только внешний фактор исчезнет, без всяких воздействий со стороны руководителя прекратится и неконструктивное поведение. Нужно просто разобраться в мотивах. Кстати, если опоздание обусловлено желанием вечером подольше почитать фейсбучек, то отрицательное подкрепление не сработает по той простой причине, что фейсбучек был вчера, а отрицательное подкрепление -сегодня.
Ограничение – один из основных менеджерских инструментов. Он заключается в невозможности осуществить нежелательную стратегию. Сотрудник пытается сидеть во время ежедневного стендапа? Проводите митинг в комнате, где сидеть вообще не на чем. Сотрудник постоянно критикует применяемые в команде процессы? Введите ограничение, что критиковать процессы можно только на ретроспективе. Увольнение сотрудника - это ведь тоже ограничение. У него больше нет возможности вести себя неправильно, по крайней мере, в рамках данной компании.
Наказание. Если предыдущий метод был основным, то этот является излюбленным методом очень многих руководителей. Когда поведение сотрудника неправильное, мы в первую очередь думаем о наказании: ругаем, снижаем зарплату, налагаем штрафы. К сожалению, этот метод работает очень плохо. Вроде и наказали за неконструктивное поведение, а оно возникает снова. Большинство руководителей просто пытается увеличить наказание. Чтобы наказание было действенным, должно сойтись много факторов:
  • Поведение должно находиться под контролем получающего наказание. Если человек вынужден отводить ребенка в детский сад за заболевшего члена семьи, то, даже наказывая его штрафом, мы не изменим поведение: сад от нашего штрафа раньше не откроется, да и заболевший быстрее не выздоровеет.
  • Сотруднику должно быть понятно, за какое поведение он не получит санкций. Если он не понимает, на что менять поведение, то он его и не изменит. Всегда нужно объяснять, что это наказание, например, не за несданный вовремя отчет. Наказание за то, что руководитель узнал о проблеме в момент, когда отчет понадобился, а не заранее, когда можно было поручить эту работу другому или помочь в работе над отчетом другим способом.
  • Наказания применяются намного реже, чем поощрения. Да, это опять отсылка к работам Скиннера: в долгосрочной перспективе работают только положительные методы.
  • Наказание воспринимается как справедливое. Это один из самых тонких моментов, но и один из самых важных. Если наказание воспринимается неадекватно легким, то на изменение поведения оно не повлияет. Если неадекватно тяжелым, то может и повлияет, но вызовет озлобленность, желание отомстить, приведет к конфликту и т.д.
  • Страх перед будущим наказанием намного больше немедленных выгод от негативного поведения. Очень часто руководители грешат тем, что сразу после нежелательного поведения на волне эмоций наказывают строго, но если денек «пошифроваться», то наказания не будет или оно будет незначительным. Это провоцирует сотрудника скрываться, отключать телефон и вообще вести себя как маленький ребенок, закрывающий глаза, чтобы не видеть буку, вместо того, чтобы решать проблему прямо сейчас. Чем позже руководитель применяет наказание сотрудника , тем оно должно быть сильнее, чтобы у наказываемого не было стимула скрывать возникающие проблемы с целью смягчения наказания.
Подводя итоги, остается только пожелать, чтобы нам всем приходилось как можно реже применять все то, что описывается в этой статье, и как можно чаще то, что на схеме находится в зеленых четвертях. Но об этом в другой раз.

Структуры в языке Си.

Видео урок.

Прежде чем говорить о структурах, вспомним массивы. Как вы, наверное, помните, массивы предназначены для хранения однотипных данных. Другими словами каждый элемент массива представляет собой значение определенного типа: целое число, символ, строка. Но зачастую, в программах, требуется хранить в одном месте данные разных типов. В качестве примера, в этом уроке будем рассматривать программу каталог книг. Про каждую книгу нам известно: название, автор, год издания, количество страниц, стоимость.
Типы переменных, используемые для хранения подобных данных очевидны:
char[] – автор, название.
int – год издания, количество страниц.
float – стоимость.

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

int book_date[100];        // дата издания
int book_pages[100];       // количество страниц
char book_author[100][50]; // автор
char book_title[100][100]; // название книги
float book_price[100];     //стоимость

Тогда, обращаясь по i-му номеру к соответствующему массиву, мы могли бы получить требуемую информацию.  Например, вот так мы могли бы вывести на экран автора, название и количество страниц четвертой книги (не забываем, что нумерация элементов массива начинается с нуля).

printf("%s-%s %d page", book_author[3], book_title[3], book_pages[3]);

Оставим пока что эту реализацию, и посмотрим, как выполнить такую же задачу с использованием структур. Но прежде всего, определим, что такое структура.

Что такое структура.

Задумаемся, что такое структура в обычном понимании этого слова. Структура – это строение или внутренне устройство какого-либо объекта.
Структура в языке Си – это тип данных, создаваемый программистом, предназначенный для объединения данных различных типов в единое целое.
Прежде чем использовать в своей программе структуру, необходимо её описать, т.е. описать её внутреннее устройство. Иногда это называю шаблоном структуры. Шаблон структуры описывается следующим образом.
На картинке слева, мы описали шаблон структуры с именем point. В любом шаблоне структуры можно выделить две основных части: заголовок (ключевое слово struct и имя структуры) и тело (поля структуры, записанные внутри составного оператора).
Точка с запятой в конце обязательна, не забывайте про неё.

Возвращаясь к нашему примеру, опишем структуру book с полями date, pages, author, title, price соответствующих типов.

struct book {
    int date;        // дата издания
    int pages;       // количество страниц
    char author[50]; // автор
    char title[100]; // название книги
    float price;     // стоимость
};

Попутно отметим, что в качестве полей структуры могут выступать любые встроенные типы данных и даже другие структуры. Подробнее об этом я расскажу в другом уроке. На имена полей накладываются те же ограничения, что и на обычные переменные. Внутри одной структуры не должно быть полей с одинаковыми именами. Имя поля не должно начинаться с цифры. Регистр учитывается.
После того, как мы описали внутреннее устройство структуры, можно считать, что мы создали новый тип данных, который устроен таким вот образом. Теперь этот тип данных можно использовать в нашей программе.
ПРИМЕЧАНИЕ:  Обычно структуры описываются сразу после подключения заголовочных файлов. Иногда, для удобства, описание структур выносят в отдельный заголовочный файл.

Как объявить структурную переменную (структуру).

Объявление структурной переменной происходит по обычным правилам.
 struct book kniga1;

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


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


Если кто-то имел дело с реляционными базами данных (MySQL, Oracle), то вам эта такая интерпретация будет очень знакома.

Как инициализировать структуру.

Хорошо, переменную мы объявили. Самое время научиться сохранять в неё данные, иначе, зачем она нам вообще нужна. Мы можем присвоить значения сразу всем полям структуры при объявлении. Это очень похоже на то, как мы присваивали значения массиву при его объявлении.

struct book kniga1 = {1998, 230, "D. Ritchi","The C programming language.", 540.2};


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

struct book kniga1 = {1998, 230, "D. Ritchi"};

Как обращаться к полям структуры.

Для обращения к отдельным полям структуры используется оператор доступа ".". Да-да, обычная точка.
Примеры:

kniga1.pages = 250; // записываем в поле pages переменной kniga1
                    // значение 250.
printf("%s-%s %d page", kniga1.author, kniga1.title, kniga1.pages);

Задание: Проверить, что хранится в полях структуры до того, как им присвоено значение.
Сейчас, в одной переменной типа book храниться вся информация об одной книге. Но так как в каталоге вряд ли будет всего одна книга, нам потребуется много переменных одного и того же типа book. А значит что? Правильно, нужно создать массив таких переменных, то есть массив структур. Делается это аналогично, как если бы мы создавали массив переменных любого стандартного типа.

struct book kniga[100];

Каждый элемент этого массива это переменная типа book. Т.е. у каждого элемента есть свои поля date, pages, author, title, price.  Тут-то и удобно вспомнить о второй интерпретации структуры. В ней массив структур будет выглядеть как таблица.


Используя массив структур получить информацию об отдельной книге можно следующим образом.

printf("%s-%s %d page", kniga[3].author, kniga[3].title, kniga[3].pages);

Обращаясь к kniga[3].author мы обращаемся к четвертой строке нашей таблицы и столбику с именем author. Удобная интерпретация, не правда ли?
На данном этапе мы научились основам работы со структурами. Точнее мы переписали с использованием структур тот же код, который использовал несколько массивов. Кажется, что особой разницы нет.  Да, на первый взгляд это действительно так. Но дьявол, как обычно, кроется в мелочах.
Например,  очевидно, что в нашей программе нам часто придется выводить данные о книге на экран. Разумным решением будет написать для этого отдельную функцию.
Если бы мы пользуемся несколькими массивами, то  эта функция выглядела бы примерно так:

void print_book ( int date, int pages, char *author, 
                  char *title, float price){
      printf("%s-%s %d page.\nDate: %d \nPRICE: %f rub.\n",
             author, title, pages,date, price);
}

А её вызов выглядел как-то вот так:

print_book (book_date[3], book_pages[3], book_author[3],
            book_title[3], book_price[3]);

Не очень-то и компактно получилось, как вы можете заметить. Легче  было бы писать каждый раз отдельный printf();.
Совсем другое дело, если мы используем структуры. Так как структура представляет собой один целый объект (большую коробку с отсеками), то и передавать в функцию нужно только его. И тогда нашу функцию можно было бы записать следующим образом:

void sprint_book (book temp){
      printf("%s-%s %d page.\nDate: %d \nPRICE: %f rub.",
             temp.author,temp.title, temp.pages, 
             temp.date, temp.price);
}

И вызов, выглядел бы приятнее:

sprint_book (kniga[3]);

Вот это я понимаю, быстро и удобно, и не нужно  каждый раз писать пять параметров. А представьте теперь, что полей у структуры  бы их было не 5, а допустим 10? Вот-вот, и я о том же.
Стоит отметить, что передача структурных переменных в функцию, как и в случае обычных переменных осуществляется по значению. Т.е. внутри функции мы работаем не с самой структурной переменной, а с её копией. Чтобы этого избежать, как и в случае переменных стандартных типов используют указатель на структуру. Там есть небольшая особенность, но об этом я расскажу в другой раз.
Кроме того, мы можем присваивать  структурные переменные, если они относятся к одному и тому же шаблону. Зачастую это очень упрощает программирование.
Например, вполне реальная задача для каталога книг, упорядочить книги по количеству страниц.
Если бы мы использовали отдельные массивы, то сортировка выглядела бы примерно так.

for (int i = 99; i > 0; i--)
    for (int j = 0; j < i; j++)
       if (book_pages[j] > book_page[j+1]){
            //меняем местами значения во всех массивах
            int temp_date;
            int temp_pages;
            char temp_author[50];
            char temp_title[100];
            float temp_price;
  
            temp_date = book_date[i];
            book_date[i] = book_date[j];
            book_date[j] = temp_date;

            temp_pages = book_pages[i];
            book_pages[i] = book_pages[j];
            book_pages[j] = temp_pages;

            //и так далее для остальных трех массивов
       }

Совсем другой дело, если мы используем структуры.

for (int i = 99; i > 0; i--)
   for (int j = 0; j < i; j++)
      if (knigi[j].pages > knigi[j+1].pages){
          struct book temp;
          temp = knigi[j];        //присваивание структур
          knigi[j] = knigi[j+1];
          knigi[j+1] = temp;
      }

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

Практическое задание:

1. Добавить в структуру поле количество прочитанных страниц.
2. Напишите несколько дополнительных функций для описанной программы.
  • Чтение данных  в структуру из файла. В файле запись о каждой книге хранится в следующем формате:
Автор||Название||Год издания||Прочитано||Количество страниц||Стоимость

Примеры:

Khnut||Art of programming. T.1||1972||129||764||234.2
Ritchie||The C Programming Language. 2 ed.||1986||80||512||140.5
Cormen||Kniga pro algoritmy||1996||273||346||239

Количество записей в файле не превышает 50 штук.
  • Вывод в файл в виде отформатированной таблицы содержимое всего каталога.
  • Функцию добавления книги в  каталог.
  • Функцию поиска по названию книги, по автору и по году издания. Например, вводим год издания, на экране формируется таблица с книгами этого года издания.
  • Сортировка книг по стоимости.
  • Функцию подсчитывающее количество прочитанных страниц.

Версия урока для просмотра  offline
Исходные коды программ  

    Структуры в языке Си.

    Видео урок.

    Прежде чем говорить о структурах, вспомним массивы. Как вы, наверное, помните, массивы предназначены для хранения однотипных данных. Другими словами каждый элемент массива представляет собой значение определенного типа: целое число, символ, строка. Но зачастую, в программах, требуется хранить в одном месте данные разных типов. В качестве примера, в этом уроке будем рассматривать программу каталог книг. Про каждую книгу нам известно: название, автор, год издания, количество страниц, стоимость.
    Типы переменных, используемые для хранения подобных данных очевидны:
    char[] – автор, название.
    int – год издания, количество страниц.
    float – стоимость.

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

    int book_date[100];        // дата издания
    int book_pages[100];       // количество страниц
    char book_author[100][50]; // автор
    char book_title[100][100]; // название книги
    float book_price[100];     //стоимость

    Тогда, обращаясь по i-му номеру к соответствующему массиву, мы могли бы получить требуемую информацию.  Например, вот так мы могли бы вывести на экран автора, название и количество страниц четвертой книги (не забываем, что нумерация элементов массива начинается с нуля).

    printf("%s-%s %d page", book_author[3], book_title[3], book_pages[3]);

    Оставим пока что эту реализацию, и посмотрим, как выполнить такую же задачу с использованием структур. Но прежде всего, определим, что такое структура.

    Что такое структура.

    Задумаемся, что такое структура в обычном понимании этого слова. Структура – это строение или внутренне устройство какого-либо объекта.
    Структура в языке Си – это тип данных, создаваемый программистом, предназначенный для объединения данных различных типов в единое целое.
    Прежде чем использовать в своей программе структуру, необходимо её описать, т.е. описать её внутреннее устройство. Иногда это называю шаблоном структуры. Шаблон структуры описывается следующим образом.
    На картинке слева, мы описали шаблон структуры с именем point. В любом шаблоне структуры можно выделить две основных части: заголовок (ключевое слово struct и имя структуры) и тело (поля структуры, записанные внутри составного оператора).
    Точка с запятой в конце обязательна, не забывайте про неё.

    Возвращаясь к нашему примеру, опишем структуру book с полями date, pages, author, title, price соответствующих типов.

    struct book {
        int date;        // дата издания
        int pages;       // количество страниц
        char author[50]; // автор
        char title[100]; // название книги
        float price;     // стоимость
    };

    Попутно отметим, что в качестве полей структуры могут выступать любые встроенные типы данных и даже другие структуры. Подробнее об этом я расскажу в другом уроке. На имена полей накладываются те же ограничения, что и на обычные переменные. Внутри одной структуры не должно быть полей с одинаковыми именами. Имя поля не должно начинаться с цифры. Регистр учитывается.
    После того, как мы описали внутреннее устройство структуры, можно считать, что мы создали новый тип данных, который устроен таким вот образом. Теперь этот тип данных можно использовать в нашей программе.
    ПРИМЕЧАНИЕ:  Обычно структуры описываются сразу после подключения заголовочных файлов. Иногда, для удобства, описание структур выносят в отдельный заголовочный файл.

    Как объявить структурную переменную (структуру).

    Объявление структурной переменной происходит по обычным правилам.
     struct book kniga1;

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


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


    Если кто-то имел дело с реляционными базами данных (MySQL, Oracle), то вам эта такая интерпретация будет очень знакома.

    Как инициализировать структуру.

    Хорошо, переменную мы объявили. Самое время научиться сохранять в неё данные, иначе, зачем она нам вообще нужна. Мы можем присвоить значения сразу всем полям структуры при объявлении. Это очень похоже на то, как мы присваивали значения массиву при его объявлении.

    struct book kniga1 = {1998, 230, "D. Ritchi","The C programming language.", 540.2};


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

    struct book kniga1 = {1998, 230, "D. Ritchi"};

    Как обращаться к полям структуры.

    Для обращения к отдельным полям структуры используется оператор доступа ".". Да-да, обычная точка.
    Примеры:

    kniga1.pages = 250; // записываем в поле pages переменной kniga1
                        // значение 250.
    printf("%s-%s %d page", kniga1.author, kniga1.title, kniga1.pages);

    Задание: Проверить, что хранится в полях структуры до того, как им присвоено значение.
    Сейчас, в одной переменной типа book храниться вся информация об одной книге. Но так как в каталоге вряд ли будет всего одна книга, нам потребуется много переменных одного и того же типа book. А значит что? Правильно, нужно создать массив таких переменных, то есть массив структур. Делается это аналогично, как если бы мы создавали массив переменных любого стандартного типа.

    struct book kniga[100];

    Каждый элемент этого массива это переменная типа book. Т.е. у каждого элемента есть свои поля date, pages, author, title, price.  Тут-то и удобно вспомнить о второй интерпретации структуры. В ней массив структур будет выглядеть как таблица.


    Используя массив структур получить информацию об отдельной книге можно следующим образом.

    printf("%s-%s %d page", kniga[3].author, kniga[3].title, kniga[3].pages);

    Обращаясь к kniga[3].author мы обращаемся к четвертой строке нашей таблицы и столбику с именем author. Удобная интерпретация, не правда ли?
    На данном этапе мы научились основам работы со структурами. Точнее мы переписали с использованием структур тот же код, который использовал несколько массивов. Кажется, что особой разницы нет.  Да, на первый взгляд это действительно так. Но дьявол, как обычно, кроется в мелочах.
    Например,  очевидно, что в нашей программе нам часто придется выводить данные о книге на экран. Разумным решением будет написать для этого отдельную функцию.
    Если бы мы пользуемся несколькими массивами, то  эта функция выглядела бы примерно так:

    void print_book ( int date, int pages, char *author, 
                      char *title, float price){
          printf("%s-%s %d page.\nDate: %d \nPRICE: %f rub.\n",
                 author, title, pages,date, price);
    }

    А её вызов выглядел как-то вот так:

    print_book (book_date[3], book_pages[3], book_author[3],
                book_title[3], book_price[3]);

    Не очень-то и компактно получилось, как вы можете заметить. Легче  было бы писать каждый раз отдельный printf();.
    Совсем другое дело, если мы используем структуры. Так как структура представляет собой один целый объект (большую коробку с отсеками), то и передавать в функцию нужно только его. И тогда нашу функцию можно было бы записать следующим образом:

    void sprint_book (book temp){
          printf("%s-%s %d page.\nDate: %d \nPRICE: %f rub.",
                 temp.author,temp.title, temp.pages, 
                 temp.date, temp.price);
    }

    И вызов, выглядел бы приятнее:

    sprint_book (kniga[3]);

    Вот это я понимаю, быстро и удобно, и не нужно  каждый раз писать пять параметров. А представьте теперь, что полей у структуры  бы их было не 5, а допустим 10? Вот-вот, и я о том же.
    Стоит отметить, что передача структурных переменных в функцию, как и в случае обычных переменных осуществляется по значению. Т.е. внутри функции мы работаем не с самой структурной переменной, а с её копией. Чтобы этого избежать, как и в случае переменных стандартных типов используют указатель на структуру. Там есть небольшая особенность, но об этом я расскажу в другой раз.
    Кроме того, мы можем присваивать  структурные переменные, если они относятся к одному и тому же шаблону. Зачастую это очень упрощает программирование.
    Например, вполне реальная задача для каталога книг, упорядочить книги по количеству страниц.
    Если бы мы использовали отдельные массивы, то сортировка выглядела бы примерно так.

    for (int i = 99; i > 0; i--)
        for (int j = 0; j < i; j++)
           if (book_pages[j] > book_page[j+1]){
                //меняем местами значения во всех массивах
                int temp_date;
                int temp_pages;
                char temp_author[50];
                char temp_title[100];
                float temp_price;
      
                temp_date = book_date[i];
                book_date[i] = book_date[j];
                book_date[j] = temp_date;

                temp_pages = book_pages[i];
                book_pages[i] = book_pages[j];
                book_pages[j] = temp_pages;

                //и так далее для остальных трех массивов
           }

    Совсем другой дело, если мы используем структуры.

    for (int i = 99; i > 0; i--)
       for (int j = 0; j < i; j++)
          if (knigi[j].pages > knigi[j+1].pages){
              struct book temp;
              temp = knigi[j];        //присваивание структур
              knigi[j] = knigi[j+1];
              knigi[j+1] = temp;
          }

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

    Практическое задание:

    1. Добавить в структуру поле количество прочитанных страниц.
    2. Напишите несколько дополнительных функций для описанной программы.
    • Чтение данных  в структуру из файла. В файле запись о каждой книге хранится в следующем формате:
    Автор||Название||Год издания||Прочитано||Количество страниц||Стоимость

    Примеры:

    Khnut||Art of programming. T.1||1972||129||764||234.2
    Ritchie||The C Programming Language. 2 ed.||1986||80||512||140.5
    Cormen||Kniga pro algoritmy||1996||273||346||239

    Количество записей в файле не превышает 50 штук.
    • Вывод в файл в виде отформатированной таблицы содержимое всего каталога.
    • Функцию добавления книги в  каталог.
    • Функцию поиска по названию книги, по автору и по году издания. Например, вводим год издания, на экране формируется таблица с книгами этого года издания.
    • Сортировка книг по стоимости.
    • Функцию подсчитывающее количество прочитанных страниц.

    Версия урока для просмотра  offline
    Исходные коды программ  

      2014 год. Цифры и факты.

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

      За 2014 год на блог зашло 67.5 тысяч уникальных пользователей. Из них, около 8.3%, то есть приблизительно 5600 пользователей, успели разочароваться в блоге за первые 15 секунд знакомства с ним и сразу же закрыли его. Но есть и другие, которые не разочаровались и совершили в общей сложности 266 788 просмотров. Причем, учитывая что в общей сложности к блогу обращались 145 тысяч раз, получается, что каждый посетитель заходил на сайт в среднем 2 раза. Хотя, понимаю, что здесь среднее не очень уместно.

      Максимальное число посетителей за сутки - 432 человека. Такое случалось дважды: 20 октября и 22 апреля. Минимальное количество посетителей за сутки – 150 человек. Данная цифра зафиксирована 5 января, то есть в новогодние праздники. Если отбросить новогодние праздники и летние каникулы, то минимальная посещаемость 227 человек.

      Средняя посещаемость 294 человек в сутки.

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

      В среднем пользователи проводят на сайте чуть более четырех минут. Максимальное среднее время за сутки составляет 15 минут. Как это ни странно, а случилось это 28 июня. Пользователей было мало, но занимались, по-видимому, они серьезно.

      Средняя глубина просмотра сайта 1.8 страниц.

      Кроме того, следующий интересный факт. Как я уже отметил раньше, в январские праздники ситуация с посещаемостью самая плачевная, провал колоссальный, почти что в два раза. Но если обратиться к статистике по месяцам, то по количеству и глубине просмотров январь лидирует, а по количеству уникальных посетителей находится на третьей позиции. Видимо, срабатывает принцип «начать новую жизнь с понедельника». Грандиозные планы на следующий год, бурная деятельность по их осуществлению в самом начале, и как итог «перегорание» через неделю-месяц усердной работы. Хотя, возможно, я и ошибаюсь и это связано с какими-то другими причинами.

      За год, мной написано всего 4 статьи. Пользователями оставлено 784 комментария, учитывая, конечно же, и мои ответы на них. В этом отношении я старался не халтурить, а отвечать по мере того, как комментарии поступали.

      На паблик в vk подписано 2360 человек.

      Кроме того, при последнем обновлении ТИЦ Яндексом, сайту присвоен ТИЦ 10.
      На этом по цифрам и фактам 2014 года всё.

      Посмотрим, что год грядущий нам готовит.