Wycinanie szyku - Array slicing

W programowania , tablica krojenia jest operacją wyodrębnia podzbioru elementów z tablicy i pakiety je jako innej tablicy, ewentualnie w różnych wymiarach od oryginału.

Typowe przykłady dzielenia na tablice to wyodrębnianie podciągu z ciągu znaków, „ łokcia ” w „h ell o”, wyodrębnianie wiersza lub kolumny z dwuwymiarowej tablicy lub wyodrębnianie wektora z macierzy .

W zależności od języka programowania wycinek tablicy może składać się z elementów niekolejnych. Również w zależności od języka elementy nowej tablicy mogą być aliasowane (tj. Współdzielić pamięć) z elementami oryginalnej tablicy.

Detale

W przypadku tablic „jednowymiarowych” (jednowymiarowych) - wektorów, sekwencji, łańcuchów itd. - najczęściej wykonywaną operacją krojenia jest wyodrębnianie zera lub większej liczby kolejnych elementów. Tak więc, jeśli mamy wektor zawierający elementy (2, 5, 7, 3, 8, 6, 4, 1) i chcemy utworzyć wycinek tablicy od 3 do 6 pozycji, otrzymujemy (7, 3, 8, 6). W językach programowania, które używają schematu indeksowania opartego na 0, wycinek będzie miał indeks od 2 do 5 .

Zmniejszenie zakresu dowolnego indeksu do jednej wartości skutecznie eliminuje ten indeks. Tej funkcji można na przykład użyć do wyodrębnienia jednowymiarowych wycinków (wektorów: w 3D, rzędach, kolumnach i rurkach) lub dwuwymiarowych wycinków (prostokątnych macierzy) z trójwymiarowej tablicy. Jednakże, ponieważ zakres można określić w czasie wykonywania, języki ze sprawdzaniem typu mogą wymagać jawnej (w czasie kompilacji) notacji, aby faktycznie wyeliminować trywialne indeksy.

Ogólny podział tablicy można zaimplementować (niezależnie od tego, czy jest wbudowany w język, czy nie), odwołując się do każdej tablicy za pomocą wektora dope lub deskryptora  - rekordu zawierającego adres pierwszego elementu tablicy, a następnie zakres każdego indeksu i odpowiedni współczynnik formuła indeksowania. Technika ta umożliwia również natychmiastową transpozycję tablicy , odwrócenie indeksu, podpróbkowanie itp. W językach takich jak C , w których indeksy zawsze zaczynają się od zera, wektor dope macierzy z indeksami d ma co najmniej 1 + 2 d parametrów. W przypadku języków, które dopuszczają dowolne dolne granice indeksów, takich jak Pascal , wektor dope wymaga wpisów 1 + 3 d .

Jeśli abstrakcja tablicy nie obsługuje prawdziwie ujemnych indeksów (jak na przykład tablice Ady i Pascala ), wówczas ujemne indeksy dla granic wycinka dla danego wymiaru są czasami używane do określenia przesunięcia od końca tablicy w ten wymiar. W schematach opartych na 1, -1 ogólnie oznaczałoby przedostatnią pozycję, podczas gdy w systemie opartym na 0 oznaczałoby to ostatnią pozycję.

Historia

Koncepcja krojenia była z pewnością znana jeszcze przed wynalezieniem kompilatorów . Cięcie na plasterki jako funkcja języka prawdopodobnie zaczęło się od FORTRANU (1957), bardziej jako konsekwencja nieistniejącego sprawdzania typu i zakresu niż przez projekt. Do koncepcji wspomniano również we wstępnym raporcie dotyczącym IAL (ALGOL 58), ponieważ składnia pozwalała na pominięcie jednego lub więcej indeksów elementu tablicy (lub, w tym przypadku, wywołania procedury), gdy jest używany jako rzeczywisty parametr.

Kenneth Iverson „s APL (1957) miał bardzo elastyczny wielowymiarowe tablicy krojenie, co przyczyniło się znacznie do ekspresyjnej władzy i popularności języku użytkownika.

ALGOL 68 (1968) wprowadził wszechstronne wielowymiarowe funkcje cięcia i przycinania tablic.

