# How to extend structs for equality, in order to make use of operator like \`in\`?

**URL:** <https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322>\
**Category:** General Usage\
**Tags:** question, package, data\_structures, struct\
**Created:** [May 15, 2024, 8:59pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322 "2024-05-15T20:59:18Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [May 15, 2024, 8:59pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322/1 "2024-05-15T20:59:19Z")

</div>

I’m creating a package to run Black Jack simulations.

I would like to write structs `A`, `C2` (2-card), … etc for each card type in a deck. And, to have a general `Card` struct, so I can overload methods like +, ==, with `Card` logic that permeates to every card.

So far, I tried some things, and ended up with problems:

```julia
export A, C2, C3, C4, C5, C6, C7, C8, C9, C10, CJ, Card, Deck, NewDeck, Hand, isA, isTens, is2_6, CountPositive, CountNegative, CardType

struct Card
    Symbol::String
    Value::Vector{Int}
end

# Define a parametric struct for card types
struct CardType{T<:Card}
    card::T
end

@generated function ≂(x, y)
    if !isempty(fieldnames(x)) && x == y
        mapreduce(n -> :(x.$n ≂ y.$n), (a,b)->:($a && $b), fieldnames(x))
    else
        :(x == y)
    end
end

function ==(a::T, b::T) where T <: CardType
    if !isempty(fieldnames(x)) && x == y
        mapreduce(n -> :(x.$n ≂ y.$n), (a,b)->:($a && $b), fieldnames(x))
    else
        :(x == y)
    end
end

struct A
    card::Card
    function A()
        new(Card("A", [1,11]))
    end
end

struct C2
    card::Card
    function C2()
        new(Card("C2", [2]))
    end
end

```

In the REPL, we see that the structs don’t behave well with equality.

```julia
julia> c = C2()
C2(Card("C2", [2]))

julia> c2 = C2()
C2(Card("C2", [2]))

julia> c == c2
false

julia> c ≂ c2
true

julia> C2() in [C2(), C2()]
false

```

How could I tweak == in order to use `in`, and `==` as expected?

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 15, 2024, 9:08pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322/2 "2024-05-15T21:08:59Z")

</div>

