вторник, 22 ноября 2011 г.

JavaScript: Случайные элементы массива

Родилось из C#-овой, но на JavaScript наглядней.

Нужно выбрать из массива N случайных элементов. Как это сделать быстро?

Если длина массива <= N - это очевидно. А если нет? Сначала склонируем массив:
Object.prototype.clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (i in this) {
  if (i == 'clone') continue;
  newObj[i] = (this[i] && typeof this[i] == "object") ? this[i].clone() : this[i];
  return newObj;
};

Потом создадим новый массив и скопируем в него нужное число элементов, а из оригинала удалим:

function selectRemoveAdd(arr, max)
{
 var arrLength = arr.length;
 if(max >= arrLength)
  return arr.clone();
 var newArray = [];
 var cloneArray = arr.clone();
 var i = 0, newPos = 0;
 while(i++ < max)
 {
  newPos = getRandom(arrLength);
  newArray.push(cloneArray[newPos]);
  cloneArray.splice(newPos, 1);
  arrLength--;
 }
 return newArray;
}
Такой вариант обычно предлагают на форумах. Но зачем создавать ещё одни массив? Можно взять исходный и удалить всё лишнее:
var arrLength = arr.length;
 if(max >= arrLength)
  return arr.clone();
 var newArray = arr.clone();
 while(arrLength-- > max)
  newArray.splice(getRandom(arrLength), 1);
 return newArray;
Какой вариант быстрее? Правильный ответ, что быстрее оба, но по-разному. Если N меньше arr.length / 2, то первый, если больше - то второй. Поэтому самый красивый и правильный способ - это:
function selectRemoveOnly(arr, max)
{
 var arrLength = arr.length;
 var newArray = arr.clone();
 while(arrLength-- > max)
   newArray.splice(getRandom(arrLength), 1);
 return newArray;
}
function selectRemoveAdd(arr, max)
{
 var arrLength = arr.length;
 var newArray = [];
 var cloneArray = arr.clone();
 var i = 0, newPos = 0;
 while(i++ < max)
 {
  newPos = getRandom(arrLength);
  newArray.push(cloneArray[newPos]);
  cloneArray.splice(newPos, 1);
  arrLength--;
 }
 return newArray;
}
function selectOptimised(arr, max)
{
 if(max >= arr.Length)
  return arr.clone();
 if(max <= arr.length / 2)
  return selectRemoveAdd(arr, max);
 else
  return selectRemoveOnly(arr, max);
}
И работать будет просто замечательно:

пятница, 4 ноября 2011 г.

C#: Пишем в Control из Thread

Решений немало.

Простое, как грабли, через делегат:
string newText = "abc"; // running on worker thread
this.Invoke((MethodInvoker)delegate {
    someLabel.Text = newText; // runs on UI thread
});

То же самое, вынесено в отдельную функцию и выверено по MSDN:

delegate void UpdateLabelDelegate (string message);

void UpdateLabel (string message)
{
    if (InvokeRequired)
    {
         Invoke (new UpdateLabelDelegate (UpdateLabel), new object[] { message });
         return;
    }
    MyLabelControl.Text = message;
}

Там же - замечательные LINQ-оподобные конструкции, которые не так просто вставить в Blogger - там есть Template-овы конструкции вроде < Func <, а Blogger принимает их за теги. Поэтому не забываем ставить пробелы: Очаровательное LINQ-оподобное решение:

private delegate void SetPropertyThreadSafeDelegate(Control @this, Expression< Func > property, TResult value);

public static void SetPropertyThreadSafe(this Control @this, Expression< Func > property, TResult value)
{
var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo;

if (propertyInfo == null ||
!@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
@this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}

if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate(SetPropertyThreadSafe), new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, @this, new object[] { value });
}
}

Улучшенные вариант с проверкой на null и более жёсткой типизацией:

