逐行学习 Julia

2021-08-05 20:47:01

Cardsjl 是一个简单的 Julia 包,它展示了 Julia 编程语言的许多有趣的部分。阅读它是一种愉快的体验。我写下的注释在这里: # import base multiply(*), bitwise-or(|), and bitwise-and(&) # # 在 Julia 中,您可以使用 `using` 或 `import` 加载模块。不同之处在于 # * `using` 将加载模块 ** 并** 将加载的模块重新导出到周围的全局 # 命名空间中。 # * `import` 只会加载模块并将模块名称重新导出到作用域。 import Base : *, |, & """ 将花色编码为 2 位值(`UInt8` 的低位):- 0 = ♣(俱乐部)- 1 = ♢(钻石)- 2 = ♡(红心) - 3 = ♠ (spades) 花色具有全局常量绑定:`♣`、`♢`、`♡`、`♠`。""" # 这里我们定义了一个结构体`Suit`。 `Suit` 包含一个类型为`UInt8` 的 `i` 变量 # # 在 Julia 中,类型对象是构造函数。我们可以通过调用函数`suit = Suit(0)`来创建结构体的新实例 # struct Suit i :: UInt8 # 这里,我们定义了一个“内部构造函数方法”来定义构造函数的约束 # # 这也是一个Julia 的 unicode 支持的简洁示例。是的,我们可以将 ≤ 用作 `<=`。西装(s :: Integer) = 0 ≤ s ≤ 3 ? new(s) : throw( ArgumentError( "invalidsuit number: $s ")) end # Julia 的抽象主要由多重分派提供支持。 # 这里我们为 `char()` 函数定义了一个“新调度”来将普通字符转换为 Suit。 # 因此,每当 `char()` 函数与 `Suit` 一起应用时,将使用该调度。char(s :: Suit) = Char( 0x2663 -s .i) # ... 以及其他一些帮助器。它们令人惊讶地不言自明Base .string(s :: Suit) = string(char(s))Base .show(io :: IO, s :: Suit) = print(io, char(s)) # 在一个普通的扑克牌,只有 4 种可能的花色 # 我们可以用 Julia 中的 unicode 符号编写可读代码 const ♣ = Suit( 0) const ♢ = Suit( 1) const ♡ = Suit( 2) const ♠ = Suit( 3) ) constsuits = [♣, ♢, ♡, ♠] """ 将一张扑克牌编码为 6 位整数(`UInt8` 的低位): - 低位代表 0 到 15 的等级 - 高位代表花色(♣、♢、♡ 或♠)等级分配如下: - 编号卡(2 到 10)的等级与其号码相同 - 杰克、皇后和国王的等级为 11、12 和 13 - 有低和高 A等级 1 和 14 - 有等级 0 和 15 的低级和高级小丑 这允许通过选择使用哪个 A 或哪个小丑来实现任何标准的纸牌等级排序。总共有 64 种可能的纸牌值这个方案,由`UInt8`值`0x00`到`0x3f`表示。” "" # 卡片是一个结构体,它将花色和等级编码成一个 UInt8 # # 在大多数高级语言中,定义这会有点乏味。 # 然而,Julia 在这里直接展示了它的表现力。 struct Card value :: UInt8 end # 我们为 `Card` 的构造函数创建一个新的 dispatch。 # 它将把秩和花色编码成 UInt8 函数 Card(r :: Integer, s :: Integer) 0 ≤ r ≤ 15 || throw( ArgumentError( "invalid card rank: $r ")) return Card(((s << 4) % UInt8) | (r % UInt8)) end # 当suitCard( r :: Integer, s :: Suit) = Card(r, s .i) # 这些是用于从 cardsuit 中获取等级或花色的 getter(c :: Card) = Suit(( 0x30 & c .value) >> > 4)rank(c :: Card) = (c .value & 0x0f) % Int8 # 让我们为 Base.show 定义一个新的调度,使其在打印时看起来不错。 function Base .show(io :: IO, c :: Card) r = rank(c) if 1 ≤ r ≤ 14 r == 10 && print(io, '1') print(io, "1234567890JQKA"[r] ) else print(io, '\U1f0cf') end print(io,suit(c)) end # 在 Julia 中,`2 * x` 可以写成 `2x`。 # # 通过为乘法运算符 `*` 创建一个新的调度,我们可以编写 `2♣` # 并且它会自动转换为 `Card(2, ♣)`。哇花哨。 *(r :: Integer, s :: Suit) = Card(r, s) # 然而,“J♣”不会被视为乘法。 # # 这里我们使用`@eval` 宏将这些变量创建为常量。 # 这样的口齿不清。我喜欢。 for s in "♣♢♡♠", (r,f) in zip( 11 : 14, "JQKA") ss, sc = Symbol(s), Symbol(" $f$s ") @eval ( export $sc ; const $sc = Card( $r, $ss)) end """ 使用`UInt64` 位集表示一手(套)牌。""" # 我们使用`UInt64` 位集来存储什么牌由于一副牌中只有 52 张牌,因此出现在手牌中。 # # 我们使用 `<:` 来表示 `Hand` 是 `AbstractSet{Card}` 的子类型。 # 因此,`Hand` 可以与所有具有可兼容分派到 `AbstractSet` 的函数一起使用。 struct Hand <: AbstractSet{ Card} card :: UInt64 Hand(cards :: UInt64) = new(cards) end # 将卡片值转换为位 set positionbit(c :: Card) = one( UInt64) << c .value #将西装转换为位集 rangebits(s :: Suit) = UInt64( 0xffff) << 16(s .i) # 将一组卡片转换为位集的简单构造函数 Hand(cards) hand = Hand(zero( UInt64)) 用于卡中卡卡 is 卡 || throw( ArgumentError( "not a card: $repr (card)")) i = bit(card) hand .cards & i == 0 || throw( ArgumentError("不支持重复的卡片")) hand = Hand(hand .cards | i) end return hand end # 为我们的 Hand typeBase 进行更多调度。 in(c :: Card, h :: Hand) = (bit(c) & h .cards) != 0Base .length(h :: Hand) = count_ones(h .cards)Base .isempty(h :: Hand) = h .cards == 0Base .lastindex(h :: Hand) = length(h) # 为我们的 Hand 定义一个迭代器。 # # 我们可以使用这里介绍的语法定义一个具有默认值的参数 function Base .iterate(h :: Hand, s :: UInt8 = trailing_zeros(h .cards) % UInt8) (h .cards >>> s) == 0 && 什么都不返回 c = Card(s); s += true c, s + trailing_zeros(h .cards >>> s) % UInt8 end # 从 Hand 函数中获取 Card 的非绑定检查函数 Base .unsafe_getindex(h :: Hand, i :: UInt8 ) card, s = 0x0, 0x5 而真正的 mask = 0xffff_ffff_ffff_ffff >> ( 0x40 - ( 0x1 <<s) - card) card += UInt8(i > count_ones(h .cards & mask) % UInt8) << ss > 0 || break s -= 0x1 end return Card(card) end # 为了避免不断地从 UInt8 转换为 Integer,# 我们为所有 IntegeresBase 的 unsafe_getindex 创建一个新的 dispatch .unsafe_getindex(h :: Hand, i :: Integer) = Base .unsafe_getindex(h, i % UInt8) # 最后,我们用一个有界检查的 `getindex` 函数来包装我们不那么安全的函数 function Base .getindex(h :: Hand, i :: Integer) # `@ boundscheck` 宏允许使用`@inbound` 宏忽略边界检查@boundscheck 1 ≤ i ≤ length(h) || throw( BoundsError(h,i)) return Base .unsafe_getindex(h, i) end # 使`Hand` 在打印时看起来很好看 function Base .show(io :: IO, hand :: Hand) if isempty(hand) | | !get(io, :compact, false) print(io, "Hand([") for card in hand print(io, card) (bit(card) << 1) ≤ hand .cards && print(io, ", ") end print(io, "])") else s = hand &suit isempty(s) && continue show(io,suit) for card in sr = rank(card) if r == 10 print( io, '\u2491') elseif 1 ≤ r ≤ 14 print(io, "1234567890JQKA"[r]) else print(io, '\U1f0cf') end end end end # 更多的dispatch让我们可以将两只手与`|`,用`|`a 将一张牌加到手上: :: Hand | b :: Hand = Hand(a .cards | b .cards)a :: Hand | c :: Card = Hand(a .cards | bit(c))c :: Card | h :: 手 = h | c # 用 `&`a 插入两只手 :: Hand & b :: Hand = Hand(a .cards & b .cards) # 用 `&`h 获取花色范围内的牌 :: Hand & s :: Suit = Hand(h .cards & bits(s))s :: Suit & h :: Hand = h & s # 更多新调度Base .intersect(s :: Suit, h :: Hand) = h & sBase .intersect(h : : Hand, s :: Suit) = intersect(s :: Suit, h :: Hand) # 我们的花色和牌的范围运算符 *(rr :: OrdinalRange{ <: Integer}, s :: Suit) = Hand(Card (r,s) for r in rr) ..(r :: Integer, c :: Card) = (r :rank(c)) *suit(c) ..(a :: Card, b :: Card) = 西装(a) == 西装(b) ? rank(a) ..b : throw( ArgumentError( "卡片范围需要匹配的花色:$a vs $b ")) # 最后,我们创建了一副套牌,其中包含 52 张独特的卡片 consteck = Hand(Card(r,s) ) for s insuits for r = 2 : 14) # 空手可以表示为 0Base .empty( :: Type{ Hand}) = Hand(zero( UInt64)) # 使用 `rand` 得到一个随机子集牌组 # 我们使用 @eval 将所有卡片插入到这个表达式中,然后对其求值 @eval Base .rand( :: Type{ Hand}) = Hand( $(deck .cards) & rand( UInt64)) # 在 Julia 中,一个以`!`结尾的函数表示它是一个就地更新函数##这里我们定义了一个`deal!`函数来根据指定的`counts`布局来填充手数 function deal!(counts :: Vector{ <: Integer}, hand :: AbstractArray{ Hand}, offset :: Int = 0) for rank = 2 : 14,suit = 0 : 3 while true hand = rand( 1 : 4) if counts[hand] > 0 counts[hand] -= 1 hand[offset + hand] |= Card(rank,suit) break end end end return hands end # 现在让我们定义我们的 `deal` 函数。它将按照给定的数量向 4 个人发牌 # # 没有参数时的默认调度 provideddeal() = deal!(fill(13, 4), fill(empty(Hand), 4)) # 为 `deal` 调度 when提供了一个 `n` Int 函数 deal(n :: Int) counts = fill( 0x0, 4) hands = fill(empty(Hand), 4, n) for i = 1 :n deal!(fill!(counts, 13), hands, 4(i - 1)) end return permutedims(hands) end # 计算给定手的点数 function points(hand :: Hand) p = 0 for rank = 11 : 14,suit = 0 : 3 card = Card(rank,suit) p += (rank - 10) *(card in hand) end return p end