> [@BuddhiLW](#):
>
> ```julia-auto
> struct Card
> Symbol::String
> Value::Vector{Int}
> end
> 
> # Define a parametric struct for card types
> struct CardType{T<:Card}
> card::T
> end
> 
> ```

This doesn’t make much sense, since the only subtype of `Card` is `Card` (concrete types are final in Julia) — it is effectively equivalent to:

```julia-auto
struct CardType
    card::Card
end

```

are you trying to make every card, e.g. A♡ or K♠ be a different type? I wouldn’t recommend this — if you have an array of cards, it will then be a heterogeneously-typed container and [be slow](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-abstract-container).

```julia-auto
function ==(a::T, b::T)

```

Did you forget to import `==` from `Base` in order to extend it?

> [@BuddhiLW](#):
>
> `mapreduce(n -> :(x.$n ≂ y.$n), (a,b)->:($a && $b), fieldnames(x))`

This doesn’t make much sense either. There effectively only one `CardType`, so you will always have the same fields.

I’m pretty confused about what you want the data structure to represent and what you want `==` to do.

# Example code

If you want to represent cards in a standard deck, why not just define a single `Card` type, e.g. something like:

```julia-auto
@enum CardSuit::UInt8 ♠ ♣ ♡ ♢
const spade, club, heart, diamond = ♠,♣,♡,♢
const J, Q, K, A = UInt8.(11:14)
struct Card
    value::UInt8 # 2–10, 11=J, 12=Q, 13=K, 14=A
    suit::CardSuit
    function Card(value::Integer, suit::CardSuit)
        1 < value < 15 || throw(ArgumentError("card values must be between 2 and 14 (A)"))
        return new(value, suit)
    end
end

```

at which point the built-in `==` method will work as-is. It will also be nice to define some convenience methods for [pretty-printing](https://docs.julialang.org/en/v1/manual/types/#man-custom-pretty-printing) and construction by multiplication:

```julia-auto
Base.:*(v::Integer, s::CardSuit) = Card(v, s)
function Base.show(io::IO, c::Card)
    if c.value < 11
        print(io, Int(c.value))
    else
        print(io, c.value == J ? 'J' : c.value == Q ? 'Q' : c.value == K ? 'K' : 'A', '*')
    end
    print(io, c.suit)
end

```

at which point you can do:

```julia-auto
julia> 3heart
3♡

julia> A*♠
A*♠

julia> deck = [Card(v,s) for v in 2:14, s in (♠,♣,♡,♢)]
13×4 Matrix{Card}:
 2♠ 2♣ 2♡ 2♢
 3♠ 3♣ 3♡ 3♢
 4♠ 4♣ 4♡ 4♢
 5♠ 5♣ 5♡ 5♢
 6♠ 6♣ 6♡ 6♢
 7♠ 7♣ 7♡ 7♢
 8♠ 8♣ 8♡ 8♢
 9♠ 9♣ 9♡ 9♢
 10♠ 10♣ 10♡ 10♢
 J*♠ J*♣ J*♡ J*♢
 Q*♠ Q*♣ Q*♡ Q*♢
 K*♠ K*♣ K*♡ K*♢
 A*♠ A*♣ A*♡ A*♢

julia> using Random

julia> shuffle(deck)
13×4 Matrix{Card}:
 3♠ 4♣ K*♡ 10♢
 10♣ J*♠ K*♢ 7♠
 5♣ 5♡ 6♠ 10♡
 2♢ 7♡ J*♡ A*♡
 5♢ 9♡ 6♡ 6♣
 A*♢ 9♣ 4♠ 3♢
 8♣ 4♡ 3♡ 6♢
 2♣ Q*♡ 10♠ 8♢
 2♡ 8♠ 2♠ 5♠
 K*♠ K*♣ 4♢ 9♢
 J*♣ J*♢ 3♣ Q*♢
 7♣ 7♢ 8♡ Q*♣
 9♠ A*♣ Q*♠ A*♠

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 15, 2024, 9:35pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322/3 "2024-05-15T21:35:33Z")

</div>

> [@stevengj](#):
>
> If you want to represent cards in a standard deck, why not just define something like:

Better yet, use an existing package that does this and more, e.g. [PlayingCards.jl](https://github.com/charleskawczynski/PlayingCards.jl) (which is much better than my toy code above, e.g. it only uses one byte per card, has many more features, and in general is better thought-through than my rough code above).

---

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [May 16, 2024, 4:28pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322/4 "2024-05-16T16:28:31Z")

</div>

Thanks for the initial reply, Steven. And, sharing the `PlayingCards.jl` package.

The goal is that I will have an struct for the `Hand` of Blackjack

```julia
mutable struct Hand
    InitialCards::Pair{Card, Card}
    Cards::Array{Card}
    Value::Int
    function Hand(InitialCards)
        # Value = sum(InitialCards)
    end
end

```

And I’m writing the sum function for the `Hand`, and one of the things I have to do is check if the hand has an Ace. Because, it can either be valued as an 11, or 1. For example, if I have an A,3 (14), then I hit and now I have A,3,8, my hand is valued 12, not 22.

So, I have to check if `A() in Hand.Cards`.

As I showed earlier, in my implementation, when I do A() in [A(), A()] it returns `false`. And, It should be `true`. This is important for the function-method `sum`.

---

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [May 16, 2024, 5:13pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322/5 "2024-05-16T17:13:46Z")

</div>

I will work around `PlayingCards.jl`

It doesn’t implement some abstractions I would like, like a general `Q()`, regardless of suit. But, I can make extensions easily.

For example, I can work around that with:

```julia
julia> rank(PlayingCards.Q♠) in [rank(card) for card in cards]
true

julia> cards
3-element Vector{PlayingCards.Card}:
 Q♡
 K♡
 A♡

```

Then, I will implement BJ-card-values constrains logic, inside the game structs, like `Hand`. (J, Q, K and Ace) have different values, in Black Jack etc.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [May 16, 2024, 5:25pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322/6 "2024-05-16T17:25:17Z")

</div>

> [@BuddhiLW](#):
>
> So, I have to check if `A() in Hand.Cards`.

This seems like the wrong abstraction here, because an ace is not a single card — it is an equivalence class of possible cards (aces in four suits, A♣ etc.). Unless you don’t store the suit and just store the equivalence class (the card value).

Here is how you would check if an ace is in a hand using PlayingCards.jl:

```julia
julia> using PlayingCards

julia> deck = shuffle!(ordered_deck());

julia> hand = pop!(deck, 4)
(T♠, 3♠, A♣, Q♣)

julia> any(card -> rank(card) == 1, hand) # check for aces, in any suit
true

julia> any(card -> rank(card) == 4, hand) # check for a 4 in any suit
false

```

So, for example, to sum the blackjack value of a hand, you might do:

```julia
function blackjack_value(hand)
    value = n_aces = 0
    for card in hand
        r = rank(card)
        value += min(r, 10)
        n_aces += r == 1
    end
    while value < 12 && n_aces > 0
        value += 10 # treat aces as 11 if beneficial
        n_aces -= 1
    end
    return value
end

```

gives

```julia
julia> blackjack_value((K♢, 8♣, 2♣, A♠))
21

julia> blackjack_value((K♢, A♠))
21

```

---

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [May 16, 2024, 6:24pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322/7 "2024-05-16T18:24:50Z")

</div>

Amazing, that took me a minute to understand the algorithm. It makes sense and it works.

Also, interesting use of `any`. I will keep it in mind and follow that construction.

Thanks, @stevengj

---

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [May 16, 2024, 6:32pm UTC](https://discourse.julialang.org/t/how-to-extend-structs-for-equality-in-order-to-make-use-of-operator-like-in/114322/8 "2024-05-16T18:32:15Z")

</div>

In case anyone is interested. I just made the repository I was working in public.

Feel free to contribute, if you feel like it.

> **[GitHub - BuddhiLW/Blackjack.jl: Julia plots, gifs and Monte Carlos...](https://github.com/BuddhiLW/Blackjack.jl)**
>
> Julia plots, gifs and Monte Carlos simulations of probabilities concerning Blackjack - BuddhiLW/Blackjack.jl

Regards,  
Pedro