public static void SetPropertyInGuiThread< C, V >(this C control, Expression< Func < C, V >> property, V value) where C : Control
{
    var memberExpression = property.Body as MemberExpression;
    if (memberExpression == null)
        throw new ArgumentException("The 'property' expression must specify a property on the control.");

    var propertyInfo = memberExpression.Member as PropertyInfo;
    if (propertyInfo == null)
        throw new ArgumentException("The 'member' expression must specify a property on the control.");

    if (control.InvokeRequired)
        control.Invoke(
            (Action< C, Expression< Func < C, V >>, V>)SetPropertyInGuiThread,
            new object[] { control, property, value }
        );
    else
        propertyInfo.SetValue(control, value, null);
}

среда, 2 ноября 2011 г.

JavaScript: Задачка

Что напришет в лог вот этот код? Ответ обосновать.

a=1/2; if (a = 0,5) console.log(a);

JavaScript: Объекты и необъекты

Говорят, во всём семействе ECMAScript все переменные - псевдообъекты.

Так вот, это неправда.

В JavaScript, например, всего 6 типов объектов:

null, undefined, number, string, boolean и object

а значит, записать в числовую переменную новое свойство - нельзя.

Вызывая оператор "." для number, string, boolean мы просто создаём ещё один object, который получает новое свйоство, а потом записывается в никуда. Правило конвертации простое:
  • если присвоили object - оставляем как есть
  • если присвоили undefined, кидаем exception
  • во всех прочих случаях - new Number(input), new String(input) или new Boolean(input)

вторник, 1 ноября 2011 г.

JavaScript: быстрый floor и приведение объектов

Чудесное от Михаила Барановского:

В JavaScript можно писать через степень:

var a = 120000; // make it shorter?
var a = 12e4;

var a = Math.floor(b);
// если b всегда > 0, можно сократить до:
var a = ~~b;

Легко получать текущую дату:
var date = +new Date;

Это работает, потому что + дёргает valueof.

var a = new Boolean(false); // how to quickly check if (a)?
alert(typeof a); // "object"
alert(!!a); // always true, because a is an object
alert(!!+a);
// right result, because "+" is calling valueOf
// method and convert result to number "0..1"
alert(a>0); // even shorter

понедельник, 31 октября 2011 г.

Lorem Ipsum

Lorem ipsum - это хорошо. Но прогресс не стоит на месте. И появляются всё новые вариации:

И картиночные (даст картинку нужного размера):

Checkbox в Chrome и Safari

В браузерах, основанных на WebKit, иногда портятся checkbox-ы. Девид Уолш описал проблему и дал великолепное решение:

.checkboxList li { /* ..or whatever the parent is */
  line-height: 20px;
}

В Safari есть WebKit!

В Safari обнаружен аналог Firebug из Firefox и WebKit из Chrome. По умолчанию отключён. Включать так:

Safari -> Preferences -> Advanced -> Show develop menu in menu bar

C#: Enum в ComboBox

Сбросить Enum в Combobox можно одной строкой:
comboBox.DataSource = Enum.GetValues(typeof(AnEnumType));

И выделяем:
comboBox.SelectedItem = AnEnumType.Value

пятница, 21 октября 2011 г.

JavaScript: быстрые циклы

length в JavaScript - штука медленная. Это очень хорошо видно по скорости выполнения циклов. Вовремя заменив for на while, можно получить выигрыш в производительности в 7 раз.

В умелых руках циклы JavaScript прекрасно заменяют друг друга. А значит, есть повод дополнить старый постинг о среднем арифметическом.

var digitRegEx=/^-?\d+([,\.]\d+)?$/g;
 
function arithmeticMean() {
  var len = arguments.length, i = len, finalSum = 0;
  if (!i)
    return 0;
  while (i--)
    if (digitRegEx.test(arguments[i]))
      finalSum += parseFloat(arguments[i]);
  return (len) ? finalSum / len : 0;
}

Кстати, как народная, так и более-менее проектная реализация всяких дополнительных функций для IE грешат for по lenght и ужасающе медленным trim(). Если руки дойдут - надо бу исправить.

вторник, 18 октября 2011 г.

JavaScript: быстрый парсинг числа