Funkcje wycinania tablic zostały włączone do kilku nowoczesnych języków, takich jak Ada 2005 , Cobra , D , Fortran 90 , Go , Rust , Julia , MATLAB , Perl , Python , S-Lang , Windows PowerShell i języki matematyczne / statystyczne GNU Octave , S i R .

Oś czasu wycinania w różnych językach programowania

1966: Fortran 66

Programiści Fortran 66 byli w stanie wykorzystać tylko cięcie macierzy po wierszu, a następnie tylko podczas przekazywania tego wiersza do podprogramu :

      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

Wynik:

   2.000000       4.000000       8.000000

Zauważ, że w FORTRAN 66 nie ma wektora domieszki, dlatego długość wycinka musi być również przekazana jako argument - lub w inny sposób - do SUBROUTINE . Pascal i C w latach 70. mieli podobne ograniczenia.

1968: Algol 68

Raport końcowy Algol68 zawiera wczesny przykład krojenia, plastry są określone w postaci:

[lower bound:upper bound] ¢ for computers with extended character sets ¢

lub:

(LOWER BOUND..UPPER BOUND) # FOR COMPUTERS WITH ONLY 6 BIT CHARACTERS. #

Obie granice są włącznie i można je pominąć, w takim przypadku domyślnie odpowiadają zadeklarowanym granicom tablicy. Ani funkcja kroku, ani aliasy przekątnych nie są częścią poprawionego raportu.

Przykłady:

[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

Lata 70 .: MATLAB

> 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 / R

Tablice w S i GNU R są zawsze oparte na jedynkach, dlatego indeksy nowego wycinka będą rozpoczynać się od jednego dla każdego wymiaru, niezależnie od poprzednich indeksów. Wymiary o długości jednego zostaną pominięte (chyba że spadek = FAŁSZ). Nazwy wymiarów (jeśli są obecne) zostaną zachowane.

> 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: Fortran 77

Standard Fortran 77 wprowadził możliwość cięcia i łączenia ciągów:

PROGRAM MAIN
  PRINT *, 'ABCDE'(2:4)
END

Produkuje:

BCD

Takie łańcuchy mogłyby być przekazane przez odniesienie do innego podprogramu, długość byłby również przekazana w sposób przezroczysty do podprogramu jako rodzaj krótkiego wektora dope.

SUBROUTINE PRINT S(STR)
  CHARACTER *(*)STR
  PRINT *, STR
END

PROGRAM MAIN
  CALL PRINT S('ABCDE'(2:4))
END

Ponownie daje:

BCD

1979: Sinclair BASIC ZX80 / 81 / Spectrum

Standardowa pamięć ROM ZX80 / 81 / Spectrum oferuje BASIC z możliwością cięcia i łączenia ciągów:

w części polecenia (x DO y), która wskazuje potrzebny wycinek tablicy, wartość xiy można pominąć, podając znaczenie użycia wszystkich połączonych komórek tablicy (OD x do końca) lub (początek do y). W przypadku tablic wielowymiarowych cięcie na plasterki jest możliwe tylko w przypadku wymiaru ostatniego poziomu.

10 LET a$="ABCDE"(2 to 4)
20 PRINT a$

Produkuje:

BCD
10 LET a$="ABCDE"
20 LET b$=a$(4 TO)+a$(2 TO 3)+a$(1)
30 PRINT b$

Produkuje:

DEBCA

1983: Ada 83 i wyżej

Ada 83 obsługuje plasterki dla wszystkich typów tablic. Podobnie jak w Fortranie 77, takie tablice mogą być przekazywane przez odniesienie do innego podprogramu, długość byłaby również przekazywana w sposób przezroczysty do podprogramu jako rodzaj krótkiego wektora dope.

with Text_IO;
 
procedure Main is
   Text : String := "ABCDE";
begin
   Text_IO.Put_Line (Text (2 .. 4));
end Main;

Produkuje:

BCD

Uwaga: Ponieważ indeksy Ady są oparte na n, termin Text (2 .. 4) da w wyniku Array z indeksem podstawowym równym 2.

Definicja Text_IO.Put_Line jest następująca:

package Ada.Text_IO is
   
   procedure Put_Line(Item : in  String);

Definicja String jest następująca:

package Standard is

   subtype Positive is Integer range 1 .. Integer'Last;

   type String is array(Positive range <>) of Character;
   pragma Pack(String);

Ponieważ Ada obsługuje prawdziwie ujemne indeksy, ponieważ type History_Data_Array is array (-6000 .. 2010) of History_Data; nie nadaje specjalnego znaczenia indeksom ujemnym. W powyższym przykładzie termin Some_History_Data (-30 .. 30) przeciąłby okres History_Data od 31 rpne do 30 rne (ponieważ nie było roku zerowego, rok numer 0 w rzeczywistości odnosi się do 1 pne ).

1987: Perl

Jeśli mamy

@a = (2, 5, 7, 3, 8, 6, 4);

jak wyżej, to pierwsze 3 elementy, środkowe 3 elementy i ostatnie 3 elementy wyglądałyby następująco:

@a[0..2];   # (2, 5, 7)
@a[2..4];   # (7, 3, 8)
@a[-3..-1]; # (8, 6, 4)

Perl obsługuje indeksy list ujemnych. Indeks -1 to ostatni element, -2 przedostatni element itd. Ponadto Perl obsługuje wycinanie na podstawie wyrażeń, na przykład:

@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: Python

Jeśli masz następującą listę:

>>> nums = [1, 3, 5, 7, 8, 13, 20]

Następnie możliwe jest cięcie przy użyciu notacji podobnej do pobierania elementów:

>>> 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]

