Haskell - Tái tạo lại hình ảnh của Numpy


8

Vào Haskell, tôi đang cố gắng tái tạo một cái gì đó như định hình lại của numpy với các danh sách. Cụ thể, đưa ra một danh sách phẳng, định hình lại nó thành một danh sách n chiều:

import numpy as np

a = np.arange(1, 18)
b = a.reshape([-1, 2, 3])

# b = 
# 
# array([[[ 1,  2,  3],
#         [ 4,  5,  6]],
# 
#        [[ 7,  8,  9],
#         [10, 11, 12]],
# 
#        [[13, 14, 15],
#         [16, 17, 18]]])

Tôi đã có thể tái tạo hành vi với các chỉ số cố định, ví dụ:

*Main> reshape23 [1..18]
[[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]],[[13,14,15],[16,17,18]]]

Mã của tôi là:

takeWithRemainder :: (Integral n) => n -> [a] -> ([a], [a])
takeWithRemainder _ [] = ([], [])
takeWithRemainder 0 xs = ([], xs)
takeWithRemainder n (x:xs) = (x : taken, remaining)
                                where (taken, remaining) = takeWithRemainder (n-1) xs

chunks :: (Integral n) => n -> [a] -> [[a]]
chunks _ [] = []
chunks chunkSize xs = chunk : chunks chunkSize remainderOfList
                        where (chunk, remainderOfList) = takeWithRemainder chunkSize xs

reshape23 = chunks 2 . chunks 3

Bây giờ, tôi dường như không thể tìm ra cách để khái quát hóa điều này thành một hình dạng tùy ý. Ý tưởng ban đầu của tôi là thực hiện một lần:

reshape :: (Integral n) => [n] -> [a] -> [b]
reshape ns list = foldr (\n acc -> (chunks n) . acc) id ns list

Nhưng, bất kể tôi đi như thế nào, tôi luôn gặp lỗi loại từ trình biên dịch. Theo hiểu biết của tôi, vấn đề là tại một thời điểm nào đó, loại accđược suy ra idlà nghĩa của a -> anó, và nó không giống như thực tế là danh sách các hàm trong nếp gấp đều có loại khác nhau (mặc dù tương thích với bố cục) Chữ ký. Tôi gặp vấn đề tương tự khi cố gắng thực hiện điều này với đệ quy thay vì một lần. Đây nhầm lẫn tôi vì ban đầu tôi đã có ý định cho [b]trong reshape's chữ ký kiểu là một stand-in cho 'khác, loại phân ly' đó có thể là bất cứ điều gì từ [[a]]đến [[[[[a]]]]].

Làm thế nào tôi đi sai về điều này? Có cách nào để thực sự đạt được hành vi mà tôi dự định, hoặc chỉ đơn giản là sai khi muốn loại hành vi "năng động" này ngay từ đầu?

Câu trả lời:


10

Có hai chi tiết ở đây khác biệt về chất với Python, cuối cùng xuất phát từ kiểu gõ động so với kiểu gõ tĩnh.

Điều đầu tiên bạn nhận thấy: ở mỗi bước phân loại, loại kết quả khác với loại đầu vào. Điều này có nghĩa là bạn không thể sử dụng foldr, vì nó mong đợi một chức năng của một loại cụ thể. Bạn có thể làm điều đó thông qua đệ quy mặc dù.

Vấn đề thứ hai ít rõ ràng hơn: kiểu trả về của reshapehàm của bạn phụ thuộc vào đối số thứ nhất là gì. Giống như, nếu đối số đầu tiên là [2], kiểu trả về là [[a]], nhưng nếu đối số đầu tiên là [2, 3], thì kiểu trả về là [[[a]]]. Trong Haskell, tất cả các loại phải được biết tại thời điểm biên dịch. Và điều này có nghĩa là reshapehàm của bạn không thể lấy đối số đầu tiên được xác định khi chạy. Nói cách khác, đối số đầu tiên phải ở cấp độ loại.

Các giá trị cấp độ loại có thể được tính toán thông qua các hàm loại (còn gọi là "họ kiểu"), nhưng vì đó không chỉ là loại (tức là bạn cũng có một giá trị để tính toán), cơ chế tự nhiên (hoặc duy nhất?) Cho đó là một loại lớp học.

Vì vậy, trước tiên hãy xác định lớp loại của chúng tôi:

class Reshape (dimensions :: [Nat]) from to | dimensions from -> to where
    reshape :: from -> to