Как вы думаете, как быстрее парсить число с плавающей точкой - вот так:

function isNumber(n) {
    if (n == null) return null;
    var num_parsed = parseFloat(n);
    return (!isNaN(num_parsed) && isFinite(n)) ?  true : false;
}

или так (regExp немного исправлен по сравнению с примером с суммой, чтобы уважить сербов):

var digitRegEx=/^-?\d+([,\.](\d+)?)?$/g;

function isNumberRegExp(n) {
    if(digitRegEx.test(n))
 return true;
    else
 return false;
}

По идее, regExp должен работать медленней. А на самом деле скорость почти одинакова. Такие дела.

понедельник, 17 октября 2011 г.

CoffeeScript

Есть такая штука - CoffeeScript. Нечто вроде JavaScript, но в 2 раза короче:

JavaScript

var cubes, list, math, num, number, opposite, race, square;
var __slice = Array.prototype.slice;
number = 42;
opposite = true;
if (opposite) number = -42;
square = function(x) {
  return x * x;
};
list = [1, 2, 3, 4, 5];
math = {
  root: Math.sqrt,
  square: square,
  cube: function(x) {
    return x * square(x);
  }
};
race = function() {
  var runners, winner;
  winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
  return print(winner, runners);
};
if (typeof elvis !== "undefined" && elvis !== null) alert("I knew it!");
cubes = (function() {
  var _i, _len, _results;
  _results = [];
  for (_i = 0, _len = list.length; _i < _len; _i++) {
    num = list[_i];
    _results.push(math.cube(num));
  }
  return _results;
})();

CoffeeScript

# Assignment:
number   = 42
opposite = true

# Conditions:
number = -42 if opposite

# Functions:
square = (x) -> x * x

# Arrays:
list = [1, 2, 3, 4, 5]

# Objects:
math =
  root:   Math.sqrt
  square: square
  cube:   (x) -> x * square x

# Splats:
race = (winner, runners...) ->
  print winner, runners

# Existence:
alert "I knew it!" if elvis?

# Array comprehensions:
cubes = (math.cube num for num in list)
Википедия говорит, что в новом Ruby он сменит JavaScript. А пока, чтобы не пропало:

Продолжаем писать слайдер

Обещанный рассказ про скрипт для слайд-шоу.

Сначала определим классы для самого слайдера и для каждого изображения внутри.

function SimpleSlider(width, height, duration){
 this.type = "SimpleSlider";
 this.width = width;
 this.height = height;
 this.duration = (duration) ? duration : 1000;
 this.images = new Array(); //картиночки
 
 this.imgprefix = 'slider-img-';
 this.currimage = 0;
}
function SimpleSliderItem(url, width, height, itemid){
 this.type = "SimpleSliderItem";
 this.url = url;
 this.width = width;
 this.height = height;
 this.itemid = itemid;
}

Добавляем изображение (slider.addImage("..."), разумеется). Перед добавлением кэшируем его, создавая объект типа Image. Этот объект хорош разве что тем, что то, что попадает в его src, будет обязательно прокешировано. Как вставить его на страницу - загадка. Поэтому заполняем images нашим собственным типом и не забываем, что добавлять больше 9999 картинок - нельзя :):

SimpleSlider.prototype.addImage = function(url) {
    if(this.images.length >= 9999)
       return;
    //Caching
    var Image1= new Image(this.width,this.height);
    Image1.src = url;
   //Appending
    this.images.push(new SimpleSliderItem(url, this.width, this.height, this.images.length));
};

SimpleSlider.prototype.insertImage = function(id) {
    if (this.images.length > id){
  var imgElem = document.createElement('img'); 
  imgElem.setAttribute('id', this.imgprefix + id);
  imgElem.setAttribute('class', 'slider-block');
  
  imgElem.setAttribute('src', this.images[id].url);
   
  imgElem.setAttribute('style', 'width: ' + this.images[id].width + '; height: ' + this.images[id].height + '; z-index: ' + (this.images.length - id) + ';');
  return imgElem;
 } else {
  return null;
 }  
};