Zauważ, że Python zezwala na indeksy list ujemnych. Indeks -1 reprezentuje ostatni element, -2 przedostatni element, itd. Python dopuszcza również właściwość step poprzez dołączenie dodatkowego dwukropka i wartości. Na przykład:

>>> 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]

Składnia stride ( nums[1:5:2] ) została wprowadzona w drugiej połowie lat 90. XX wieku w wyniku postulatów wysuwanych przez użytkowników naukowych w Pythonie „matrix-SIG” (grupa o szczególnych zainteresowaniach).

Semantyka wycinka może się różnić w zależności od obiektu; nową semantykę można wprowadzić, gdy operator przeciąża operator indeksowania. W przypadku standardowych list Pythona (które są tablicami dynamicznymi ) każdy wycinek jest kopią. Z kolei wycinki tablic NumPy są widokami na ten sam podstawowy bufor.

1992: Fortran 90 i nowsze

W Fortranie 90 plasterki są określone w formularzu

lower_bound:upper_bound[:stride]

Obie granice są włącznie i można je pominąć, w takim przypadku domyślnie odpowiadają zadeklarowanym granicom tablicy. Stride domyślnie przyjmuje wartość 1. Przykład:

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

Każdy wymiar wartości tablicy w Analytica jest identyfikowany przez zmienną indeksu. Podczas tworzenia wycinków lub indeksów dolnych składnia identyfikuje wymiar (y), według których wykonujesz wycinanie lub indeksowanie przez nazewnictwo wymiaru. Jak na przykład:

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}

Nazywanie indeksów w wycinaniu i indeksowaniu jest podobne do nazywania parametrów w wywołaniach funkcji, zamiast polegać na ustalonej sekwencji parametrów. Jedną z zalet nazywania indeksów przy wycinaniu jest to, że programista nie musi pamiętać sekwencji indeksów w wielowymiarowej tablicy. Głębszą zaletą jest to, że wyrażenia uogólniają się automatycznie i bezpiecznie bez konieczności przepisywania, gdy zmienia się liczba wymiarów X.

1998: S-Lang

Cięcie tablic zostało wprowadzone w wersji 1.0. Wcześniejsze wersje nie obsługiwały tej funkcji.

Załóżmy, że A jest tablicą 1-wymiarową, taką jak

    A = [1:50];           % A = [1, 2, 3, ...49, 50]

Następnie tablicę B z pierwszych 5 elementów A można utworzyć za pomocą

    B = A[[:4]];

Podobnie, B można przypisać do tablicy ostatnich 5 elementów A poprzez:

    B = A[[-5:]];

Inne przykłady krojenia 1-w to:

    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

Cięcie tablic o wyższych wymiarach działa podobnie:

    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

Indeksy tablicowe mogą być również tablicami liczb całkowitych. Na przykład załóżmy, że I = [0:9] jest to tablica 10 liczb całkowitych. To A[I] jest odpowiednikiem tablicy pierwszych 10 elementów A . Praktycznym tego przykładem jest operacja sortowania, taka jak:

    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: D.

