LoginSignup
6
0

More than 3 years have passed since last update.

Elmでシクシク素数

Last updated at Posted at 2018-12-10

こちらは シクシク素数列 Advent Calendar 2018 11日目の記事です。

ルール(転載)

  • 数値に4か9を含む素数をシクシク素数と呼ぶことにします
    • 19とか41とか149とか。
  • 標準入力として正の整数 N を与えたら N 番目までのシクシク素数を半角カンマ区切りで標準出力してください
    • 例 N = 9 の場合、 19,29,41,43,47,59,79,89,97
  • N は最大で 100 とします

実装

Elmはフロントエンドの関数型言語であるため、本来は標準入力であるところをスライダーによる入力としました。

Ellie-appによるデモ ←デモをおいておきますので自由に使ってみてください。

ソースコードを貼っておきます。解説は下の方で。

module Main exposing (main)

import Browser exposing (Document, document)
import Html exposing (Html, div, input, li, text, ul)
import Html.Attributes as A
import Html.Events exposing (onInput)


constMax : Int
constMax =
    100


main =
    Browser.document
        { init = init
        , view = view
        , update = update
        , subscriptions = subscriptions
        }


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none


type alias Model =
    { n : Int
    }


init : () -> ( Model, Cmd Msg )
init _ =
    ( { n = 9 }, Cmd.none )


type Msg
    = NoOp
    | UpdateMax Int


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        NoOp ->
            ( model, Cmd.none )

        UpdateMax n ->
            ( { model | n = n }, Cmd.none )


view : Model -> Document Msg
view model =
    { title = "シクシク素数"
    , body = viewBody model
    }


viewBody : Model -> List (Html Msg)
viewBody model =
    [ div []
        [ input [ A.type_ "range", A.min "1", A.max <| String.fromInt constMax, A.value <| String.fromInt model.n, onInput inputMax ] []
        , text <| String.fromInt model.n
        ]
    , show49Primes model
    ]


inputMax : String -> Msg
inputMax v =
    case String.toInt v of
        Just n ->
            if n > 0 && n <= constMax then
                UpdateMax n

            else
                NoOp

        Nothing ->
            NoOp


show49Primes : Model -> Html Msg
show49Primes model =
    div []
        [ list49Primes model.n
            |> List.map (\n -> String.fromInt n)
            |> String.join ","
            |> text
        ]


list49Primes : Int -> List Int
list49Primes n =
    list49Primes_ n 2 []


list49Primes_ : Int -> Int -> List Int -> List Int
list49Primes_ n i xs =
    case n of
        0 ->
            List.reverse xs

        _ ->
            if isPrime i && hasFourOrNine i then
                list49Primes_ (n - 1) (i + 1) (i :: xs)

            else
                list49Primes_ n (i + 1) xs


isPrime : Int -> Bool
isPrime n =
    List.range 2 (floor <| sqrt <| toFloat n)
        |> List.all (\i -> modBy i n /= 0)


hasFourOrNine : Int -> Bool
hasFourOrNine n =
    String.fromInt n
        |> (\s -> String.contains "4" s || String.contains "9" s)

解説

メインロジックの部分を解説しますね。
list49Primes という関数でシクシク素数列を生成しています。

list49Primes : Int -> List Int
list49Primes n =
    list49Primes_ n 2 []


list49Primes_ : Int -> Int -> List Int -> List Int
list49Primes_ n i xs =
    case n of
        0 ->
            List.reverse xs

        _ ->
            if isPrime i && hasFourOrNine i then
                list49Primes_ (n - 1) (i + 1) (i :: xs)

            else
                list49Primes_ n (i + 1) xs

最初の list49Primeslist49Primes_ という関数に初期値を与えるだけのものです。

関数型言語であるElmでは変数への代入ができず、 while などのループが使えません(そもそも while がありません)。そのため既定の個数の素数が見つかるまでループするには再帰関数を使うことになります。

list49Primes_ という関数は末尾再帰関数になっていて、シクシク素数が見つかったら n を一つ減らしてリストに素数を追加します。(末尾再帰にする必要はありませんが、ただの好みです。)
スキップする部分は省略しますが、実行イメージはこんな感じです。リストの先頭に要素を追加して最後にひっくり返してます。

list49Primes_ 4 19 []
list49Primes_ 3 29 [19]
list49Primes_ 2 41 [29, 19]
list49Primes_ 1 43 [41, 29, 19]
list49Primes_ 0 44 [43, 41, 29, 19]
[19, 29, 41, 43]

偶数をスキップするなどすればもっと効率化できますが、100個までならこれでも十分だと思います。

最後に

Elmに興味を持った方がいらっしゃれば、Elmアドベントカレンダーもありますのでぜひどうぞ。

6
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0