Нарезка массива
В компьютерном программировании нарезка массива — это операция, которая извлекает подмножество элементов из массива и упаковывает их в другой массив, возможно, в другом измерении , чем исходный.
Типичными примерами среза массива являются извлечение подстроки из строки символов, « элл » в «привет о », извлечение строки или столбца из двумерного массива или извлечение вектора из матрицы .
В зависимости от языка программирования срез массива может состоять из непоследовательных элементов. Также, в зависимости от языка, элементы нового массива могут иметь псевдонимы (т. е. совместно использовать память) с элементами исходного массива.
Подробности
[ редактировать ]Для «одномерных» (одноиндексированных) массивов — векторов, последовательностей, строк и т. д. — наиболее распространенной операцией нарезки является извлечение нуля или более последовательных элементов. Таким образом, если у нас есть вектор, содержащий элементы (2, 5, 7, 3, 8, 6, 4, 1), и мы хотим создать срез массива с 3-го по 6-й элементы, мы получим (7, 3, 8, 6). В языках программирования , использующих схему индексации с отсчетом от 0, срез будет иметь индекс от 2 до 5 .
Сокращение диапазона любого индекса до одного значения фактически устраняет этот индекс. Эту функцию можно использовать, например, для извлечения одномерных срезов (векторов: в 3D, строк, столбцов и трубок). [1] ) или двумерные срезы (прямоугольные матрицы) трехмерного массива. Однако, поскольку диапазон может быть указан во время выполнения, языки с проверкой типов могут потребовать явную нотацию (во время компиляции), чтобы фактически исключить тривиальные индексы.
Общее разделение массива может быть реализовано (независимо от того, встроено оно в язык или нет) путем ссылки на каждый массив через вектор или дескриптор — запись, содержащую адрес первого элемента массива, а затем диапазон каждого индекса и соответствующий коэффициент в формула индексации. Этот метод также позволяет немедленно транспонировать массив , переворачивать индексы, выполнять подвыборку и т. д. Для таких языков, как C , где индексы всегда начинаются с нуля, вектор допинга массива с d индексами имеет как минимум 1 + 2 d параметров. Для языков, которые допускают произвольные нижние границы индексов, таких как Pascal , вектору соответствия требуется 1 + 3 d записей.
Если абстракция массива не поддерживает истинные отрицательные индексы (как это делают, например, массивы Ada и Pascal ), то отрицательные индексы для границ среза для данного измерения иногда используются для указания смещения от конца массива в это измерение. В схемах с отсчетом от 1, -1 обычно обозначает предпоследний элемент, тогда как в системе с отсчетом от 0 это будет означать самый последний элемент.
История
[ редактировать ]Концепция нарезки была наверняка известна еще до изобретения компиляторов . Нарезка как языковая функция, вероятно, началась с FORTRAN (1957), скорее из-за отсутствия проверки типов и диапазонов, чем из-за замысла. Эта концепция также упоминалась в предварительном отчете по IAL (АЛГОЛ 58) в том смысле, что синтаксис позволял опускать один или несколько индексов элемента массива (или, если уж на то пошло, вызова процедуры) при использовании в качестве фактического значения. параметр.
Кеннета Айверсона ( APL 1957) имел очень гибкую нарезку многомерных массивов, что во многом способствовало выразительной силе и популярности языка.
АЛГОЛ 68 (1968) представил комплексные функции среза и обрезки многомерных массивов.
Средства нарезки массивов включены в несколько современных языков, таких как Ada 2005 , Cobra , D , Fortran 90 , Go , Rust , Julia , MATLAB , Perl , Python , S-Lang , Windows PowerShell и математические/статистические языки GNU Octave . С и Р.
Хронология нарезки на различных языках программирования
[ редактировать ]1964: ПЛ/И
[ редактировать ]PL/I предоставляет две возможности для нарезки массива.
- Используя iSub DEFINING , можно объявить срез массива с использованием переменных iSUB для сопоставления определенных элементов «базового массива» с элементами «определенного массива». iSUB могут определять строки, столбцы, диагонали или сопоставления «многие к одному». [2] : стр. 212–213. В следующем примере определяется
Y
как одномерный срез, состоящий из диагональных элементов двумерного массиваX
.
DECLARE X(5,5);
DECLARE Y(5) DEFINED(X(1SUB,1SUB));
Ссылка на Y(2)
является ссылкой на X(2,2)
, и так далее.
- На срез массива, называемый поперечным сечением , можно указать, используя звездочку в качестве нижнего индекса для одного или нескольких измерений. Следующий код устанавливает все элементы в первом столбце
X
до нуля. В выражении с помощью звездочек можно указать один или несколько индексов. [2] : стр.43
DECLARE X(5,5);
X(*,1)=0;
1966: Фортран 66
[ редактировать ]Программисты на Фортране 66 могли воспользоваться преимуществом разрезания матриц только по строкам, и то только при передаче этой строки в подпрограмму :
SUBROUTINE PRINT V(VEC, LEN)
REAL VEC(*)
PRINT *, (VEC(I), I = 1, LEN)
END
PROGRAM MAIN
PARAMETER(LEN = 3)
REAL MATRIX(LEN, LEN)
DATA MATRIX/1, 1, 1, 2, 4, 8, 3, 9, 27/
CALL PRINT V(MATRIX(1, 2), LEN)
END
Результат:
2.000000 4.000000 8.000000
Обратите внимание, что в FORTRAN 66 нет вектора привязки , поэтому длина среза также должна быть передана в качестве аргумента (или каким-либо другим способом) в SUBROUTINE
. В 1970-х годах у Паскаля и Си были аналогичные ограничения.
1968: Алгол 68
[ редактировать ]Итоговый отчет Algol68 содержит ранний пример нарезки, срезы указаны в форме:
[lower bound:upper bound] ¢ for computers with extended character sets ¢
или:
(LOWER BOUND..UPPER BOUND) # FOR COMPUTERS WITH ONLY 6 BIT CHARACTERS. #
Обе границы являются инклюзивными и могут быть опущены, и в этом случае они по умолчанию будут соответствовать объявленным границам массива. Ни возможность шага, ни псевдонимы диагональных срезов не являются частью пересмотренного отчета.
Примеры:
[3, 3]real a := ((1, 1, 1), (2, 4, 8), (3, 9, 27)); # declaration of a variable matrix # [,] real c = ((1, 1, 1), (2, 4, 8), (3, 9, 27)); # constant matrix, the size is implied #
ref[]real row := a[2,]; # alias/ref to a row slice # ref[]real col2 = a[, 2]; # permanent alias/ref to second column #
print ((a[:, 2], newline)); # second column slice # print ((a[1⌈a, :], newline)); # last row slice # print ((a[:, 2⌈a], newline)); # last column slice # print ((a[:2, :2], newline)); # leading 2-by-2 submatrix "slice" #
+1.000010+0 +4.000010+0 +9.000010+0 +3.000010+0 +9.000010+0 +2.700010+1 +1.000010+0 +8.000010+0 +2.700010+1 +1.000010+0 +1.000010+0 +2.000010+0 +4.000010+0
1968: БАЗОВЫЙ
[ редактировать ]Системы HP 2000 , представленные в ноябре 1968 года, использовали HP Time-Shared BASIC в качестве основного интерфейса и языка программирования. Эта версия BASIC использовала нарезку для большинства операций по манипуляции со строками. Одной из странностей языка было то, что он допускал взаимозаменяемые круглые и квадратные скобки, и то, что использовалось на практике, обычно было функцией компьютерного терминала используемого .
Пример:
10 A$="HELLO, WORLD"
20 PRINT A$(1,5)
30 PRINT A$[7,11]
Будет производить:
HELLO WORLD
Системы HP широко использовались в начале 1970-х годов, особенно в технических школах и во многих небольших промышленных и научных учреждениях. [3] Когда в середине 1970-х годов появились первые микрокомпьютеры , HP также часто использовался в качестве образца для их диалектов BASIC. 1977 года Яркие примеры включают Apple BASIC 1978 года , Atari BASIC и Sinclair BASIC 1979 года . Этот стиль манипуляции обычно дает преимущества с точки зрения использования памяти и часто выбирается в системах с небольшим объемом памяти. Лишь диалект Синклера существенно отличался, используя TO
ключевое слово вместо списка, разделенного запятыми:
10 LET a$="ABCDE"(2 to 4)
20 PRINT a$
Срезирование также было выбрано в качестве основы для стандарта ANSI Full BASIC , в котором в качестве разделителя используется двоеточие и, таким образом, проводится различие между срезом и доступом к массиву:
10 DIM A$(5)
20 LET A$(2)="HELLO, WORLD"
30 PRINT A$(2)(1:5)
Хотя этот стиль доступа давал ряд преимуществ, особенно для небольших машин того времени, где-то после 1970 года корпорация Digital Equipment представила свой собственный вариант BASIC, в котором использовалась LEFT$
, RIGHT$
и MID$
строковые функции. Microsoft BASIC был написан на PDP-10 , и его BASIC использовался в качестве образца. В конце 1970-х годов оба стиля широко использовались, но к началу 1980-х годов функции в стиле DEC стали фактическим стандартом.
1970-е: МАТЛАБ
[ редактировать ]> A = round(rand(3, 4, 5)*10) % 3x4x5 three-dimensional or cubic array
> A(:, :, 3) % 3x4 two-dimensional array along first and second dimensions
ans =
8 3 5 7
8 9 1 4
4 4 2 5
> A(:, 2:3, 3) % 3x2 two-dimensional array along first and second dimensions
ans =
3 5
9 1
4 2
> A(2:end, :, 3) % 2x4 two-dimensional array using the 'end' keyword; works with GNU Octave 3.2.4
ans =
6 1 4 6
10 1 3 1
> A(1, :, 3) % single-dimension array along second dimension
ans =
8 3 5 7
> A(1, 2, 3) % single value
ans = 3
1976: С / Р
[ редактировать ]Массивы в S и GNU R всегда начинаются с единицы, поэтому индексы нового среза начинаются с единицы для каждого измерения, независимо от предыдущих индексов. Размеры с длиной, равной единице, будут отброшены (если drop = FALSE). Имена измерений (если они есть) будут сохранены.
> A <- array(1:60, dim = c(3, 4, 5)) # 3x4x5 three-dimensional or cubic array
> A[, , 3] # 3x4 two-dimensional array along first and second dimensions
[, 1] [, 2] [, 3] [, 4]
[1,] 25 28 31 34
[2,] 26 29 32 35
[3,] 27 30 33 36
> A[, 2:3, 3, drop = FALSE] # 3x2x1 cubic array subset (preserved dimensions)
, , 1
[, 1] [, 2]
[1,] 28 31
[2,] 29 32
[3,] 30 33
> A[, 2, 3] # single-dimension array along first dimension
[1] 28 29 30
> A[1, 2, 3] # single value
[1] 28
1977: Фортран 77
[ редактировать ]В стандарте Fortran 77 появилась возможность разрезать и объединять строки:
PROGRAM MAIN
PRINT *, 'ABCDE'(2:4)
END
Производит:
BCD
Такие строки могут передаваться по ссылке в другую подпрограмму, длина также будет прозрачно передаваться в подпрограмму как своего рода короткий вектор привязки.
SUBROUTINE PRINT S(STR)
CHARACTER *(*)STR
PRINT *, STR
END
PROGRAM MAIN
CALL PRINT S('ABCDE'(2:4))
END
Опять производит:
BCD
1983: Ада 83 и выше
[ редактировать ]
Ada 83 поддерживает срезы для всех типов массивов. Как и в Фортране 77, такие массивы могут передаваться по ссылке в другую подпрограмму, длина также будет прозрачно передаваться в подпрограмму как своего рода короткий вектор привязки.
with Text_IO;
procedure Main is
Text : String := "ABCDE";
begin
Text_IO.Put_Line (Text (2 .. 4));
end Main;
Производит:
BCD
Примечание. Поскольку в Ada индексы основаны на n, термин Text (2 .. 4)
приведет к массиву с базовым индексом 2.
Определение Text_IO.Put_Line
является:
package Ada.Text_IO is
procedure Put_Line(Item : in String);
Определение String
является:
package Standard is
subtype Positive is Integer range 1 .. Integer'Last;
type String is array(Positive range <>) of Character;
pragma Pack(String);
Поскольку Ада поддерживает истинно отрицательные индексы, как в type History_Data_Array is array (-6000 .. 2010) of History_Data;
он не придает особого значения отрицательным индексам. В примере выше термин Some_History_Data (-30 .. 30)
нарезал бы History_Data
с 31 г. до н. э. по 30 г. н. э. (поскольку нулевого года не было, номер года 0 фактически относится к 1 г. до н. э. ).
1987: Перл
[ редактировать ]Если у нас есть
@a = (2, 5, 7, 3, 8, 6, 4);
как указано выше, тогда первые 3 элемента, средние 3 элемента и последние 3 элемента будут:
@a[0..2]; # (2, 5, 7)
@a[2..4]; # (7, 3, 8)
@a[-3..-1]; # (8, 6, 4)
Perl поддерживает индексы отрицательных списков. Индекс -1 — это последний элемент, -2 — предпоследний элемент и т. д. Кроме того, Perl поддерживает нарезку на основе выражений, например:
@a[ 3.. $#a ]; # 4th element until the end (3, 8, 6, 4)
@a[ grep { !($_ % 3) } (0...$#a) ]; # 1st, 4th and 7th element (2,3,4)
@a[ grep { !(($_+1) % 3) } (0..$#a) ]; # every 3rd element (7,6)
1991: Питон
[ редактировать ]Если у вас есть следующий список:
>>> nums = [1, 3, 5, 7, 8, 13, 20]
Тогда можно выполнить срез, используя обозначение, аналогичное поиску элементов:
>>> nums[3] # no slicing
7
>>> nums[:3] # from index 0 (inclusive) until index 3 (exclusive)
[1, 3, 5]
>>> nums[1:5]
[3, 5, 7, 8]
>>> nums[-3:]
[8, 13, 20]
Обратите внимание, что Python допускает отрицательные индексы списков. Индекс -1 представляет последний элемент, -2 - предпоследний элемент и т. д. Python также позволяет использовать свойство шага, добавляя дополнительное двоеточие и значение. Например:
>>> nums[3:]
[7, 8, 13, 20]
>>> nums[3::] # == nums[3:]
[7, 8, 13, 20]
>>> nums[::3] # starting at index 0 and getting every third element
[1, 7, 20]
>>> nums[1:5:2] # from index 1 until index 5 and getting every second element
[3, 7]
Синтаксис шага ( nums[1:5:2]
) был представлен во второй половине 1990-х годов в результате запросов, выдвинутых научными пользователями Python «matrix-SIG» (группа особых интересов). [4]
Семантика среза потенциально различается для каждого объекта; новая семантика может быть введена при перегрузке оператора индексации. В стандартных списках Python (которые представляют собой динамические массивы ) каждый фрагмент является копией. Срезы массивов NumPy , напротив, представляют собой представления одного и того же базового буфера.
1992: Фортран 90 и выше
[ редактировать ]В Фортране 90 срезы задаются в форме
lower_bound:upper_bound[:stride]
Обе границы являются инклюзивными и могут быть опущены, и в этом случае они по умолчанию равны объявленным. границы массива. По умолчанию Stride равен 1. Пример:
real, dimension(m, n):: a ! declaration of a matrix
print *, a(:, 2) ! second column
print *, a(m, :) ! last row
print *, a(:10, :10) ! leading 10-by-10 submatrix
1994: Аналитика
[ редактировать ]Каждое измерение значения массива в Analytica идентифицируется индексной переменной. При нарезке или индексации синтаксис идентифицирует измерения, по которым вы нарезаете или индексируете, путем присвоения имени измерению. Такой как:
Index I := 1..5 { Definition of a numerical Index } Index J := ['A', 'B', 'C'] { Definition of a text-valued Index } Variable X := Array(I, J, [[10, 20, 30], [1, 2, 3], ....]) { Definition of a 2D value } X[I = 1, J = 'B'] -> 20 { Subscript to obtain a single value } X[I = 1] -> Array(J, [10, 20, 30]) { Slice out a 1D array. } X[J = 2] -> Array(I, [20, 2, ....]) { Slice out a 1D array over the other dimension. } X[I = 1..3] {Slice out first four elements over I with all elements over J}
Именование индексов при срезе и индексировании аналогично именованию параметров в вызовах функций, вместо того, чтобы полагаться на фиксированную последовательность параметров. Одним из преимуществ именования индексов при нарезке является то, что программисту не нужно запоминать последовательность индексов в многомерном массиве. Более глубокое преимущество заключается в том, что выражения обобщаются автоматически и безопасно, не требуя перезаписи при изменении количества измерений X.
1998: С-Ланг
[ редактировать ]Срез массива был представлен в версии 1.0. В более ранних версиях этого не было поддержите эту функцию.
Предположим, что A представляет собой одномерный массив, такой как
A = [1:50]; % A = [1, 2, 3, ...49, 50]
Затем массив B из первых 5 элементов A может быть создан с помощью
B = A[[:4]];
Аналогично, B может быть присвоен массиву из последних 5 элементов A с помощью:
B = A[[-5:]];
Другие примеры одномерной нарезки включают в себя:
A[-1] % The last element of A A[*] % All elements of A A[[::2]] % All even elements of A A[[1::2]] % All odd elements of A A[[-1::-2]] % All even elements in the reversed order A[[[0:3], [10:14]]] % Elements 0-3 and 10-14
Нарезка многомерных массивов работает аналогично:
A[-1, *] % The last row of A A[[1:5], [2:7]] % 2d array using rows 1-5 and columns 2-7 A[[5:1:-1], [2:7]] % Same as above except the rows are reversed
Индексы массива также могут быть массивами целых чисел. Например, предположим
что I = [0:9]
представляет собой массив из 10 целых чисел. Затем
A[I]
эквивалентен массиву из первых 10 элементов
из A
. Практический пример — сортировка
такие операции, как:
I = array_sort(A); % Obtain a list of sort indices B = A[I]; % B is the sorted version of A C = A[array_sort(A)]; % Same as above but more concise.
1999: Д
[ редактировать ]Рассмотрим массив:
int[] a = [2, 5, 7, 3, 8, 6, 4, 1];
Выделим из этого кусочек:
int[] b = a[2 .. 5];
и содержание b
будет [7, 3, 8]
. Первый индекс среза является инклюзивным, второй — исключающим.
auto c = a[$ - 4 .. $ - 2];
означает, что динамический массив c
теперь содержит [8, 6]
потому что внутри [] $
Символ относится к длине массива.
Срезы массива D имеют псевдонимы исходного массива, поэтому:
b[2] = 10;
означает, что a
теперь есть содержимое [2, 5, 7, 3, 10, 6, 4, 1]
. Чтобы создать копию данных массива, а не только псевдоним, выполните:
auto b = a[2 .. 5].dup;
В отличие от Python, границы среза D не насыщаются, поэтому код, эквивалентный этому коду Python, является ошибкой в D:
>>> d = [10, 20, 30]
>>> d[1 : 5]
[20, 30]
2004: Суперколлайдер
[ редактировать ]Язык программирования SuperCollider реализует некоторые концепции из J / APL . Нарезка выглядит следующим образом:
a = [3, 1, 5, 7] // assign an array to the variable a
a[0..1] // return the first two elements of a
a[..1] // return the first two elements of a: the zero can be omitted
a[2..] // return the element 3 till last one
a[[0, 3]] // return the first and the fourth element of a
a[[0, 3]] = [100, 200] // replace the first and the fourth element of a
a[2..] = [100, 200] // replace the two last elements of a
// assign a multidimensional array to the variable a
a = [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]];
a.slice(2, 3); // take a slice with coordinates 2 and 3 (returns 13)
a.slice(nil, 3); // take an orthogonal slice (returns [3, 8, 13, 18])
2005: рыба
[ редактировать ]Массивы в fish всегда начинаются с единицы, поэтому индексы нового среза будут начинаться с единицы , независимо от предыдущих индексов.
> set A (seq 3 2 11) # $A is an array with the values 3, 5, 7, 9, 11
> echo $A[(seq 2)] # Print the first two elements of $A
3 5
> set B $A[1 2] # $B contains the first and second element of $A, i.e. 3, 5
> set -e A[$B]; echo $A # Erase the third and fifth elements of $A, print $A
3 5 9
2006: Кобра
[ редактировать ]Cobra поддерживает нарезку в стиле Python. Если у вас есть список
nums = [1, 3, 5, 7, 8, 13, 20]
тогда первые 3 элемента, средние 3 элемента и последние 3 элемента будут:
nums[:3] # equals [1, 3, 5]
nums[2:5] # equals [5, 7, 8]
nums[-3:] # equals [8, 13, 20]
Cobra также поддерживает синтаксис в стиле срезов для «числовых циклов for»:
for i in 2 : 5
print i
# prints 2, 3, 4
for j in 3
print j
# prints 0, 1, 2
2006: Windows PowerShell
[ редактировать ]В PowerShell массивы начинаются с нуля и могут быть определены с помощью оператора запятой:
PS> $a = 2, 5, 7, 3, 8, 6, 4, 1
PS> # Print the first two elements of $a:
PS> Write-Host -NoNewline $a[0, 1]
2 5
PS> # Take a slice out of it using the range operator:
PS> Write-Host -NoNewline $a[2..5]
7 3 8 6
PS> # Get the last 3 elements:
PS> Write-Host -NoNewline $a[-3..-1]
6 4 1
PS> # Return the content of the array in reverse order:
PS> Write-Host -NoNewline $a[($a.Length - 1)..0] # Length is a property of System.Object[]
1 4 6 8 3 7 5 2
2009: Вперёд
[ редактировать ]Go поддерживает синтаксис в стиле Python для нарезки (за исключением того, что отрицательные индексы не поддерживаются). Массивы и срезы можно нарезать. Если у тебя есть кусочек
nums := []int{1, 3, 5, 7, 8, 13, 20}
тогда первые 3 элемента, средние 3 элемента, последние 3 элемента и копия всего среза будут такими:
nums[:3] // equals []int{1, 3, 5}
nums[2:5] // equals []int{5, 7, 8}
nums[4:] // equals []int{8, 13, 20}
nums[:] // equals []int{1, 3, 5, 7, 8, 13, 20}
Срезы в Go являются ссылочными типами, что означает, что разные срезы могут ссылаться на один и тот же базовый массив.
2010: Силк Плюс
[ редактировать ]Cilk Plus поддерживает синтаксис для нарезки массивов как расширение C и C++.
array_base [lower_bound:length[:stride]]*
Нарезка Cilk Plus выглядит следующим образом:
A[:] // All of vector A
B[2:6] // Elements 2 to 7 of vector B
C[:][5] // Column 5 of matrix C
D[0:3:2] // Elements 0, 2, 4 of vector D
Нарезка массива в Cilk Plus отличается от нарезки в Fortran двумя способами:
- второй параметр — это длина (количество элементов в срезе) вместо верхней границы, чтобы соответствовать стандартным библиотекам C;
- нарезка никогда не создает временных данных и, следовательно, никогда не требует выделения памяти. Присвоения должны быть либо непересекающимися, либо полностью перекрывающимися, в противном случае результат не определен.
2012: Юлия
[ редактировать ]Нарезка массива Julia аналогична нарезке MATLAB , но использует квадратные скобки. Пример:
julia> x = rand(4, 3)
4x3 Array{Float64,2}:
0.323877 0.186253 0.600605
0.404664 0.894781 0.0955007
0.223562 0.18859 0.120011
0.149316 0.779823 0.0690126
julia> x[:, 2] # get the second column.
4-element Array{Float64,1}:
0.186253
0.894781
0.18859
0.779823
julia> x[1, :] # get the first row.
1x3 Array{Float64,2}:
0.323877 0.186253 0.600605
julia> x[1:2,2:3] # get the submatrix spanning rows 1,2 and columns 2,3
2x2 Array{Float64,2}:
0.186253 0.600605
0.894781 0.0955007
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Чжан, Земин; Аэрон, Щучин (15 марта 2017 г.). «Точное тензорное завершение с использованием t-SVD» . Транзакции IEEE по обработке сигналов . 65 (6). Институт инженеров по электротехнике и электронике (IEEE): 1511–1526. arXiv : 1502.04689 . Бибкод : 2017ITSP...65.1511Z . дои : 10.1109/tsp.2016.2639466 . ISSN 1053-587X .
- ^ Jump up to: а б Корпорация IBM (1995). Справочник по языкам PL/I для MVS и VM .
- ^ «Прохождение 10-летнего рубежа» . Журнал МЕРА . Хьюлетт Паккард. Октябрь 1976 года.
- ^ Миллман, К. Джаррод; Айвазис, Михаил (2011). «Python для ученых и инженеров» . Вычисления в науке и технике . 13 (2): 9–12. Бибкод : 2011CSE....13b...9M . дои : 10.1109/MCSE.2011.36 .