Haskell: как сгенерировать декартово произведение двух простых алгебраических типов данных

Я изучаю Haskell, поэтому пишу несколько простых карточных игр. Я определил некоторые типы данных:

data Rank = Ace|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|Jack|Queen|King deriving (Eq,Show,Ord)

data Suit = Hearts|Spades|Diamonds|Clubs deriving (Show)

data Card = Card Rank Suit 

Теперь я хотел бы создать чистую колоду из 52 карт. Я уверен, что есть простой способ сделать это, но все, что я могу придумать, это:

 pristineDeck = [Card Ace Hearts, Card Two Hearts, ...]

Могу ли я заставить Haskell сгенерировать этот список для меня?


person Tony K.    schedule 04.01.2013    source источник
comment
Сделайте ваши типы производными от класса типов Enum (это может произойти, просто поместив Enum рядом с Show там вверху). Три из них: ранг, масть и карта.   -  person Alp Mestanogullari    schedule 05.01.2013
comment
P.S. то, что вы ищете, - это не перекрестное произведение, а нечто, связанное с трехмерными векторами. Вы, вероятно, имели в виду декартово произведение.   -  person Ben Millwood    schedule 05.01.2013
comment
@BenMillwood Мой плохой ... перекрестное соединение SQL + декартово произведение + степень по физике   -  person Tony K.    schedule 05.01.2013
comment
@BenMillwood: А еще лучше перестраховаться и быть как математики - просто называть все продуктом и полагаться на контекст для устранения неоднозначности. Может быть, назовите это тензорным продуктом, если вы чувствуете себя щедрым.   -  person C. A. McCann    schedule 05.01.2013
comment
@C.A.McCann: У вас есть сумма? Вместо этого назовите это побочным продуктом!   -  person    schedule 05.01.2013


Ответы (3)


Понимание списков — очень удобный синтаксис для этого. Если вы выведете Enum из Rank и Suit, вы можете выразить это очень просто:

pristineDeck = [ Card rank suit | suit <- [Hearts .. Clubs], rank <- [Ace .. King] ]

Если вам интересно, почему у меня suit и rank в разном порядке, первое из-за порядка, который использует конструктор Card, а второе — получить порядок результирующего списка — костюмы вместе в порядке возрастания.

В более общем смысле, или когда понимание одного списка становится слишком громоздким, декартово произведение — это именно то поведение, которое задается экземпляром Monad для списков. Следующее эквивалентно приведенному выше пониманию списка:

pristineDeck = do suit <- [Hearts .. Clubs]
                  rank <- [Ace .. King]
                  return $ Card rank suit

В качестве еще одного второстепенного момента, чтобы избавить себя от необходимости помнить, в каком порядке находятся значения Suit, получение Bounded также позволит написать [minBound .. maxBound] для перечисления всех значений любого типа с экземплярами как Enum, так и Bounded.

person C. A. McCann    schedule 04.01.2013
comment
Лично я бы не стал выводить Enum для типа Suit, а просто выписал полный список в понимание. Костюмы не имеют естественного порядка, ИМО, поэтому синтаксис с многоточием сбивает с толку. - person Ben Millwood; 05.01.2013
comment
И я бы также получил экземпляр Bounded. [minBound .. maxBound] дает понять, что вы перечисляете все варианты, даже если нет естественного порядка. - person Roman Cheplyaka; 05.01.2013
comment
@К.А. McCann Будьте осторожны при вводе [Hearts .. Clubs] и [Ace .. King], иначе вы получите сообщение об ошибке: Not in scope: 'Hearts..'. - person Alfonso Villén; 05.01.2013
comment
@RomanCheplyaka: Да, это тоже мое любимое решение, поэтому я редактировал его в ответ, когда вы писали этот комментарий. ;] - person C. A. McCann; 05.01.2013
comment
@AlfonsoVillén: Спасибо. Глупые паршивые нехорошие синтаксические случаи головной боли... - person C. A. McCann; 05.01.2013
comment
@Tinctorius: Да, хотя, если вам нужна хорошо отсортированная колода, это, вероятно, не идеально. - person C. A. McCann; 05.01.2013
comment
@Tinctorius: Это сработает! Но в этом случае я действительно думаю, что понимание списка более читабельно. Более полезным был бы enumerate = [minBound .. maxBound], который я определил в своем измененном Prelude. Это удобно именно для таких целей. - person C. A. McCann; 05.01.2013
comment
@BenMillwood: у костюмов есть порядок в таких играх, как Bridge. - person siride; 05.01.2013
comment
@siride: да, но в такой игре, как Hearts, может быть другой порядок. На самом деле, возможно, то же самое относится и к рангам, так что это своего рода суждение. Поскольку весь список мастей в любом случае состоит всего из четырех элементов, я не думаю, что будет слишком больно просто написать его. - person Ben Millwood; 05.01.2013

Есть несколько способов сделать это с разной степенью волшебства.

Во-первых, поскольку ни один из конструкторов ваших типов не имеет аргументов, вы можете вывести для них Enum. Это позволит вам написать, например. [Ace..King], чтобы получить список всех карт.

Во-вторых, генераторы списков — отличный способ сформировать список элементов, взятых из нескольких других списков. Попробуй это:

[x + y | x <- [100,200,300], y <- [1,2,3]]

Это должно дать вам инструменты, необходимые для применения к вашему примеру.

person Ben Millwood    schedule 04.01.2013

Альп правильно сказал вам вывести Enum

>data Rank = Ace|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|Jack|Queen|King deriving (Eq,Show,Ord,Enum)
>data Suit = Hearts|Spades|Diamonds|Clubs deriving (Show,Enum)

Теперь:

>enumFrom Ace
[Ace,Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King]

Чтобы получить перестановки двух списков, вы можете использовать понимание списка:

>[[x,y]|x<-[1..2],y<-[2..5]]
[[1,2],[1,3],[1,4],[1,5],[2,2],[2,3],[2,4],[2,5]]

или чтобы получить перестановки сложения:

>[x + y|x<-[1..2],y<-[2..5]]
[3,4,5,6,4,5,6,7]

Теперь вам просто нужно сделать несколько замен, чтобы получить перестановки автомобиля с рангом и костюмом.

person Davorak    schedule 04.01.2013