Lớp này có ba tham số: dimensionsloại [Nat]là một mảng số loại, đại diện cho kích thước mong muốn. fromlà loại đối số và tolà loại kết quả. Lưu ý rằng, mặc dù được biết rằng loại đối số luôn luôn [a], chúng ta phải có nó làm biến kiểu ở đây, vì nếu không, các thể hiện lớp của chúng ta sẽ không thể khớp chính xác agiữa đối số và kết quả.

Thêm vào đó, lớp có một phụ thuộc chức năng dimensions from -> tođể chỉ ra rằng nếu tôi biết cả hai dimensionsfrom, tôi có thể xác định rõ ràng to.

Tiếp theo, trường hợp cơ sở: khi dimentionslà một danh sách trống, hàm chỉ xuống cấp thành id:

instance Reshape '[] [a] [a] where
    reshape = id

Và bây giờ là thịt: trường hợp đệ quy.

instance (KnownNat n, Reshape tail [a] [b]) => Reshape (n:tail) [a] [[b]] where
    reshape = chunksOf n . reshape @tail
        where n = fromInteger . natVal $ Proxy @n

Đầu tiên, nó thực hiện cuộc gọi đệ quy reshape @tailđể tách ra kích thước trước đó, và sau đó nó thực hiện kết quả của việc sử dụng giá trị của kích thước hiện tại làm kích thước khối.

Cũng lưu ý rằng tôi đang sử dụng chunksOfchức năng từ thư việnsplit . Không cần phải xác định lại chính mình.

Hãy thử nghiệm xem:

λ reshape @ '[1] [1,2,3]          
[[1],[2],[3]]                     

λ reshape @ '[1,2] [1,2,3,4]        
[[[1,2]],[[3,4]]]                   

λ reshape @ '[2,3] [1..12]              
[[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]

λ reshape @ '[2,3,4] [1..24]                                                      
[[[[1,2,3,4],[5,6,7,8],[9,10,11,12]],[[13,14,15,16],[17,18,19,20],[21,22,23,24]]]]

Để tham khảo, đây là chương trình đầy đủ với tất cả nhập khẩu và tiện ích mở rộng:

{-# LANGUAGE
    MultiParamTypeClasses, FunctionalDependencies, TypeApplications,
    ScopedTypeVariables, DataKinds, TypeOperators, KindSignatures,
    FlexibleInstances, FlexibleContexts, UndecidableInstances,
    AllowAmbiguousTypes
#-}

import Data.Proxy (Proxy(..))
import Data.List.Split (chunksOf)
import GHC.TypeLits (Nat, KnownNat, natVal)

class Reshape (dimensions :: [Nat]) from to | dimensions from -> to where
    reshape :: from -> to

instance Reshape '[] [a] [a] where
    reshape = id

instance (KnownNat n, Reshape tail [a] [b]) => Reshape (n:tail) [a] [[b]] where
    reshape = chunksOf n . reshape @tail
        where n = fromInteger . natVal $ Proxy @n

Là gì (..)gia import Data.Proxy (Proxy(..))?
Micha Wiedenmann

@MichaWiedenmann (..)có nghĩa là nhập tất cả các hàm tạo kiểu dữ liệu và có thể các trường bản ghi. Vì Proxychỉ có một hàm tạo nên nó tương đương vớiProxy(Proxy))
lehins

6

@Fyodor Câu trả lời của Soikin là hoàn hảo đối với câu hỏi thực tế. Ngoại trừ có một chút vấn đề với chính câu hỏi. Danh sách các danh sách không giống như một mảng. Đó là một quan niệm sai lầm phổ biến rằng Haskell không có mảng và bạn buộc phải xử lý các danh sách, điều không thể tiếp tục từ sự thật.

Bởi vì câu hỏi được gắn thẻ arrayvà có so sánh với numpy, tôi muốn thêm một câu trả lời thích hợp xử lý tình huống này cho các mảng nhiều chiều. Có một vài thư viện mảng trong hệ sinh thái Haskell, một trong số đó làmassiv

Một reshapechức năng tương tự numpycó thể đạt được bằng resize'chức năng:

λ> 1 ... (18 :: Int)
Array D Seq (Sz1 18)
  [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 ]
λ> resize' (Sz (3 :> 2 :. 3)) (1 ... (18 :: Int))
Array D Seq (Sz (3 :> 2 :. 3))
  [ [ [ 1, 2, 3 ]
    , [ 4, 5, 6 ]
    ]
  , [ [ 7, 8, 9 ]
    , [ 10, 11, 12 ]
    ]
  , [ [ 13, 14, 15 ]
    , [ 16, 17, 18 ]
    ]
  ]

2
Lưu ý rằng ở đây, giống như trong câu trả lời của tôi, đối số kích thước nằm ở cấp độ loại
Fyodor Soikin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.