Rozważ tablicę:

int[] a = [2, 5, 7, 3, 8, 6, 4, 1];

Wyjmij z tego kawałek:

int[] b = a[2 .. 5];

a zawartość b będzie [7, 3, 8] . Pierwszy indeks wycinka jest włączający, drugi wyłączny.

auto c = a[$ - 4 .. $ - 2];

oznacza, że ​​tablica dynamiczna c zawiera teraz, [8, 6] ponieważ wewnątrz [] $ symbol odnosi się do długości tablicy.

D plasterki tablicy są aliasowane do oryginalnej tablicy, więc:

b[2] = 10;

oznacza, że a teraz ma zawartość [2, 5, 7, 3, 10, 6, 4, 1] . Aby utworzyć kopię danych tablicy zamiast aliasu, wykonaj:

auto b = a[2 .. 5].dup;

W przeciwieństwie do Pythona, granice wycinka D nie ulegają nasyceniu, więc kod odpowiadający temu kodowi w Pythonie jest błędem w D:

>>> d = [10, 20, 30]
>>> d[1 : 5]
[20, 30]

2004: SuperCollider

Język programowania SuperCollider implementuje pewne koncepcje z J / APL . Krojenie wygląda następująco:

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: ryby

Tablice w rybach są zawsze oparte na jedności, dlatego indeksy nowego wycinka będą rozpoczynać się od jedynki , niezależnie od poprzednich indeksów.

> 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

Cobra obsługuje krojenie w stylu Pythona. Jeśli masz listę

nums = [1, 3, 5, 7, 8, 13, 20]

wtedy pierwsze 3 elementy, środkowe 3 elementy, a ostatnie 3 elementy to:

nums[:3]   # equals [1, 3, 5]
nums[2:5]  # equals [5, 7, 8]
nums[-3:]  # equals [8, 13, 20]

Cobra obsługuje również składnię w stylu wycinania dla `` numerycznych pętli dla '':

for i in 2 : 5
    print i
# prints 2, 3, 4

for j in 3
    print j
# prints 0, 1, 2

2006: Windows PowerShell

Tablice w programie PowerShell są liczone od zera i można je definiować przy użyciu operatora przecinka:

PS  > $a = 2, 5, 7, 3, 8, 6, 4, 1
PS  > # Print the first two elements of $a:
PS  > "$($a[0, 1])"
2 5
PS  > # Take a slice out of it using the range operator:
PS  > "$($a[2..5])"
7 3 8 6
PS  > # Get the last 3 elements:
PS  > "$($a[-3..-1])"
6 4 1
PS  > # Return the content of the array in reverse order:
PS  > "$($a[($a.Length - 1)..0])" # Length is a property of System.Object[]
1 4 6 8 3 7 5 2

2009: Idź

Go obsługuje składnię w stylu Pythona do wycinania (z wyjątkiem ujemnych indeksów nie są obsługiwane). Tablice i plasterki można kroić. Jeśli masz kawałek

nums := []int{1, 3, 5, 7, 8, 13, 20}

wtedy pierwsze 3 elementy, środkowe 3 elementy, ostatnie 3 elementy i kopia całego wycinka wyglądałaby następująco:

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}

Plasterki w Go są typami odwołań, co oznacza, że ​​różne wycinki mogą odnosić się do tej samej podstawowej tablicy.

2010: Cilk Plus

Cilk Plus obsługuje składnię wycinania tablic jako rozszerzenie C i C ++.

array_base [lower_bound:length[:stride]]*

Krojenie Cilk Plus wygląda następująco:

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

Cięcie macierzy Cilk Plus różni się od Fortrana na dwa sposoby:

  • drugim parametrem jest długość (liczba elementów w wycinku) zamiast górnej granicy, aby zachować zgodność ze standardowymi bibliotekami C;
  • krojenie nigdy nie tworzy tymczasowego, a zatem nigdy nie musi przydzielać pamięci. Przypisania muszą się nie nakładać lub całkowicie nakładać, w przeciwnym razie wynik będzie niezdefiniowany.

2012: Julia

Krojenie tablicy Julii jest podobne do cięcia w MATLAB-u , ale używa nawiasów kwadratowych. Przykład:

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

Zobacz też

Bibliografia