2015-09-17 7 views
9

Numpy verfügt in seinem Array-Zugriffsoperator über eine ausgefeilte Indizierung/Slicing/Stepping-Funktionalität. Siehe auch: http://docs.scipy.org/doc/numpy/reference/arrays.indexing.htmlReplizieren von Numpys Advanced Indexing/Slicing in Haskell

Während ich mit Haskell experimentierte, dachte ich, es wäre lehrreich, eine Teilmenge dieser Indizierungsfunktionalität zu replizieren. Insbesondere ist es „Tupel Auswahlobjekte“ oder n-dimensionale Projektion“(https://en.wikipedia.org/wiki/Projection_%28relational_algebra%29)

Grundsätzlich können Sie tun:.

two_d_array[0:1:1, 0:4:2] 

Und dies wird Ihnen die erste Zeile trat um 1 die ersten 2 Spalten mit abgestuften von 2 (Überspringen 1 Spalte).

mit anderen Worten kann es einen originellen 2-dimensionalen Array in einen kleineren 2-dimensionalen Array projiziert. Das Ergebnis bleibt als 2-dimensionales Array.

Also hier ist, was habe ich versucht, in Haskell

Die Art einer solchen Funktion sollte so etwas wie:

(!!!) :: (Functor f) => f a -> [(Int, Int, Int)] -> f a 

So sehen Sie könnte so etwas wie:

three_d_tensor !!! [(s1,e1,st1), (s2,e2,st2), (s3,e3,st3)] 

Wo sx, ex, STX Start sind, Ende, Schritt sind.

Das Beispiel sollte die ursprüngliche Tensor in einen kleineren Tensor, mit der ersten Dimension von s1 to e1, stepping by st1, zweiter Dimension von s2 to e2, stepping by st2 beschränkt beschränkt Projekt ... usw.

Also hier ist, was ich habe:

slicing from to xs = take (to - from + 1) (drop from xs) 

stepping n = map head . takeWhile (not . null) . iterate (drop n) 

(!!!) tensor ((start, end, step):tail) = 
    project (stepSlice start end step tensor) tail map 
    where 
     project tensor ((start, end, step):tail) projection = 
      project (projection (stepSlice start end step) tensor) tail (projection . map) 
     project tensor [] _ = 
      tensor 
     stepSlice start end step tensor = 
      ((stepping step) . (slicing start end)) tensor 

Das obige funktioniert aufgrund eines Problems der "polymorphen Rekursion" nicht. Grundsätzlich kann ich nicht unendlich die map Funktion verfassen, der spezifische Ausdruck, der das tut, ist das (projection . map). Wenn diese Art von polymorpher Rekursion möglich wäre, glaube ich, dass es funktionieren würde. Aber ich bin offen für alternative Implementierungen, die keine polymorphe Rekursion beinhalten.

Ich habe dieses Problem untersucht und haben immer noch zu kurz kommt:

+0

_Tensors sind nicht functors_ (verschachtelt oder nicht). Ja, es funktioniert manchmal so, so zu tun, und [linear] (http://hackage.haskell.org/package/linear) tut es, aber das ist eine Sache, wo EKmett es falsch gemacht hat, IMO. - Benötigen Sie wirklich Laufzeit-variable Ränge? – leftaroundabout

+1

Beachten Sie, dass Sie ** polymorphe Rekursion ** verwenden können, vorausgesetzt, dass 1) der Typ, den die Funktion haben würde, tatsächlich syntaktisch ausdrückbar ist und 2) Sie eine explizite Typ-Signatur bereitstellen. – Bakuriu

+0

Ich weiß nicht, ob sich das auf beide bezieht, aber sie scheinen ziemlich interessant zu sein: https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/syntax-extns.html#generalised-list-comprehensions und https://hackage.haskell.org/package/DSH – dfeuer

Antwort

6

Es gibt bereits eine Art für aus bestehenden Werten neue Werte Computing - Funktionen . Angenommen, wir haben eine Funktion, die in eine Struktur indiziert, können wir sie verwenden, um die Struktur zu indexieren, indem wir sie auf die Struktur anwenden.

(!!!) = flip ($) 

infixr 2 !!! 

Wenn wir eine Funktion, die Indizes der Struktur und eine andere Funktion haben, die Indizes alle verschachtelten Strukturen wir sie zusammen durch fmap ing die zweite Funktion über die Struktur zusammensetzen kann dann die äußere Funktion anwenden.

(!.) :: Functor f => (f b -> g c) -> (a -> b) -> f a -> g c 
t !. f = t . fmap f 

infixr 5 !. 

mit einem Beispiel 3D-Struktur

three_d_tensor :: [[[(Int,Int,Int)]]] 
three_d_tensor = [[[(x, y, z) | z <- [0..4]] | y <- [0..3]] | x <- [0..2]] 

Wir können eine aufwendige Scheibe sehen es mit Ihren Slicing-Funktionen gebaut slicing und stepping.

example = three_d_tensor !!! slicing 1 2 !. stepping 2 !. stepping 2 . slicing 1 3 

was dazu führt,

[[[(1,0,1),(1,0,3)],[(1,2,1),(1,2,3)]],[[(2,0,1),(2,0,3)],[(2,2,1),(2,2,3)]]] 
+0

Wow. Das funktioniert wirklich großartig. Sie erstellen alle Dimensionsprojektionen zusammen '! .' und wenden sie dann auf einmal mit' !!! 'an. Ich habe die Parallele mit der normalen Komposition "(fb -> fc) -> (fa -> fb) -> fa -> fc" ähnlich wie nur "(.) :: (b -> c) -> (a -> b) -> a -> c. Aber stattdessen "nistet" sich die Komposition in den Inhalt des Funktors ein. Ich habe auch bemerkt, dass du 'g' benutzt hast, um eine Änderung im Funktor selbst zu erlauben? Wie heißt diese Art von Transformation? "Verschachtelte Zusammensetzung?" – CMCDragonkai

+0

Der Hauptunterschied war, dass meine ursprüngliche Absicht war, eine Liste von Projektionen zu nehmen, was den Vorteil hat, beliebige Projektionen zu erlauben, die durch irgendeinen Laufzeitwert bestimmt sind. Die aktuelle Form kann nur Projektionen haben, die zur Kompilierzeit bestimmt werden. Aber Laufzeit Metaprogrammierung oder Typklasse Hacker oder abhängige Typen können listenbasierte Projektionen erlauben. – CMCDragonkai

+0

Sie können Funktionen zur Laufzeit erstellen; Das ist es, was Partial Application und Lambdas tun. Der einzige Teil davon, der zur Kompilierzeit erledigt werden muss, besteht darin, zu überprüfen, ob die Projektion korrekt ist. – Cirdec