Вставить все, одно под одним:

SimpleSlider.prototype.insertImages = function(imgElem) {
 imgElem.setAttribute('style', 'width: '+this.width+'; height: ' + this.height + ';');
    for(var img in this.images)  
 {
  var elem = this.insertImage(img);
  if(elem)
   imgElem.appendChild(elem);
 }
};

И вот настало время сделать сам слайдер. Какое изображение следующее - он узнают из функции nextImageId(), а текущее - currImageId(). Если бы это был C#, мы бы оформили их как параметры класса.

В changeImage применяем jQuery. Сначала находим то, что надо и то, на что его менять - а потом меняем. Правда, возникает проблема: в 1-ой картинке z_index больше, чем в последней, и если сделать ей show, то она просто прыгнет наверх безо всякого fade. Поэтому мы ставим последней по счёту картинке z-Index в 9999 (поэтому добавлять можно не больше 9998 изображений), в currImage.fadeOut встроили небольшую callback функцию, которая возвращает z-Index обратно:

SimpleSlider.prototype.nextImageId = function() {
 if(!this.images.length)
  return null;
 this.currimage++;
 if(this.currimage >= this.images.length)
  this.currimage = 0;
 return "#" + this.imgprefix + this.currimage;
};

SimpleSlider.prototype.currImageId = function() {
 if(!this.images.length)
  return null;
 return "#" + this.imgprefix + this.currimage;
};

SimpleSlider.prototype.changeImage = function() {
 var currImage = $(slider.currImageId());
 var nextImage = $(slider.nextImageId());
 isLastItem = !this.currimage;
 if(isLastItem)
  currImage.css('z-Index', '9999');
 currImage.fadeOut(this.duration, function() {
   if(parseInt(currImage.css('z-Index')) == 9999)
    currImage.css('z-Index', '1');
  });
 nextImage.show();
};

пятница, 14 октября 2011 г.

JavaScript: переменные в RegExp

RegExp-переменные для string.replace() в JavaScript от автора известной мануалки.

  • $1: 1-ая группа
  • $99: 99-ая группа. Если группы 99 нет - 9-ая и 9. Если 9-ой нет - просто 99 долларов
  • $+: Группа с максимальным номером.
  • $&: Весь RegExp. Никакого $0 нет!!!
  • $` (backtick): Слева от RegExp-а.
  • $' (single quote): Справа от RegExp-а.
  • $_: Вся строка, на которую натравили RegExp

четверг, 13 октября 2011 г.

Плагины для Firefox

По мотивам списка плагинов Firefox для веб-разработки.

Мой выбор:

- Firebug
- Firecookie
- YSlow
- Web Developer
- ColorZilla
- iMacros - тестировать за нас будут роботы.
- SEO-шникам - гореть в аду.

JavaScript: concat для getElementsByTagName

Иногда гибкость JavaScript немного обманывает. Например, все знают, что getElementsByTagName возвращает вроде бы массив. И если нам нужно получить все input и textarea, то мы посмотрим в справке, что есть фунция concat и - склеим!

Увы, нас ждёт разочарование. То, что приходит - это набор, но не совсем массив. Его надо преобразовывать в Array и уже потом склеивать.

Как склеивать? Обычно рекомендуют Array.prototype.slice.call(document.getElementsByTagName('input'), 0) или немыслимое [].slice.call(document.getElementsByTagName('input'), 0). Это работает во всех браузерах, кроме... IE. IE считает, что COM-объект HTMLCollection вовсе не обзательно будет массивом. И бросает ошибку "JScript object expected.".

Поэтому мы расширим функцию из предыдущего примера ещё одной полезной тулзой.

function toArray(obj) {
    var array = [];
    for (var i = 0; obj.length && i < obj.length; i++)
      array[i] = obj[i];
    return array;
  }

  function setReadOnly(){
    var item = document.getElementsByTagName('input');
    var el, els = toArray(document.getElementsByTagName('input')).concat(
                      toArray(document.getElementsByTagName('textarea')));
   for(el in els)
     if(els[el].readOnly && typeof(els[el].className) != "undefined")
       els[el].className += ' readonly';
   }

JavaScript: Стиль для read-only

Как изменить стиль для input'ов, для которых выставлено readonly="readonly"? По идее, в css:

input[readonly="readonly"]
{
 color:  #707070;
 cursor: default; 
}
textarea[readonly="readonly"]
{
 color:  #707070;
 cursor: default; 
}

Это работает в Firefox, Opera, Chrome и, наверное, Safari. А вот в IE - нет :(.

Поэтому напишем код, который цепляется к OnLoad и будет работать только в IE (другим браузерам window.attachEvent и className неизвестны).

function setReadOnly(){
  var el, els = document.getElementsByTagName('input');
  for (el in els)
    if (els[el].readOnly && typeof(els[el].className) != "undefined")
      els[el].className += ' readonly';
}
if (window.attachEvent) 
  window.attachEvent('onload', setReadOnly);

А потом добавим в css обработку для этого класса:
.readonly
{
 color:  #707070;
 cursor: default; 
}

Не забудьте дописать в скрипт обработку для textarea.

C++: Размер массива, switch в одну строку и cдвиги

Размер массива в C/C++:

MyHugeStructure array[100];
int array_size = sizeof(array)/sizeof(*array);

for(int i = 0; i < array_size; i++) 
    array[i].id = i;

Следующие два варианта актуальны и для других языков.

Switch в одну строку с поддержкой всех типов:
str = number == 1 ? "one" : 
      number == 2 ? "two" :
      number == 3 ? "three" :
      number == 4 || rand() == 42 ? "four" :
      number == 5 ? "five" :
      "unknown number";

Стандарт C - штука нестрогая. Например, char - это не 1 байт, не 2 и не 4. char - это sizeof(char) и единственное ограничение: sizeof(char) < sizeof(int). Вспоминаем, что из себя представляют числа в памяти и поэтому НИКОГДА не пытаемся поделить на 2 через сдвиг отрицательное число.
//ТАК ДЕЛАТЬ НЕЛЬЗЯ!!
int a = -2;
printf("%dn", a >> 1);

Взято отсюда

среда, 12 октября 2011 г.

JavaScript: Переменное количество аргументов у функции

Внутри каждой функции JavaScript доступен массив arguments, в котором лежат все переданные аргументы. Это позволяет, например, посчитать среднее арифметическое для любого числа аргументов.

Пишем:

var digitRegEx=/^-?\d+([,\.]\d+)?$/g;

function arithmeticMean() {
  if (!arguments.length)
    return 0;
  var finalSum = 0;
  for (var i = 0; i < arguments.length; i++)
    if (digitRegEx.test(arguments[i]))
      finalSum += parseFloat(arguments[i]);
  return (arguments.length) ? finalSum / arguments.length : 0;
}

Считаем:

alert(arithmeticMean(1, 2, 3, 4, '5', 'this param is counted as 0');

А вот делать digitRegEx.compile() - не надо. В Opera 11 откомпилированные RegExp перестают работать.

вторник, 11 октября 2011 г.

JavaScript: Простое слайд-шоу

На главной странице сайта бюро переводов КонтактЧайна недавно появилось новое слайдшоу. Быстрое и настраивается легко. Написал его я. JavaScript и чуть-чуть jQuery.
После запуска нужно, чтобы функция changeImage() вызывалась раз за разом через определённые промежутки времени. В коде это выглядит так:

var slider = new SimpleSlider(640, 250);

function sliderTimer() {
 slider.changeImage();
 t=setTimeout("sliderTimer()",1000);
}
$(function(){
    slider.addImage("image1.jpg");
    slider.addImage("image2.jpg");
    slider.addImage("image3.jpg");
    slider.insertImages(document.getElementById("slider_simple"));
    sliderTimer();
});
Дальше я расскажу о том, как устроен этот скрипт изнутри.