20210403のRubyに関する記事は25件です。

[Rails]正規表現でバリデーションしてみよう

#はじめに
私の過去の記事で,ユーザ管理機能の実装でdeviseをご紹介しました。その際に特別触れることはありませんでしたが、実はdeviseではemailのアドレス登録で@をつけることが必須です。
当時は、便利だな〜と思いましたがその仕組みが気になり、調べていくと、、、
「正規表現」といものと巡り合ったのでこの記事にてご紹介します。

正規表現とは

正規表現とは、文字列の一部を抽出あるいは置換をしたり、文字列が制約を満たしているかを調べるための表現方法です。

たとえば、あるアンケートで電話番号と郵便番号を書かせるフォームがあったとします。
その時にただの空欄ボックスであった場合にハイフンをつけたりつけなかったり、いろんな人がでてきて管理がしづらいですよね。なので、正規表現によってハイフンが入っていたら取り除いたりするわけです。
このように文字列に特定の文字が含まれているかを確認することや、特定の文字を取り除くなどの操作を行うための技術が正規表現です。

正規表現の基礎

まずは正規表現を利用するための3つのメソッドをご紹介します。
・subメソッド
・matchメソッド
・gsubメソッド

subメソッド

subメソッドは、文字列の指定した部分を別の文字列に置き換えるためのメソッドです。
操作する文字列は//で囲ってやって、第2引数に置換後の文字列を入れます

こんな感じです

irb(main):001:0> str = "ラーメンを食べる"
=> "ラーメンを食べる"

irb(main):002:0> str.sub(/ラーメン/,"チャーハン")
=> "テャーハンを食べる"

matchメソッド

matchメソッドは、指定した文字列がある文字列に含まれているか否かをチェックするためのメソッドです。
これはみた方が理解が早いので以下に例を示します。

irb(main):001:0> str = "Hello, World"
=> "Hello, World"

irb(main):002:0> str.match(/Hello/)
=> #<MatchData "Hello">

irb(main):003:0> str.match(/Good/)
=> nil

戻り値には、MatchDataという種類のオブジェクトで返されます。

gsubメソッド

指定の文字列すべてを置換します。
subと似ていますね。ただし、subの場合、最初にマッチした文字列に対してのみで、gsubは全てです。
そう、gとはgrobalのこと、全てなのです!!!!(笑)

irb(main):001:0> tel = '090-1234-5678'
=> "090-1234-5678"

irb(main):002:0> tel.sub(/-/,'')
=> "0901234-5678"
# 最初のハイフンしか置換されない

irb(main):003:0> tel.gsub(/-/,'')
=> "09012345678"

いよいよバリデーションの準備です!!

ここまでで、なんとなく基本的な部分は理解できましたか??
ここからはさっそくバリデーションをかける準備です。

今回はパスワードに4文字以上という制限をかけましょう。

先に結論から言うと、こんな感じにします。

user.rb
validates :password,format:{with: /[a-z\d]{4,}/i }

初見殺しすぎますよね。。。わかります。。。。

でも焦らない!一つづつ見ていきましょう。
まず、/[a-z\d]{4,}/iの部分を分解してみましょう。

[a-z]
\d
{n, m}
i

[a-z]

角括弧[]を使用することで角括弧で囲まれた文字のうちいずれか1つがマッチするかをチェックしています。-(ハイフン)で範囲を設定することができます。
今回はアルファベットのaからzまでのいずれかにマッチという意味になります。
実際にmatchを使って見てみます

irb(main):001:0> 'debug'.match(/[a-c]/)
=> nil

\d

これは数字を表します。なお、このような文字を特殊文字といいます。

{n, m}

直前の文字が少なくとも n 回、多くても m 回出現するものにマッチすることを確認します。

i

iはオプションで、大文字・小文字を区別せずに検索します。

もう意味はわかりましたか??

ここまでこればもう意味はなんとなくわかってきたはず!!
もう一度さっきのバリデーションをみてみましょう

user.rb
validates :password,format:{with: /\A[a-z\d]{4,}\z/i }

/[a-z\d]{4,}/i を読むと、
[a-z\d](英数字を)
{4,}(最低4回)
/i(大文字小文字区別なく)
と言う意味になります!!!

追記(2021年4月4日):
英数字4文字以上のパスワードにするためには、文字列の最初ですと言う意味を持つ\Aと文字列の末尾ですよと言う芋を持つ\zが必要です。詳しくはこちらをどうぞ。
https://qiita.com/itsumoonazicode/items/e3c7886909ce68986c9a

この制約でのフォーマットをバリデーションをかけると上記のようになるわけです。

おわりに

以上、正規表現についてざっくりですがご紹介いたしました、
ほかにもたくさん表現の仕方があるので興味のある方は調べてみてください!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SATソルバ―で遊んでみた

はじめに

Kinx で Package Manager を作ろうと思い、バージョン依存性チェックを SAT ソルバ―にやらせようと思いました。しかし複数バージョン共存できる方が良いのでは、と、バージョン依存性チェックいらないかも、と思い始めた今日この頃。結局作ったけど使わないかも、な感じになってます。

とはいえ、せっかく実装したのでいくつか遊んでみました。8 Queens と数独を解いてみます。

ちなみに Kinx とは↓コレです。

また、最後に(これもせっかくなので)バージョン依存性チェックプログラムを載せておきます。使わないかもですが。

SAT ソルバ―とは

ご注意: SAT ソルバーはリリース版には含まれていません。最新の master ブランチに含まれています。

充足可能性問題を解く機械(プログラム)。Wikipedia が詳しいのでご参考。Kinx では picosat をバックエンドに使いました。

簡単に言うと、$(x_1 \lor x_2) \land (x_1 \lor \lnot x_2) \land (\lnot x_1 \lor \lnot x_2)$ みたいな論理式があって、この式全体が真となる各変数の真偽値の組合せを見つけるというものです。例えばこの例の場合、$x_1 = $ 真、$x_2 = $ 偽とすれば全体が真となるのでこの組合せは解の一つです。

SAT ソルバ―を使う、というのは、与えられた問題を上記のような論理式で表し、SAT ソルバーに解かせ、出てきた結果を解釈する、ということを行うこと、意味します。

ちなみに、論理式の形は $(A \lor B \lor \ldots) \land (X \lor Y \lor \ldots) \land ...$ と OR でつながった式を AND でつなげた式として表現します。また、ある命題を解く際に、$A \Rightarrow B$ ($A$ ならば $B$)の条件を考えるシーンがよくありますが、これは論理式として同一の解となる $\lnot A \lor B$ で置き換えて考えます。

Kinx で使うには、以下のように using します。また、ライブラリは Satisfiablity クラスで、インスタンス化しておきましょう。

using SatisfiablitySolver;
var s = new Satisfiablity();

8 Queens を解いてみた

8 Queens を解いてみましょう。8 Queens に関しても Wikipedia が詳しいのでご参考。

SAT ソルバ―を使うということは、8 Queens の問題を論理式で表すということです。どう表せばよいでしょう。

SAT ソルバーでは、各変数を整数で管理します。そして、変数 1 が真の場合は +1、偽の場合は -1 として符号で判断できるようにします。従って、全ての状態を変数として番号を付け、ある変数(番号)が真(プラス)の時に他の変数(番号)がどうなるのかを論理式で表すようにします。こんな感じです。

  • 変数
    • すべてのマスを表す変数を仮定する。8 x 8 マスなので、64 個の変数がある。
    • 変数が真の場合は Queen が存在するものとする。
  • 条件(制約)
    • 行(列でも良い)の中に少なくとも 1 つは Queen が存在する。
    • ある $(i, j)$ 座標に Queen があるとした場合、その上下左右、ナナメ全てに Queen は存在しない。

この時、例えば最初の条件として 1 行目を考えます。1行目を表す変数を $x_1$, $x_2$, $x_3$, $\ldots$, $x_8$ とすると、$(x_1 \lor x_2 \lor x_3 \lor x_4 \lor x_5 \lor x_6 \lor x_7 \lor x_8)$ となります。2 ~ 8 行目も同じように条件が書けます。

プログラム的には、これを配列で表して、[1, 2, 3, 4, 5, 6, 7, 8] と扱います。全部真なのでプラスです。この 1 つの OR の式を節(Clause)と言います。複数の節を作って条件を絞っていきます。

まず、分かりやすくするために、ある $(i, j)$ の変数の番号を返す関数を作っておきましょう。ここで N は 8 です。+1 するのは、変数は 1 始まりだからです。

const N = 8;

function getVarNum(i, j) {
    return (i + j * N) + 1;
}

では各行に少なくとも 1 つは Queen が存在する、というのを書くと以下のようになります。これで [1, 2, 3, 4, 5, 6, 7, 8][57, 58, 59, 60, 61, 62, 63, 64] までの 8 つの節が登録されます。

for (var jx = 0; jx < N; ++jx) {
    s.addClause(N.times().map { => getVarNum(_1, jx) });
}

次の条件「ある $(i, j)$ 座標に Queen があるとした場合、その上下左右、ナナメ全てに Queen は存在しない。」はどう表しましょう。これは、$A \Rightarrow B$ の形で個別に指定していきます。例えば、「$(0, 0)$ にあれば $(1, 0)$ には存在しない」は変数で表すと、$x_1 \Rightarrow \lnot x_2$ なので、$\lnot x_1 \lor \lnot x_2$ です。つまり、節としては [-1, -2] になります。すべてのマスで上下左右、ナナメそれぞれ同じように節を追加していきます。

ところで、実は $x_1 \Rightarrow \lnot x_2$ は $x_2 \Rightarrow \lnot x_1$ と同じことを表しています。なぜなら OR は交換可能なのでどちらも $\lnot x_1 \lor \lnot x_2$ だからです。ということで、左上から順にスキャンする場合、あるマス $(i, j)$ の条件を調べる際は既に設定した節と同じものを設定する必要はありません。つまり、右下左下、だけをスキャンして節を登録すればよいのです。

これをプログラムにすると、以下のようになります。これをすべてのマスで呼びます。

function addClauses(s, i, j) {
    const ijQ = getVarNum(i, j);

    /* right */
    for (var ix = i + 1; ix < N; ++ix) {
        s.addClause([-ijQ, -getVarNum(ix, j)]);
    }

    /* down */
    for (var jx = j + 1; jx < N; ++jx) {
        s.addClause([-ijQ, -getVarNum(i, jx)]);
    }

    /* right-down */
    for (var ix = i + 1, jx = j + 1; ix < N && jx < N; ++ix, ++jx) {
        s.addClause([-ijQ, -getVarNum(ix, jx)]);
    }

    /* left-down */
    for (var ix = i - 1, jx = j + 1; ix >= 0 && jx < N; --ix, ++jx) {
        s.addClause([-ijQ, -getVarNum(ix, jx)]);
    }
}

for (var ix = 0; ix < N; ++ix) {
    for (var jx = 0; jx < N; ++jx) {
        addClauses(s, ix, jx);
    }
}

さて、ここまで準備できたら SAT ソルバーから解を求めます。Satisfiablity クラスはイテレータブルにしてあるので、以下のように for-in で回せます。

function display(e) {
    System.print("\nSolution %d\n" % ++solution);
    var board = [];
    for (var ijQ = 0, l = e.length(); ijQ < l; ++ijQ) {
        var i = ijQ % N;
        var j = Integer.parseInt(ijQ / N);
        board[i][j] = e[ijQ] > 0;
    }
    for (var jx = 0; jx < N; ++jx) {
        for (var ix = 0; ix < N; ++ix) {
            System.print(board[ix][jx] ? " Q" : " .");
        }
        System.println("");
    }
}

for (var e in s) {
    display(e);
}

実行すると、無事 92 個の解が表示されました。

$ ./kinx examples/sat_nqueens.kx

Solution 1
 . . . . . . . Q
 . Q . . . . . .
 . . . . Q . . .
 . . Q . . . . .
 Q . . . . . . .
 . . . . . . Q .
 . . . Q . . . .
 . . . . . Q . .

Solution 2
 . . . Q . . . .
 . . . . . . . Q
 . . . . Q . . .
 . . Q . . . . .
 Q . . . . . . .
 . . . . . . Q .
 . Q . . . . . .
 . . . . . Q . .

/* 省略 */

Solution 92
 . . . Q . . . .
 . . . . . . Q .
 . . . . Q . . .
 . Q . . . . . .
 . . . . . Q . .
 Q . . . . . . .
 . . Q . . . . .
 . . . . . . . Q

数独を解いてみた

次に数独を解いてみます。数独に関してもまた Wikipedia が詳しいのでご参考。

数独は変数の数が増えます。縦横に加えてマスごとに 1 ~ 9 までの数値を取るといった奥行的な(立体的な)イメージを持つ必要がありますが、考え方は一緒です。

各マスが 1 ~ 9 までの数値を取るので、マスごとに 9 個の状態を持たせます。つまり、左上のマスが 1 であるとき、2 であるとき、... と全て異なる変数として扱います。例えば、$R \times R$ の数独において $(i, j)$ が $v$ であるときの変数の番号を $(i + j \times R) \times R + v$ として定義します。$v$ が 1 から始まるので 1 加える必要はありません。例えば、変数 1 が真であれば、左上のマスは 1 です。変数 9 が真であれば、左上のマスは 9 であることを表します。変数 10 はマスが移って、左上の 1 つ右(左から 2 番目)のマスが 1 ということになります。

では早速先ほどと同様に、変数の番号を返す関数から定義してみましょう。$R \times R$ の数独です。

function getVarNum(i, j, v) {
    return ((i + j * R) * R) + v;
}

次に制約条件です。「ミニ区画」と言ってますが、$9 \times 9$ の数独の場合、その中に 9 つ存在する $3 \times 3$ の領域のことを意味しています。この 3 を $N$ とします。つまり、$R = N^{2}$ です。

  • あるマス $(i, j)$ には 1 ~ 9 のいずれかの数値が入る。
  • あるマス $(i, j)$ が $v$ であるとき、同じ $(i, j)$ のマスには $v$ 以外の数値は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、同じ行に $v$ は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、同じ列に $v$ は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、そのマスを含むミニ区画に $v$ は存在しない。

これをプログラムで書くと、次のようになります。

function addClauses(s, i, j, v) {
    const ijv = getVarNum(i, j, v);

    // There is at most one number in each cell. (Cell - uniqueness)
    for (var vi = 1; vi <= R; ++vi) {
        if (v != vi) {
            s.addClause([-ijv, -getVarNum(i, j, vi)]);
        }
    }

    // Each number appears at most once in each row. (Row - uniqueness)
    for (var ix = i + 1; ix < R; ++ix) {
        s.addClause([-ijv, -getVarNum(ix, j, v)]);
    }

    // Each number appears at most once in each column. (Column - uniqueness)
    for (var jx = j + 1; jx < R; ++jx) {
        s.addClause([-ijv, -getVarNum(i, jx, v)]);
    }

    var si = Integer.parseInt(i / N) * N;  // i as a left top in sub square.
    var sj = Integer.parseInt(j / N) * N;  // j as a left top in sub square.
    var ei = si + N;
    var ej = sj + N;
    // Each number appears at most once in each block. (Block - uniqueness)
    for (var ix = si; ix < ei; ++ix) {
        for (var jx = sj; jx < ej; ++jx) {
            const ijv2 = getVarNum(ix, jx, v);
            if (ijv2 > ijv) {
                s.addClause([-ijv, -ijv2]);
            }
        }
    }
}

for (var j = 0; j < R; ++j) {
    for (var i = 0; i < R; ++i) {
        const ijv = ((i + j * R) * R);
        var v = test[j][i];
        if (v == 0) {
            // There is at least one number in each cell. (Cell - definedness)
            s.addClause(R.times().map { => ijv + (_1 + 1) });
        } else {
            s.addClause([ ijv + v ]);
        }
        R.times().each { => addClauses(s, i, j, (_1 + 1)) };
    }
}

実際に $9 \times 9$ の数独を解いてみましょう。

+----------+----------+----------+
|  8  .  . |  .  .  . |  .  .  . |
|  .  .  3 |  6  .  . |  .  .  . |
|  .  7  . |  .  9  . |  2  .  . |
+----------+----------+----------+
|  .  5  . |  .  .  7 |  .  .  . |
|  .  .  . |  .  4  5 |  7  .  . |
|  .  .  . |  1  .  . |  .  3  . |
+----------+----------+----------+
|  .  .  1 |  .  .  . |  .  6  8 |
|  .  .  8 |  5  .  . |  .  1  . |
|  .  9  . |  .  .  . |  4  .  . |
+----------+----------+----------+

+----------+----------+----------+
|  8  1  2 |  7  5  3 |  6  4  9 |
|  9  4  3 |  6  8  2 |  1  7  5 |
|  6  7  5 |  4  9  1 |  2  8  3 |
+----------+----------+----------+
|  1  5  4 |  2  3  7 |  8  9  6 |
|  3  6  9 |  8  4  5 |  7  2  1 |
|  2  8  7 |  1  6  9 |  5  3  4 |
+----------+----------+----------+
|  5  2  1 |  9  7  4 |  3  6  8 |
|  4  3  8 |  5  2  6 |  9  1  7 |
|  7  9  6 |  3  1  8 |  4  5  2 |
+----------+----------+----------+

解けました。

ところが、これでもいいのですが、実は $25 \times 25$ の数独で返ってきません。実は冗長であっても節を増やしたほうが余計な条件を枝刈りして速く解を得ることができたりします。ここでは、以下の条件を追加してみます。

  • 各数値は、少なくとも 1 つは各行に存在する。
  • 各数値は、少なくとも 1 つは各列に存在する。

プログラムで書くとこんな感じです。

for (var v = 1; v <= R; ++v) {
    // Each number appears at least once in each row. (Row - definedness)
    for (var j = 0; j < R; ++j) {
        s.addClause(R.times().map { => getVarNum(_1, j, v) });
    }
    // Each number appears at least once in each column. (Column - definedness)
    for (var i = 0; i < R; ++i) {
        s.addClause(R.times().map { => getVarNum(i, _1, v) });
    }
}

これを加えると、$25 \times 25$ の数独も 1 秒未満で答えが得られました!

+----------------+----------------+----------------+----------------+----------------+
|  . 12  .  .  . |  .  .  .  .  . |  .  .  .  9  . |  .  . 15  .  . | 22  .  .  .  . |
|  .  .  .  .  . |  .  9  . 19  . |  .  . 10 11  . |  .  .  .  .  . |  .  .  .  .  . |
|  .  4  . 22  . |  .  .  .  .  . |  .  .  .  .  . |  .  . 12  .  . | 20 15  1  .  . |
| 16  1 20 15  . |  .  .  .  .  . |  .  .  .  .  . | 14  .  4  . 22 | 12 25  .  .  . |
|  .  .  .  .  . |  .  7  2 11  . | 23  . 19  8  . |  .  .  . 13  . |  .  .  .  .  . |
+----------------+----------------+----------------+----------------+----------------+
| 13  .  8  .  2 |  .  .  .  .  . |  .  .  7 23  6 |  .  9  . 19 11 |  .  .  .  .  . |
|  .  .  .  . 23 |  .  .  .  . 16 |  .  .  .  .  . |  .  .  .  .  . |  1  .  .  .  . |
|  7  .  .  . 10 |  3  .  .  .  . |  .  .  9 19  . |  . 13  . 23  . |  .  .  .  5  . |
|  .  .  .  .  . | 15  .  .  . 22 |  .  .  .  .  . |  .  .  .  .  . | 25 20  .  .  . |
|  .  .  .  .  . | 12  . 14  1 25 |  .  .  .  .  . |  .  .  3  .  . | 16  4 15  .  . |
+----------------+----------------+----------------+----------------+----------------+
|  .  .  .  .  . |  . 19  9  .  . |  .  . 13  7  . |  .  .  .  5  . |  .  .  . 23 10 |
|  . 22  . 25 17 |  .  .  .  .  . |  .  .  .  .  . | 12  . 20  .  . |  .  .  .  .  . |
|  . 20 12 16  . |  .  .  .  .  . |  .  .  .  . 14 | 15 22  1  . 25 |  .  .  .  .  . |
|  . 15  .  .  . |  . 11  .  .  . |  .  .  .  .  . |  .  . 16  .  . |  .  .  .  9  . |
|  .  .  .  1  . |  . 10  . 23  . |  .  .  .  . 18 |  .  .  .  .  . |  .  .  .  .  8 |
+----------------+----------------+----------------+----------------+----------------+
| 10  .  .  .  8 |  . 13  .  5  . |  .  .  .  .  . |  . 19  . 11 23 |  .  .  .  6  . |
|  .  .  . 17  7 |  .  .  .  .  . |  .  .  .  .  1 |  .  .  .  .  . |  4 22  .  .  . |
|  .  .  .  . 11 |  . 23  .  .  . |  .  .  .  . 20 |  .  .  .  2  . | 14  .  .  .  . |
| 19  . 23  .  5 |  .  8  .  9  . |  . 21  .  .  . |  . 10  .  7  . |  .  .  .  .  . |
|  .  3  .  .  . |  .  .  .  .  . | 25  4  .  . 12 |  .  .  .  .  . | 15  1 16  .  . |
+----------------+----------------+----------------+----------------+----------------+
|  .  .  .  .  . |  .  .  .  . 15 |  . 12  .  . 25 |  1  . 22  .  . |  3  .  .  .  . |
| 23  .  .  . 19 |  .  2  .  .  . |  .  .  .  .  . |  .  .  . 10  . |  .  .  .  7 11 |
|  .  .  . 18  . |  .  .  .  .  . |  . 20  .  .  . |  .  .  .  .  . |  .  .  .  .  . |
|  .  .  .  .  . |  .  .  .  .  4 | 14 15  .  . 22 |  .  .  .  .  . |  .  .  . 10  . |
| 11  .  .  .  9 |  .  .  .  .  . |  .  .  .  .  . |  .  .  .  .  . |  .  .  . 19  . |
+----------------+----------------+----------------+----------------+----------------+

+----------------+----------------+----------------+----------------+----------------+
|  8 12 11 10 18 | 14 25  4 16 24 | 20 17  1  9 21 | 19  5 15  6  2 | 22 23  7  3 13 |
|  2 23 13  7 25 | 22  9 15 19 20 | 12 24 10 11  4 | 17 16 18  1  3 |  8  6  5 14 21 |
|  9  4 19 22 21 | 13  3 18 17  5 | 16  6 25 14  7 | 23 11 12  8 10 | 20 15  1 24  2 |
| 16  1 20 15 24 | 10 21  6  8 23 |  5  2 18  3 13 | 14  7  4  9 22 | 12 25 11 17 19 |
| 14  5 17  6  3 |  1  7  2 11 12 | 23 22 19  8 15 | 21 25 24 13 20 | 10  9 18 16  4 |
+----------------+----------------+----------------+----------------+----------------+
| 13 25  8  4  2 | 18 20 21 24 10 | 15  1  7 23  6 | 16  9  5 19 11 | 17  3 14 22 12 |
| 22 24  3 21 23 |  9 17 19 13 16 | 18 14 20 25  5 |  6 12  2 15  4 |  1 10  8 11  7 |
|  7 14 15 12 10 |  3  4  8  2 11 | 22 16  9 19 17 | 20 13 25 23  1 |  6 24 21  5 18 |
| 17 11 16  5  1 | 15  6 23  7 22 |  3 13  4 12  8 | 18 21 10 14 24 | 25 20 19  2  9 |
|  6  9 18 19 20 | 12  5 14  1 25 |  2 10 21 24 11 |  7 17  3 22  8 | 16  4 15 13 23 |
+----------------+----------------+----------------+----------------+----------------+
|  3  8  2 11  4 | 20 19  9 15 18 |  1 25 13  7 16 | 24  6 14  5 17 | 21 12 22 23 10 |
|  5 22 10 25 17 |  2  1  3  4 13 |  8 11  6 21 19 | 12 23 20 18  9 |  7 14 24 15 16 |
| 18 20 12 16 13 |  8 24  5  6  7 |  9 23 17 10 14 | 15 22  1 21 25 | 19 11  2  4  3 |
| 24 15  7 23 14 | 21 11 25 12 17 |  4  5 22 20  2 | 10  8 16  3 19 | 13 18  6  9  1 |
| 21 19  9  1  6 | 16 10 22 23 14 | 24  3 12 15 18 | 11  2  7  4 13 |  5 17 20 25  8 |
+----------------+----------------+----------------+----------------+----------------+
| 10 16  1 14  8 | 25 13 12  5  2 | 17 18 15 22 24 |  4 19 21 11 23 |  9  7  3  6 20 |
| 12  2 25 17  7 |  6 15 24 18 21 | 10 19 11 13  1 |  3 14  9 20 16 |  4 22 23  8  5 |
| 15 21  4 13 11 | 17 23 16 22  3 |  7  9  8  5 20 | 25  1  6  2 12 | 14 19 10 18 24 |
| 19 18 23 24  5 |  4  8 20  9  1 |  6 21 14 16  3 | 22 10 17  7 15 | 11  2 13 12 25 |
| 20  3  6  9 22 | 11 14  7 10 19 | 25  4 23  2 12 | 13 18  8 24  5 | 15  1 16 21 17 |
+----------------+----------------+----------------+----------------+----------------+
|  4 10  5  8 16 | 23 18 11 21 15 | 19 12  2  6 25 |  1 24 22 17  7 |  3 13  9 20 14 |
| 23 17 22 20 19 | 24  2  1 25  6 | 21  8  3  4  9 |  5 15 13 10 14 | 18 16 12  7 11 |
| 25 13 21 18 15 |  7 12 10 14  9 | 11 20 24 17 23 |  8  3 19 16  6 |  2  5  4  1 22 |
|  1  7 24  2 12 | 19 16 13  3  4 | 14 15  5 18 22 |  9 20 11 25 21 | 23  8 17 10  6 |
| 11  6 14  3  9 |  5 22 17 20  8 | 13  7 16  1 10 |  2  4 23 12 18 | 24 21 25 19 15 |
+----------------+----------------+----------------+----------------+----------------+

尚、サンプルプログラムでは答えに色を付けて、元々あった数値と区別できるようにしています。こんな感じです。

image.png

バージョン依存性チェックを作ってみた

さて、当初の目的の本命です。使わないかもしれませんが。

製品(プロダクト)とバージョンの組合せを変数にして、問題を解かせて組合せとして真となるかを判断するのは一緒です。番号付けが登録順で対処する感じですが、まぁ考え方は一緒です。ただ、バージョンチェックに関していうとこれまでと違うところがあります。それは 偽となったときに何が悪かったのか を教えてあげないといけないのです。どの版数とどの版数がコンフリクトしているのか、と。

これは、これまでのやり方だけではできません。ここでは picosat の機能を使って実現しました。picosat には MUS(Minimally Unsatisfiable Subformulas)というものを見つける仕組みがあり、これを使います。

バージョンチェックには特別に用意した VersionSatisfiablity クラスを使います。

var vs = new VersionSatisfiablity();

製品 X のバージョン 0.0.1 がターゲットです。製品 A, B, Z はそれぞれバージョン 0.0.1 と 0.0.2 が存在します。

var X = vs.addProduct("X")
    .addVersion("0.0.1", true);
var A = vs.addProduct("A")
    .addVersion("0.0.1")
    .addVersion("0.0.2");
var B = vs.addProduct("B")
    .addVersion("0.0.1")
    .addVersion("0.0.2");
var Z = vs.addProduct("Z")
    .addVersion("0.0.1")
    .addVersion("0.0.2");

ここで、製品 A, B, Z の依存関係を定義します。ここでは単純に同じバージョンで依存する感じで A と B が Z に依存するものとします。

A("0.0.1").dependsOn(Z("0.0.1"));
A("0.0.2").dependsOn(Z("0.0.2"));

B("0.0.1").dependsOn(Z("0.0.1"));
B("0.0.2").dependsOn(Z("0.0.2"));

ここで、X が A と B に依存し、どちらも同じ版数に依存していれば問題無いですね。やってみましょう。

X("0.0.1").dependsOn(A("0.0.2"));
X("0.0.1").dependsOn(B("0.0.2"));
tryit();

ちなみに tryit() は結果を出すものですが、その定義は重要ではないので後程お見せします。今回は結果だけ。ちゃんと定義した依存関係の版数で問題無し、という結果になりました。

1: [{
    "index": 1,
    "isFix": 1,
    "name": "X",
    "version": "0.0.1"
}, {
    "index": 3,
    "isFix": null,
    "name": "A",
    "version": "0.0.2"
}, {
    "index": 5,
    "isFix": null,
    "name": "B",
    "version": "0.0.2"
}, {
    "index": 7,
    "isFix": null,
    "name": "Z",
    "version": "0.0.2"
}]

条件を変えて、B の 0.0.1 に依存するようにしてみましょう。

X("0.0.1").dependsOn(A("0.0.2"));
X("0.0.1").dependsOn(B("0.0.1"));
tryit();

こうなりました。

Unsatisfiable - Conflict here
- X is v0.0.1
- Z is v0.0.1 => Z is NOT v0.0.2
- A is v0.0.2 => Z is v0.0.2
- B is v0.0.1 => Z is v0.0.1
- X is v0.0.1 => A is v0.0.2
- X is v0.0.1 => B is v0.0.1

これは、以下の組合せが満たせない、と伝えています。今回はほぼほぼ全部出てしまってますが、条件が複雑になると満たせなかった条件だけが出力されます。

  • X が 0.0.1 であること
  • Z の 0.0.1 と 0.0.2 が排他であること
  • A が 0.0.2 のとき Z が 0.0.2 であること
  • B が 0.0.1 のとき Z が 0.0.1 であること
  • X が 0.0.1 のとき A が 0.0.2 であること
  • X が 0.0.1 のとき B が 0.0.1 であること

おわりに

せっかく SAT ソルバー入れてみたのでどこかで使えるといいな。パズルを解かせるのはちょっと面白かった。

そして本題の Package Manager はどうやって実現しようか考え中です。

P.S. tryit() の定義

function msg(a) {
    if (a.not) {
        return ("%1% is NOT v%2%" % a.value.name % a.value.version).format();
    } else {
        return ("%1% is v%2%" % a.value.name % a.value.version).format();
    }
}
function error(item) {
    switch (item.length()) {
    when 1:
        System.println("- ", msg(item[0]));
    when 2:
        item[0].not = !item[0].not; // (!A || B) means (A => B)
        System.println("- ", msg(item[0]), " => ", msg(item[1]));
    else:
        System.println("- ", item.map { => msg(_1) }.join(', or\n  '));
    }
}

function tryit() {
    var count = 0;
    for (var e in vs) {
        System.println("%d: " % ++count, e.toJsonString(true));
    }
    if (count == 0) {
        System.println("Unsatisfiable - Conflict here");
        vs.getConflict().each { => error(_1) };
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SATソルバ―で遊んでみた(8 Queens/数独)

はじめに

Kinx で Package Manager を作ろうと思い、バージョン依存性チェックを SAT ソルバ―にやらせようと思いました。しかし複数バージョン共存できる方が良いのでは、と、バージョン依存性チェックいらないかも、と思い始めた今日この頃。結局作ったけど使わないかも、な感じになってます。

とはいえ、せっかく実装したのでいくつか遊んでみました。8 Queens と数独を解いてみます。

ちなみに Kinx とは↓コレです。

また、最後に(これもせっかくなので)バージョン依存性チェックプログラムを載せておきます。使わないかもですが。

SAT ソルバ―とは

ご注意: SAT ソルバーは v1.0.0 のリリース版には含まれていません。最新の master ブランチに含まれています。

充足可能性問題を解く機械(プログラム)。Wikipedia が詳しいのでご参考。Kinx では picosat をバックエンドに使いました。

簡単に言うと、$(x_1 \lor x_2) \land (x_1 \lor \lnot x_2) \land (\lnot x_1 \lor \lnot x_2)$ みたいな論理式があって、この式全体が真となる各変数の真偽値の組合せを見つけるというものです。例えばこの例の場合、$x_1 = $ 真、$x_2 = $ 偽とすれば全体が真となるのでこの組合せは解の一つです。

SAT ソルバ―を使う、というのは、与えられた問題を上記のような論理式で表し、SAT ソルバーに解かせ、出てきた結果を解釈する、ということを行うこと、意味します。

ちなみに、論理式の形は $(A \lor B \lor \ldots) \land (X \lor Y \lor \ldots) \land ...$ と OR でつながった式を AND でつなげた式として表現します。また、ある命題を解く際に、$A \Rightarrow B$ ($A$ ならば $B$)の条件を考えるシーンがよくありますが、これは論理式として同一の解となる $\lnot A \lor B$ で置き換えて考えます。

Kinx で使うには、以下のように using します。また、ライブラリは Satisfiablity クラスで、インスタンス化しておきましょう。

using SatisfiablitySolver;
var s = new Satisfiablity();

8 Queens を解いてみた

8 Queens を解いてみましょう。8 Queens に関しても Wikipedia が詳しいのでご参考。

SAT ソルバ―を使うということは、8 Queens の問題を論理式で表すということです。どう表せばよいでしょう。

SAT ソルバーでは、各変数を整数で管理します。そして、変数 1 が真の場合は +1、偽の場合は -1 として符号で判断できるようにします。従って、全ての状態を変数として番号を付け、ある変数(番号)が真(プラス)の時に他の変数(番号)がどうなるのかを論理式で表すようにします。こんな感じです。

  • 変数
    • すべてのマスを表す変数を仮定する。8 x 8 マスなので、64 個の変数がある。
    • 変数が真の場合は Queen が存在するものとする。
  • 条件(制約)
    • 行(列でも良い)の中に少なくとも 1 つは Queen が存在する。
    • ある $(i, j)$ 座標に Queen があるとした場合、その上下左右、ナナメ全てに Queen は存在しない。

この時、例えば最初の条件として 1 行目を考えます。1行目を表す変数を $x_1$, $x_2$, $x_3$, $\ldots$, $x_8$ とすると、$(x_1 \lor x_2 \lor x_3 \lor x_4 \lor x_5 \lor x_6 \lor x_7 \lor x_8)$ となります。2 ~ 8 行目も同じように条件が書けます。

プログラム的には、これを配列で表して、[1, 2, 3, 4, 5, 6, 7, 8] と扱います。全部真なのでプラスです。この 1 つの OR の式を節(Clause)と言います。複数の節を作って条件を絞っていきます。

まず、分かりやすくするために、ある $(i, j)$ の変数の番号を返す関数を作っておきましょう。ここで N は 8 です。+1 するのは、変数は 1 始まりだからです。

const N = 8;

function getVarNum(i, j) {
    return (i + j * N) + 1;
}

では各行に少なくとも 1 つは Queen が存在する、というのを書くと以下のようになります。これで [1, 2, 3, 4, 5, 6, 7, 8][57, 58, 59, 60, 61, 62, 63, 64] までの 8 つの節が登録されます。

for (var jx = 0; jx < N; ++jx) {
    s.addClause(N.times().map { => getVarNum(_1, jx) });
}

次の条件「ある $(i, j)$ 座標に Queen があるとした場合、その上下左右、ナナメ全てに Queen は存在しない。」はどう表しましょう。これは、$A \Rightarrow B$ の形で個別に指定していきます。例えば、「$(0, 0)$ にあれば $(1, 0)$ には存在しない」は変数で表すと、$x_1 \Rightarrow \lnot x_2$ なので、$\lnot x_1 \lor \lnot x_2$ です。つまり、節としては [-1, -2] になります。すべてのマスで上下左右、ナナメそれぞれ同じように節を追加していきます。

ところで、実は $x_1 \Rightarrow \lnot x_2$ は $x_2 \Rightarrow \lnot x_1$ と同じことを表しています。なぜなら OR は交換可能なのでどちらも $\lnot x_1 \lor \lnot x_2$ だからです。ということで、左上から順にスキャンする場合、あるマス $(i, j)$ の条件を調べる際は既に設定した節と同じものを設定する必要はありません。つまり、右下左下、だけをスキャンして節を登録すればよいのです。

これをプログラムにすると、以下のようになります。これをすべてのマスで呼びます。

function addClauses(s, i, j) {
    const ijQ = getVarNum(i, j);

    /* right */
    for (var ix = i + 1; ix < N; ++ix) {
        s.addClause([-ijQ, -getVarNum(ix, j)]);
    }

    /* down */
    for (var jx = j + 1; jx < N; ++jx) {
        s.addClause([-ijQ, -getVarNum(i, jx)]);
    }

    /* right-down */
    for (var ix = i + 1, jx = j + 1; ix < N && jx < N; ++ix, ++jx) {
        s.addClause([-ijQ, -getVarNum(ix, jx)]);
    }

    /* left-down */
    for (var ix = i - 1, jx = j + 1; ix >= 0 && jx < N; --ix, ++jx) {
        s.addClause([-ijQ, -getVarNum(ix, jx)]);
    }
}

for (var ix = 0; ix < N; ++ix) {
    for (var jx = 0; jx < N; ++jx) {
        addClauses(s, ix, jx);
    }
}

さて、ここまで準備できたら SAT ソルバーから解を求めます。Satisfiablity クラスはイテレータブルにしてあるので、以下のように for-in で回せます。

function display(e) {
    System.print("\nSolution %d\n" % ++solution);
    var board = [];
    for (var ijQ = 0, l = e.length(); ijQ < l; ++ijQ) {
        var i = ijQ % N;
        var j = Integer.parseInt(ijQ / N);
        board[i][j] = e[ijQ] > 0;
    }
    for (var jx = 0; jx < N; ++jx) {
        for (var ix = 0; ix < N; ++ix) {
            System.print(board[ix][jx] ? " Q" : " .");
        }
        System.println("");
    }
}

for (var e in s) {
    display(e);
}

実行すると、無事 92 個の解が表示されました。

$ ./kinx examples/sat_nqueens.kx

Solution 1
 . . . . . . . Q
 . Q . . . . . .
 . . . . Q . . .
 . . Q . . . . .
 Q . . . . . . .
 . . . . . . Q .
 . . . Q . . . .
 . . . . . Q . .

Solution 2
 . . . Q . . . .
 . . . . . . . Q
 . . . . Q . . .
 . . Q . . . . .
 Q . . . . . . .
 . . . . . . Q .
 . Q . . . . . .
 . . . . . Q . .

/* 省略 */

Solution 92
 . . . Q . . . .
 . . . . . . Q .
 . . . . Q . . .
 . Q . . . . . .
 . . . . . Q . .
 Q . . . . . . .
 . . Q . . . . .
 . . . . . . . Q

数独を解いてみた

次に数独を解いてみます。数独に関してもまた Wikipedia が詳しいのでご参考。

数独は変数の数が増えます。縦横に加えてマスごとに 1 ~ 9 までの数値を取るといった奥行的な(立体的な)イメージを持つ必要がありますが、考え方は一緒です。

各マスが 1 ~ 9 までの数値を取るので、マスごとに 9 個の状態を持たせます。つまり、左上のマスが 1 であるとき、2 であるとき、... と全て異なる変数として扱います。例えば、$R \times R$ の数独において $(i, j)$ が $v$ であるときの変数の番号を $(i + j \times R) \times R + v$ として定義します。$v$ が 1 から始まるので 1 加える必要はありません。例えば、変数 1 が真であれば、左上のマスは 1 です。変数 9 が真であれば、左上のマスは 9 であることを表します。変数 10 はマスが移って、左上の 1 つ右(左から 2 番目)のマスが 1 ということになります。

では早速先ほどと同様に、変数の番号を返す関数から定義してみましょう。$R \times R$ の数独です。

function getVarNum(i, j, v) {
    return ((i + j * R) * R) + v;
}

次に制約条件です。「ミニ区画」と言ってますが、$9 \times 9$ の数独の場合、その中に 9 つ存在する $3 \times 3$ の領域のことを意味しています。この 3 を $N$ とします。つまり、$R = N^{2}$ です。

  • あるマス $(i, j)$ には 1 ~ 9 のいずれかの数値が入る。
  • あるマス $(i, j)$ が $v$ であるとき、同じ $(i, j)$ のマスには $v$ 以外の数値は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、同じ行に $v$ は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、同じ列に $v$ は存在しない。
  • あるマス $(i, j)$ が $v$ であるとき、そのマスを含むミニ区画に $v$ は存在しない。

これをプログラムで書くと、次のようになります。

function addClauses(s, i, j, v) {
    const ijv = getVarNum(i, j, v);

    // There is at most one number in each cell. (Cell - uniqueness)
    for (var vi = 1; vi <= R; ++vi) {
        if (v != vi) {
            s.addClause([-ijv, -getVarNum(i, j, vi)]);
        }
    }

    // Each number appears at most once in each row. (Row - uniqueness)
    for (var ix = i + 1; ix < R; ++ix) {
        s.addClause([-ijv, -getVarNum(ix, j, v)]);
    }

    // Each number appears at most once in each column. (Column - uniqueness)
    for (var jx = j + 1; jx < R; ++jx) {
        s.addClause([-ijv, -getVarNum(i, jx, v)]);
    }

    var si = Integer.parseInt(i / N) * N;  // i as a left top in sub square.
    var sj = Integer.parseInt(j / N) * N;  // j as a left top in sub square.
    var ei = si + N;
    var ej = sj + N;
    // Each number appears at most once in each block. (Block - uniqueness)
    for (var ix = si; ix < ei; ++ix) {
        for (var jx = sj; jx < ej; ++jx) {
            const ijv2 = getVarNum(ix, jx, v);
            if (ijv2 > ijv) {
                s.addClause([-ijv, -ijv2]);
            }
        }
    }
}

for (var j = 0; j < R; ++j) {
    for (var i = 0; i < R; ++i) {
        const ijv = ((i + j * R) * R);
        var v = test[j][i];
        if (v == 0) {
            // There is at least one number in each cell. (Cell - definedness)
            s.addClause(R.times().map { => ijv + (_1 + 1) });
        } else {
            s.addClause([ ijv + v ]);
        }
        R.times().each { => addClauses(s, i, j, (_1 + 1)) };
    }
}

実際に $9 \times 9$ の数独を解いてみましょう。

+----------+----------+----------+
|  8  .  . |  .  .  . |  .  .  . |
|  .  .  3 |  6  .  . |  .  .  . |
|  .  7  . |  .  9  . |  2  .  . |
+----------+----------+----------+
|  .  5  . |  .  .  7 |  .  .  . |
|  .  .  . |  .  4  5 |  7  .  . |
|  .  .  . |  1  .  . |  .  3  . |
+----------+----------+----------+
|  .  .  1 |  .  .  . |  .  6  8 |
|  .  .  8 |  5  .  . |  .  1  . |
|  .  9  . |  .  .  . |  4  .  . |
+----------+----------+----------+

+----------+----------+----------+
|  8  1  2 |  7  5  3 |  6  4  9 |
|  9  4  3 |  6  8  2 |  1  7  5 |
|  6  7  5 |  4  9  1 |  2  8  3 |
+----------+----------+----------+
|  1  5  4 |  2  3  7 |  8  9  6 |
|  3  6  9 |  8  4  5 |  7  2  1 |
|  2  8  7 |  1  6  9 |  5  3  4 |
+----------+----------+----------+
|  5  2  1 |  9  7  4 |  3  6  8 |
|  4  3  8 |  5  2  6 |  9  1  7 |
|  7  9  6 |  3  1  8 |  4  5  2 |
+----------+----------+----------+

解けました。

ところが、これでもいいのですが、実は $25 \times 25$ の数独で返ってきません。実は冗長であっても節を増やしたほうが余計な条件を枝刈りして速く解を得ることができたりします。ここでは、以下の条件を追加してみます。

  • 各数値は、少なくとも 1 つは各行に存在する。
  • 各数値は、少なくとも 1 つは各列に存在する。

プログラムで書くとこんな感じです。

for (var v = 1; v <= R; ++v) {
    // Each number appears at least once in each row. (Row - definedness)
    for (var j = 0; j < R; ++j) {
        s.addClause(R.times().map { => getVarNum(_1, j, v) });
    }
    // Each number appears at least once in each column. (Column - definedness)
    for (var i = 0; i < R; ++i) {
        s.addClause(R.times().map { => getVarNum(i, _1, v) });
    }
}

これを加えると、$25 \times 25$ の数独も 1 秒未満で答えが得られました!

+----------------+----------------+----------------+----------------+----------------+
|  . 12  .  .  . |  .  .  .  .  . |  .  .  .  9  . |  .  . 15  .  . | 22  .  .  .  . |
|  .  .  .  .  . |  .  9  . 19  . |  .  . 10 11  . |  .  .  .  .  . |  .  .  .  .  . |
|  .  4  . 22  . |  .  .  .  .  . |  .  .  .  .  . |  .  . 12  .  . | 20 15  1  .  . |
| 16  1 20 15  . |  .  .  .  .  . |  .  .  .  .  . | 14  .  4  . 22 | 12 25  .  .  . |
|  .  .  .  .  . |  .  7  2 11  . | 23  . 19  8  . |  .  .  . 13  . |  .  .  .  .  . |
+----------------+----------------+----------------+----------------+----------------+
| 13  .  8  .  2 |  .  .  .  .  . |  .  .  7 23  6 |  .  9  . 19 11 |  .  .  .  .  . |
|  .  .  .  . 23 |  .  .  .  . 16 |  .  .  .  .  . |  .  .  .  .  . |  1  .  .  .  . |
|  7  .  .  . 10 |  3  .  .  .  . |  .  .  9 19  . |  . 13  . 23  . |  .  .  .  5  . |
|  .  .  .  .  . | 15  .  .  . 22 |  .  .  .  .  . |  .  .  .  .  . | 25 20  .  .  . |
|  .  .  .  .  . | 12  . 14  1 25 |  .  .  .  .  . |  .  .  3  .  . | 16  4 15  .  . |
+----------------+----------------+----------------+----------------+----------------+
|  .  .  .  .  . |  . 19  9  .  . |  .  . 13  7  . |  .  .  .  5  . |  .  .  . 23 10 |
|  . 22  . 25 17 |  .  .  .  .  . |  .  .  .  .  . | 12  . 20  .  . |  .  .  .  .  . |
|  . 20 12 16  . |  .  .  .  .  . |  .  .  .  . 14 | 15 22  1  . 25 |  .  .  .  .  . |
|  . 15  .  .  . |  . 11  .  .  . |  .  .  .  .  . |  .  . 16  .  . |  .  .  .  9  . |
|  .  .  .  1  . |  . 10  . 23  . |  .  .  .  . 18 |  .  .  .  .  . |  .  .  .  .  8 |
+----------------+----------------+----------------+----------------+----------------+
| 10  .  .  .  8 |  . 13  .  5  . |  .  .  .  .  . |  . 19  . 11 23 |  .  .  .  6  . |
|  .  .  . 17  7 |  .  .  .  .  . |  .  .  .  .  1 |  .  .  .  .  . |  4 22  .  .  . |
|  .  .  .  . 11 |  . 23  .  .  . |  .  .  .  . 20 |  .  .  .  2  . | 14  .  .  .  . |
| 19  . 23  .  5 |  .  8  .  9  . |  . 21  .  .  . |  . 10  .  7  . |  .  .  .  .  . |
|  .  3  .  .  . |  .  .  .  .  . | 25  4  .  . 12 |  .  .  .  .  . | 15  1 16  .  . |
+----------------+----------------+----------------+----------------+----------------+
|  .  .  .  .  . |  .  .  .  . 15 |  . 12  .  . 25 |  1  . 22  .  . |  3  .  .  .  . |
| 23  .  .  . 19 |  .  2  .  .  . |  .  .  .  .  . |  .  .  . 10  . |  .  .  .  7 11 |
|  .  .  . 18  . |  .  .  .  .  . |  . 20  .  .  . |  .  .  .  .  . |  .  .  .  .  . |
|  .  .  .  .  . |  .  .  .  .  4 | 14 15  .  . 22 |  .  .  .  .  . |  .  .  . 10  . |
| 11  .  .  .  9 |  .  .  .  .  . |  .  .  .  .  . |  .  .  .  .  . |  .  .  . 19  . |
+----------------+----------------+----------------+----------------+----------------+

+----------------+----------------+----------------+----------------+----------------+
|  8 12 11 10 18 | 14 25  4 16 24 | 20 17  1  9 21 | 19  5 15  6  2 | 22 23  7  3 13 |
|  2 23 13  7 25 | 22  9 15 19 20 | 12 24 10 11  4 | 17 16 18  1  3 |  8  6  5 14 21 |
|  9  4 19 22 21 | 13  3 18 17  5 | 16  6 25 14  7 | 23 11 12  8 10 | 20 15  1 24  2 |
| 16  1 20 15 24 | 10 21  6  8 23 |  5  2 18  3 13 | 14  7  4  9 22 | 12 25 11 17 19 |
| 14  5 17  6  3 |  1  7  2 11 12 | 23 22 19  8 15 | 21 25 24 13 20 | 10  9 18 16  4 |
+----------------+----------------+----------------+----------------+----------------+
| 13 25  8  4  2 | 18 20 21 24 10 | 15  1  7 23  6 | 16  9  5 19 11 | 17  3 14 22 12 |
| 22 24  3 21 23 |  9 17 19 13 16 | 18 14 20 25  5 |  6 12  2 15  4 |  1 10  8 11  7 |
|  7 14 15 12 10 |  3  4  8  2 11 | 22 16  9 19 17 | 20 13 25 23  1 |  6 24 21  5 18 |
| 17 11 16  5  1 | 15  6 23  7 22 |  3 13  4 12  8 | 18 21 10 14 24 | 25 20 19  2  9 |
|  6  9 18 19 20 | 12  5 14  1 25 |  2 10 21 24 11 |  7 17  3 22  8 | 16  4 15 13 23 |
+----------------+----------------+----------------+----------------+----------------+
|  3  8  2 11  4 | 20 19  9 15 18 |  1 25 13  7 16 | 24  6 14  5 17 | 21 12 22 23 10 |
|  5 22 10 25 17 |  2  1  3  4 13 |  8 11  6 21 19 | 12 23 20 18  9 |  7 14 24 15 16 |
| 18 20 12 16 13 |  8 24  5  6  7 |  9 23 17 10 14 | 15 22  1 21 25 | 19 11  2  4  3 |
| 24 15  7 23 14 | 21 11 25 12 17 |  4  5 22 20  2 | 10  8 16  3 19 | 13 18  6  9  1 |
| 21 19  9  1  6 | 16 10 22 23 14 | 24  3 12 15 18 | 11  2  7  4 13 |  5 17 20 25  8 |
+----------------+----------------+----------------+----------------+----------------+
| 10 16  1 14  8 | 25 13 12  5  2 | 17 18 15 22 24 |  4 19 21 11 23 |  9  7  3  6 20 |
| 12  2 25 17  7 |  6 15 24 18 21 | 10 19 11 13  1 |  3 14  9 20 16 |  4 22 23  8  5 |
| 15 21  4 13 11 | 17 23 16 22  3 |  7  9  8  5 20 | 25  1  6  2 12 | 14 19 10 18 24 |
| 19 18 23 24  5 |  4  8 20  9  1 |  6 21 14 16  3 | 22 10 17  7 15 | 11  2 13 12 25 |
| 20  3  6  9 22 | 11 14  7 10 19 | 25  4 23  2 12 | 13 18  8 24  5 | 15  1 16 21 17 |
+----------------+----------------+----------------+----------------+----------------+
|  4 10  5  8 16 | 23 18 11 21 15 | 19 12  2  6 25 |  1 24 22 17  7 |  3 13  9 20 14 |
| 23 17 22 20 19 | 24  2  1 25  6 | 21  8  3  4  9 |  5 15 13 10 14 | 18 16 12  7 11 |
| 25 13 21 18 15 |  7 12 10 14  9 | 11 20 24 17 23 |  8  3 19 16  6 |  2  5  4  1 22 |
|  1  7 24  2 12 | 19 16 13  3  4 | 14 15  5 18 22 |  9 20 11 25 21 | 23  8 17 10  6 |
| 11  6 14  3  9 |  5 22 17 20  8 | 13  7 16  1 10 |  2  4 23 12 18 | 24 21 25 19 15 |
+----------------+----------------+----------------+----------------+----------------+

尚、サンプルプログラムでは答えに色を付けて、元々あった数値と区別できるようにしています。こんな感じです。

image.png

バージョン依存性チェックを作ってみた

さて、当初の目的の本命です。使わないかもしれませんが。

製品(プロダクト)とバージョンの組合せを変数にして、問題を解かせて組合せとして真となるかを判断するのは一緒です。番号付けが登録順で対処する感じですが、まぁ考え方は一緒です。ただ、バージョンチェックに関していうとこれまでと違うところがあります。それは 偽となったときに何が悪かったのか を教えてあげないといけないのです。どの版数とどの版数がコンフリクトしているのか、と。

これは、これまでのやり方だけではできません。ここでは picosat の機能を使って実現しました。picosat には MUS(Minimally Unsatisfiable Subformulas)というものを見つける仕組みがあり、これを使います。

バージョンチェックには特別に用意した VersionSatisfiablity クラスを使います。

var vs = new VersionSatisfiablity();

製品 X のバージョン 0.0.1 がターゲットです。製品 A, B, Z はそれぞれバージョン 0.0.1 と 0.0.2 が存在します。

var X = vs.addProduct("X")
    .addVersion("0.0.1", true);
var A = vs.addProduct("A")
    .addVersion("0.0.1")
    .addVersion("0.0.2");
var B = vs.addProduct("B")
    .addVersion("0.0.1")
    .addVersion("0.0.2");
var Z = vs.addProduct("Z")
    .addVersion("0.0.1")
    .addVersion("0.0.2");

ここで、製品 A, B, Z の依存関係を定義します。ここでは単純に同じバージョンで依存する感じで A と B が Z に依存するものとします。

A("0.0.1").dependsOn(Z("0.0.1"));
A("0.0.2").dependsOn(Z("0.0.2"));

B("0.0.1").dependsOn(Z("0.0.1"));
B("0.0.2").dependsOn(Z("0.0.2"));

ここで、X が A と B に依存し、どちらも同じ版数に依存していれば問題無いですね。やってみましょう。

X("0.0.1").dependsOn(A("0.0.2"));
X("0.0.1").dependsOn(B("0.0.2"));
tryit();

ちなみに tryit() は結果を出すものですが、その定義は重要ではないので後程お見せします。今回は結果だけ。ちゃんと定義した依存関係の版数で問題無し、という結果になりました。

1: [{
    "index": 1,
    "isFix": 1,
    "name": "X",
    "version": "0.0.1"
}, {
    "index": 3,
    "isFix": null,
    "name": "A",
    "version": "0.0.2"
}, {
    "index": 5,
    "isFix": null,
    "name": "B",
    "version": "0.0.2"
}, {
    "index": 7,
    "isFix": null,
    "name": "Z",
    "version": "0.0.2"
}]

条件を変えて、B の 0.0.1 に依存するようにしてみましょう。

X("0.0.1").dependsOn(A("0.0.2"));
X("0.0.1").dependsOn(B("0.0.1"));
tryit();

こうなりました。

Unsatisfiable - Conflict here
- X is v0.0.1
- Z is v0.0.1 => Z is NOT v0.0.2
- A is v0.0.2 => Z is v0.0.2
- B is v0.0.1 => Z is v0.0.1
- X is v0.0.1 => A is v0.0.2
- X is v0.0.1 => B is v0.0.1

これは、以下の組合せが満たせない、と伝えています。今回はほぼほぼ全部出てしまってますが、条件が複雑になると満たせなかった条件だけが出力されます。

  • X が 0.0.1 であること
  • Z の 0.0.1 と 0.0.2 が排他であること
  • A が 0.0.2 のとき Z が 0.0.2 であること
  • B が 0.0.1 のとき Z が 0.0.1 であること
  • X が 0.0.1 のとき A が 0.0.2 であること
  • X が 0.0.1 のとき B が 0.0.1 であること

おわりに

せっかく SAT ソルバー入れてみたのでどこかで使えるといいな。パズルを解かせるのはちょっと面白かった。

そして本題の Package Manager はどうやって実現しようか考え中です。

P.S. tryit() の定義

function msg(a) {
    if (a.not) {
        return ("%1% is NOT v%2%" % a.value.name % a.value.version).format();
    } else {
        return ("%1% is v%2%" % a.value.name % a.value.version).format();
    }
}
function error(item) {
    switch (item.length()) {
    when 1:
        System.println("- ", msg(item[0]));
    when 2:
        item[0].not = !item[0].not; // (!A || B) means (A => B)
        System.println("- ", msg(item[0]), " => ", msg(item[1]));
    else:
        System.println("- ", item.map { => msg(_1) }.join(', or\n  '));
    }
}

function tryit() {
    var count = 0;
    for (var e in vs) {
        System.println("%d: " % ++count, e.toJsonString(true));
    }
    if (count == 0) {
        System.println("Unsatisfiable - Conflict here");
        vs.getConflict().each { => error(_1) };
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【rails初学者な】ピンとこない英単語たちの意味を知ろう!

知らない英単語たくさん出てきますよね

プログラミングを勉強していると知らない英単語がたくさん出てくる。
ただでさえ難しいプログラミングなのに英単語の意味がわからないと何がどのように働きそうなのんか予想しずらいと思う。

しかし、私の経験から、英単語の意味を確認するとピンとくる事が多いので下の表に単語の意味をまとめた。
今回はrails tutorial に出てくる日常で見かけない言葉を中心に記したい。

よく出てくるけどピンとこない英単語

英単語    意味     
assert 断言する
belong (...に)属す、(...の)ものである
branch 木の枝、部門
commit 受託する、引き渡す
deploy 展開する、配置する
digest 消化する、圧縮して整理する
fix 修理する、壊れたものをなおす感
fixture 定着物、居座った人
merge 統合する、溶け込ませる
migrate 移住する、渡る、大群が移動するニュアンスもある
modify 改造する、そんなに壊れてないけど改良する感
presence 存在、配置
render (…に)する、与える、下す、提出する、give に近いけどもっとお硬い感
unique 唯一の、比類のない
valid 正当な、確かな
validate 有効にする、確認する
yield 産む、もたらす、身を委ねる

プログラミングを行う際にピンときやすい表現を多くしたつもりです。
学習のヒントになったらなによりです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【rails初学者】ピンとこない英単語たちの意味を知ろう!

知らない英単語たくさん出てきますよね

プログラミングを勉強していると知らない英単語がたくさん出てくる。
ただでさえ難しいプログラミングなのに英単語の意味がわからないと何がどのように働きそうなのか予想しずらいと思う。

しかし、私の経験から、英単語の意味を確認するとピンとくる事が多いので下の表に単語の意味をまとめた。
今回はrails tutorial に出てくる日常で見かけない言葉を中心に記したい。

よく出てくるけどピンとこない英単語

英単語    意味      本来の意味、ニュアンス
assert 主張する 断言する、キッパリ言う
belong (...に)属す、(...の)ものである
branch 木の枝、部門
commit 受託する、引き渡す
deploy 展開する、配置する
digest 消化する、圧縮して整理する
fix 修理する、壊れたものをなおす感
fixture 定着物、居座った人
merge 統合する、溶け込ませる
migrate 移住する、渡る 大群が移動するニュアンスもある
modify 改造する そんなに壊れてないけど改良する感
presence 存在
render 描画する、表現する (…に)する、与える、提出する、give に近いけどもっとお硬い感
unique 唯一
valid 正当な、確かな
validate 有効であることを確認する 確認する、有効にする
yield 産む、もたらす、身を委ねる

プログラミングを行う際にピンときやすい表現を多くしたつもりです。
学習のヒントになったらなによりです。

*ご指摘をくださった方、ありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

試験の合格判定プログラム

問題

総合力を重視する paiza 大学の入試では 1 次試験 (英語、数学、理科、国語、地理歴史の 5 科目で各 100 点満点) の成績で2段階選抜を行います。2段階選抜を通過する条件は以下のようになっています。

全科目の合計得点が 350 点以上
理系の受験者の場合は理系 2 科目 (数学、理科) の合計得点が 160 点以上
文系の受験者の場合は文系 2 科目 (国語、地理歴史) の合計得点が 160 点以上
受験者それぞれの各科目の点数が入力されるので、何人2段階選抜を通過できるかを求めてください。

スクリーンショット 2021-04-03 20.27.42.png

受験者 2 は全科目の合計は 350 点以上ですが文系 2 科目の合計が 160 点未満なので不合格。一方受験者 4 は理系 2 科目の合計は 160 点以上ですが全科目の合計が 350 点未満なので不合格となります。

→ 通過人数: 2 人

getsで入力される値は以下の通りです。

5
s 70 78 82 57 74
l 68 81 81 60 78
s 63 76 55 80 75
s 90 100 96 10 10
l 88 78 81 97 93
n = gets.chomp.to_i

ret = 0
n.times do |i|
  arr = gets.chomp.split(' ')
  sl = arr[0]
  scores = arr[1..-1].map(&:to_i)
  sum = scores.inject(0) { |sum, n| sum = sum + n }
  #puts sum

  if sum < 350
    next
  end

  en = scores[0]
  mt = scores[1]
  sc = scores[2]
  jp = scores[3]
  gh = scores[4]

  if sl == "s"
    next if (mt + sc ) < 160
  else
    next if (jp + gh) < 160
  end

  ret = ret + 1
end

puts ret

=> 2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby ハッシュの値の取得方法

ハッシュには3種類の定義方法があります。
①キーに文字列を使う方法
 hash1 = { "sample" => "例" }
②キーにシンボルを使う方法
 hash2 = { :sample => "例" }
③キーにシンボルを使う方法(②よりもシンプル)
 hash3 = { sample: "例" } 

①を使用した場合のハッシュの値の取得の仕方は、ハッシュ["取得したい値のキー"]です。

例)

qiita.rb
favorites = { "fruit" => "いちご", "subject" => "数学" }
puts favorites["fruit"]

②と③を使用した場合のハッシュの値の取得の仕方は、ハッシュ[:取得したい値のキー]です。

例)

qiita.rb
favorites = { :fruit => "いちご", :subject => "数学" }
puts favorites[:subject]
favorites = { fruit:  "いちご", subject: "数学" }
puts favorites[:subject]

ハッシュの定義方法によって、値の取得の仕方が異なります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby】配列の最初の要素を削除するshiftメソッドの使い方

プログラミング勉強日記

2021年4月3日

shiftメソッドとは

 配列の先頭の要素を削除した要素を返すメソッド。ただし、shiftメソッドを使用すると元となる配列自体が変更されるので注意しないといけない。
 引数を指定することもでき、指定した場合にはその個数だけ取り除いてそれを配列で返す。空の配列で指定した場合はnilを返す。

使い方
配列オブジェクト.shift
配列オブジェクト.shift(num)

サンプルプログラム

引数を指定しない場合
# 配列を定義
num = [0, 1, 2, 3, 4, 5]

puts "削除前:#{num}"

# 配列の最初の要素を削除する
puts "削除した要素:#{num.shift}"

puts "削除後#{num}"
実行結果
削除前:[0, 1, 2, 3, 4, 5]
削除した要素:0
削除後:[1, 2, 3, 4, 5]
引数を指定する場合
# 配列を定義
num = [0, 1, 2, 3, 4, 5]

puts "削除前:#{num}"

# 配列の最初の要素を削除する
puts "削除した要素:#{num.shift(2)}"

puts "削除後#{num}"
実行結果
削除前:[0, 1, 2, 3, 4, 5]
削除した要素:[0, 1]
削除後:[2, 3, 4, 5]

参考文献

instance method Array#shift

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6・Docker・MySQLによる環境構築

無事環境構築できたものを自分用に備忘録として残しておく(適宜修正予定)

プラスでよく使うものも込みでまとめておく。
・Bootstrap導入
・Git / Heroku へPush

ファイル用意

ディレクトリを作成し、その中に以下のファイルを用意
Dockerfile
docker-compose.yml
Gemfile
Gemfile.lock
entrypoint.sh

これより以下のファイル内のmyappは自分が作成したディレクトリ名に置き換える

Dockerfile
FROM ruby:2.7.1

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -qq && apt-get install -y nodejs yarn
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

Rails5との変更点は、webpacker導入によるyarnnodeのインストール
credentials:editを使う予定の場合は、build時点で設定すると良さそう。
RUN apt-get install -y vim

Gemfile
source 'https://rubygems.org'
gem 'rails', '6.0.3'
Gemfile.lock
# 空のままで
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
docker-compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    stdin_open: true
    tty: true
volumes:
  mysql-data:
    driver: local

アプリ作成

このコマンドで最後webpackerがインストールされて完了

$ docker-compose run web rails new . --force --no-deps --database=mysql

アプリ生成したら、buildする。最後Successfully ~~と出る

$ docker-compose build

DB作成

config/database.ymlを以下のように変更
また、ここまでの時点でmyappが適切に書き変わっているため、config/database.ymlでは修正する必要は無し。

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch("MYSQL_USERNAME", "root") %>
  password: <%= ENV.fetch("MYSQL_PASSWORD", "password") %>
  host: <%= ENV.fetch("MYSQL_HOST", "db") %>

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

DBを作成する

$ docker-compose run web rake db:migrate

Dockerを起動する

$ docker-compose up

http://localhost:3000にアクセスし、お馴染みのページが表示されれば無事成功。

Bootstrap導入

必要なものをインストール(バージョンは指定しなくても。)

$ yarn add bootstrap@4.4.1 jquery@3.5.1 popper.js@1.16.1

config/webpack/environment.jsに追記

config/webpack/environment.js
const { environment } = require('@rails/webpacker')

const webpack = require('webpack')
environment.plugins.append('Provide', new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: ['popper.js', 'default']
}))

module.exports = environment

app/javascript/packs/application.jsに追記

app/javascript/packs/application.js
require("bootstrap/dist/js/bootstrap")

application.cssの拡張子cssをscssに変更

最後にapp/assets/stylesheets/application.scssに追記

app/assets/stylesheets/application.scss
/*
 *= require_tree .
 *= require_self
 */

@import "bootstrap/scss/bootstrap";

bootstrap jquery popper.jsをインストールしてから以上の記述だと、Herokuにデプロイした際にも適用された。

--

しかし以下のやり方だと、Herokuで適用されなかった。一応残しておく
インストールしたファイルをwebpackerの管理下に加える

app/javascript/packs/application.js
~省略~
import "bootstrap"
import "bootstrap/scss/bootstrap.scss"

--

正常に適用されているか確認するために、トップページを用意する。

$ rails g controller welcome index

ルーティングを設定

config/routes.rb
Rails.application.routes.draw do
  root "welcome#index"
end

viewを記述

app/views/layouts/application.html.erb
~省略~
  <body>
    <header class="navbar navbar-expand-sm navbar-light bg-light">
      <div class="container">
        <%= link_to "サービス名", root_path, class: "navbar-brand" %>
      </div>
    </header>
    <div class="container">
      <%= yield %>
    </div>
  </body>
</html>

適切に記述出来ていれば、bootstrapが適用される。

GitへPush

GitHubに登録している名前とメールアドレスを設定

$ git config --global user.name "自分の名前"
$ git config --global user.email "自分のメアド"

パスワード保持の時間設定(1日)

$ git config --global credential.helper "cache --timeout=86400"

Gitリポジトリ初期化

$ git init
$ git add -A
$ git commit -m "コミットメッセージ"

GitHubページで新規リポジトリ作成

そしてPushする

$ git remote add origin https://github.com/GitHubアカウント名/プロジェクト名.git
$ git push -u origin master

HerokuへPush(失敗談込み)

Herokuをインストールする。しかしここで問題発生。以前までCloud9で開発していたので、下のコマンドでインストールしていた。

$ source <(curl -sL https://cdn.learnenough.com/heroku_install)

そして上のコマンドを実行し、versionを確認すると、下のようになり上手くいかず。

$ heroku -v
/usr/local/heroku/bin/heroku: line 44: /usr/local/heroku/bin/node: cannot execute binary file

最終的には下記のように行いインストールに成功

まず、アンインストール

$ rm -rf /usr/local/heroku /usr/local/lib/heroku /usr/local/bin/heroku ~/.local/share/heroku ~/Library/Caches/heroku

次に以下のコマンドでインストール

$ brew tap heroku/brew && brew install heroku

そしてもう一度versionを確認

$ heroku -v
heroku/7.51.0 darwin-x64 node-v12.21.0

気をとりなおして、Herokuにログインする。

$ heroku login --interactive

アプリケーション作成

$ heroku create

Herokuにデプロイする

$ git push heroku master

アプリケーション名変更

$ heroku rename アプリケーション名

参考にした記事

https://qiita.com/me-654393/items/ac6f61f3eee66380ecd7
https://qiita.com/nsy_13/items/9fbc929f173984c30b5d
https://qiita.com/shingokubota/items/3562bf4996468899613c
https://qiita.com/take18k_tech/items/a36d77316e32a6696205

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

オブジェクト指向とは何ぞや

目的

テックキャンプのカリキュラムで「オブジェクト指向」について学んだので、その備忘録として残します。

概要

オブジェクト指向1とは、

アプリケーションを作成するときに、登場する役割ごとに分けて実装する方針

のことを言います。
RubyなどのWebアプリケーションに使用される言語の多くが、オブジェクト指向で実装する必要があります。

オブジェクト指向に沿うメリット

オブジェクト指向、つまりクラスに含まれる値や処理のまとまりを意識しながら、役割ごとにオブジェクトを分けて実装することで

  • 実装がしやすくなる
  • 後からコードを改編するときも、他のオブジェクトに影響しなくなる

といったメリットがあります。

オブジェクト指向に沿ったアプリケーションを考える

ここでは、以下のような
切符の券売機のアプリケーションの実装を例にして考えます。
券売機.gif

用意すべきクラスを考える

まずは、このアプリケーションについてどんなクラスを用意すべきかを考えます。

クラスを考える際は、「このアプリケーションにおいて、どのような処理が存在するのか」に着目します。

券売機アプリケーションには、以下のような処理が存在しそうです。

- 乗車券のデータを作成する
- ユーザーに販売している商品を見せる
- 購入したい乗車券を決める
- お金を投入する
- 投入金額からお釣りの計算をする(購入処理を行う)

これらの処理を、役割の種類ごとに分類し、それぞれをクラスとします。上記を分類すると、以下のような形になります。

クラス 意味 何をするのか
Ticket 販売する乗車券 登録された金額を実体のあるデータ(インスタンス)にする
TicketMachine 券売機 ユーザーに販売している乗車券を見せて、投入金額からお釣りの計算をする
User 購入する人 購入したい乗車券を決めて、お金を投入する



このアプリケーションには、このように少なくとも3つのクラスが必要ということになります。

「券売機のアプリケーションなので、券売機クラスだけ用意すればいいのでは?」と考えた方もいるかもしれません。
しかしその場合、「券売機が行うべきでないこと」も、券売機のクラスに含まれてしまうことになります。

例えば、TicketMachineクラスから見た場合、

  • 乗車券のデータを作成する
    • →券売機は、乗車券のデータそのものを作り出すことはできないのでは?
  • 購入したい乗車券を決める、お金を投入する
    • →これは券売機ではなく、購入者が行うことでは?

と言った疑問が生じます。

このようなアプリケーションになってしまうと、後からこのアプリケーションを修正する人は、
「なぜ券売機自身が投入金額を決めているの?」
と疑問を抱き、修正がしにくくなり、保守性が悪くなってしまいます。

それぞれのクラスには、明確な役割が1つだけ与えられている必要があります。これを単一責任の法則2と言います。

コードの記述

上記のクラスの情報をもとに、実際にコードを記述していきます。
(今回はコードの記述過程は省略します。)

今回は、クラス毎にファイルを分割して実装するため、
- application.rb(requireで他の3つのファイルを読み込む)
- user.rb
- ticket.rb
- ticket_machine.rb
の4つに分けてコードを記述します。


application.rb
require "./ticket"
require "./user"
require "./ticket_machine"



puts "乗車券を用意してください"
tickets = []
3.times do |i|
  puts "行き先を入力してください"
  ticket_name = gets.chomp
  puts "金額を入力してください"
  ticket_fee = gets.to_i
  tickets << Ticket.new(ticket_name,ticket_fee)
end

ticket_machine = TicketMachine.new(tickets)
ticket_machine.show_tickets
# 生成したインスタンスに対してshow_ticketsメソッドを適用

puts "投入金額を決めてください。"
money = gets.to_i
user = User.new(money)

ticket_machine.sell(user)
user.rb
# 投入金額を決める
class User
  def initialize(money)
    @money = money
  end

  def money
    @money
  end

  def choose_ticket
    gets.to_i
  end

end
ticket.rb
class Ticket
  def initialize(name,fee)
  @name = name
  @fee = fee
  end


  def name
    @name
  end
  # インスタンス変数の値のみを戻り値としたメソッドのことをゲッターと呼ぶ。

  def fee
   @fee
  end

end
ticket_machine.rb
class TicketMachine
  def initialize(tickets)
  @tickets = tickets
  end

  def tickets
    @tickets
  end

  def show_tickets
    puts "いらっしゃいませ。以下の商品を販売しています。"
    i = 0
    self.tickets.each do |ticket|
      # rubyのインスタンスメソッド内でのself記述は特別。ここではselfが書かれているインスタンスメソッドを適用したインスタンス自身が代入されていると考える。
      # 今回の場合、TicketMachineクラスのインスタンスが代入されている。
      puts "【#{i}#{ticket.name}: #{ticket.fee}円"
      i += 1
    end
  end

    # 選んだ商品をユーザーに対して販売する
    def sell(user)
      puts "商品を選んでください"
      chosen_ticket = user.choose_ticket
      change = user.money - self.tickets[chosen_ticket].fee
      if change >= 0
        puts "ご利用ありがとうございました。お釣りは#{change}円です。"
      else
        puts "投入金額が足りません。"
      end
    end

end



ここで、用意したクラスが券売機クラスのみだったと仮定すると、ticket_machine.rbに加えticket.rbuser.rbの内容が混在してしまいます。
そうなると、他の開発者と共同で開発を行なっている場合に、どこを修正すればいいのか非常に分かりづらくなってしまいます。

終わりに

ここまで読んで頂き、ありがとうございました。
コメント欄で改善点等、記事に関するご指摘を頂けると更に励みになりますので、よろしくお願いします。


  1. ここでいうオブジェクトとは、Rubyにおけるクラスやインスタンス、その他の値のことを意味する。文字列""や数値、配列[]やハッシュ{}など、これまで値と読んできたもの全てがオブジェクトである。 

  2. アプリケーションの設計を考える上で、必要となる決まりの1つ。上記の券売機アプリケーションでは、券売機・乗車券・購入者の役割が1つ程度になるよう分割されている。このように、「1つのクラスは1つの振る舞いしか持たない」という原則を意識しないと、他者からみた時に意図のわからないアプリケーションが完成してしまう。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]途中でアプリ名を変更する方法

結論

-手順-
① renameのgem を記述して % bundle install
② 旧名のDBを削除 % rails db:drop
③ アプリ名の変更 % rails g rename:into (新しいアプリ名)
④ 新しいアプリ名のディレクトリへ移動 % cd ../(新しいアプリ名)
⑤ 新しいDBを作成 % rails db:create
⑥ 新しいDBのテーブル作成 % rails db:migrate
⑦ 再起動 % rails s
⑧ GitHubの旧アプリ名を変更

全体の流れは上記の手順です。
下記よりそれぞれ細かく説明していきます。

手順

①renameのgemを追加

Gemfile
  gem 'rename'
  % bundle install

②旧名のDBを削除

  % rails db:drop

旧名のDBを残すなら不要ですが、アプリ名変更の前に削除していた方が簡単です。

③アプリ名の変更

  % rails g rename:into (新しいアプリ名)

①で作成したrenameのgemを使用してアプリ名の変更します。

④新しいアプリ名のディレクトリへ移動

  % cd ../(新しいアプリ名)

⑤新しいDBを作成

  % rails db:create

⑥新しいDBのテーブル作成

  % rails db:migrate

⑦再起動

  % rails s

正しく起動するか確かめます。

⑧GitHubの旧アプリ名を変更

1.GitHubを開いた後、旧アプリ名で利用していたリポジトリを開き、Settingsを選択。
2.Repository nameを変更
3.新アプリ名を入力後、Renameをクリック

これで完了です。

注意点

  • 【アプリ名変更後】元々、旧アプリ名が必ず使用されていた箇所はgemによって修正されましたが、個別に名称を使用した箇所に関しては変更されてない為、自身で修正する必要があります。
  • アプリ名をモデルやコントローラーなどの名称と同じにするとエラーが発生します。

下記のコマンドで旧アプリ名が含まれている箇所を探すことができます。

% grep -rn (新しいアプリ名) . --exclude-dir={.git,tmp,log}

Rubyバージョン

ruby 2.6.5

参考リンク

Railsのアプリ名を変更して、GitHubのリポジトリ名を変更するまで -Qiita

Railsアプリの名称を変更したい!できるだけ楽に安心して行える方法を解説 -blog

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby で解く AtCoder ABC 197 D

はじめに

AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。

今回のお題

AtCoder Beginner Contest D - Opposite
Difficulty: 831

今回のテーマ、座標の変換

(x, y)を回転させた座標が答えとなります。
公式解説およびユーザ解説をrubyにトランスします。

公式解説

public.rb
include Math
n = gets.to_f
xy0 = gets.split.map(&:to_f)
xy2 = gets.split.map(&:to_f)
p = 2 * PI / n
q = [(xy0[0] + xy2[0]) / 2, (xy0[1] + xy2[1]) / 2]
r = sqrt((xy0[0] - q[0])**2 + (xy0[1] - q[1])**2)
t = atan2(xy0[1] - q[1], xy0[0] - q[0]) + p
puts [cos(t) * r + q[0], sin(t) * r + q[1]].join(' ')
Math.rb
include Math

Mathモジュールを呼び出しています。毎度、Math.PIなどと書かずPIと省略できます。
atan2は普段使わないし、公式解説はコードが載っていないので、AtCoder Beginner Contest 197 (Sponsored by Panasonic) 参戦記を参照しました。

ユーザ解説

user.rb
require 'complex'
include Math
n = gets.to_f
xy0 = gets.split.map(&:to_f)
xy2 = gets.split.map(&:to_f)
c0 = Complex(xy0[0], xy0[1])
c2 = Complex(xy2[0], xy2[1])
q = (c0 + c2) / 2
a = q + (c0 - q) * Complex.polar(1, 2 * PI / n)
puts [a.real, a.imag].join(' ')
Complex.rb
require 'complex'

こちらは複素数を用いた解法です。
class Complex
Complex.polarで回転させています。

自分のコード

my.rb
include Math
n = gets.to_f
xy0 = gets.split.map(&:to_f)
xy2 = gets.split.map(&:to_f)
p = 2 * PI / n
q = [(xy0[0] + xy2[0]) / 2, (xy0[1] + xy2[1]) / 2]
xa = cos(p) * (xy0[0] - q[0]) - sin(p) * (xy0[1] - q[1]) + q[0]
ya = sin(p) * (xy0[0] - q[0]) + cos(p) * (xy0[1] - q[1]) + q[1]
puts [xa, ya].join(' ')

公式解説の半径を使用していないバージョンです。

RubyP RubyU RubyM
コード長 (Byte) 306 267 310
実行時間 (ms) 63 66 63
メモリ (KB) 14640 14596 14712

まとめ

  • ABC 197 D を解いた
  • 座標変換 に詳しくなった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] 親モデルと子モデルを同時に削除する方法 [dependent: :destroy]

結論

dependent: :destroy を記述することで、「親モデルを削除した際にその子モデルも同時に削除」できます。

投稿内容を削除する場合、その投稿に紐づくコメントなども同時にテーブルから削除する時に使用します。

Rubyバージョン

ruby 2.6.5

dependent: :destroyの記述場所

dependent: :destroyは親モデルのアソシエーションのみに記述します。

(親モデル) post.rb
class Post < ApplicationRecord
  has_many :comments, dependent: :destroy 
(子モデル) comment.rb
class Comment < ApplicationRecord
  belongs_to :post

モデルが複数存在する場合

子モデル(comment)に紐づいて、さらに子モデル(like)が存在する場合、追加で「親モデル」にdependent: :destroyを記述します。

comment.rb
class Comment < ApplicationRecord
  has_many :likes, dependent: :destroy

参考リンク

Active Record の関連付け

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rubyで大容量ファイルをストリーム処理する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

field_with_errorsのレイアウトが崩れるのを防ぐ【Tailwind css】

背景

ユーザ管理機能のgem「devise」を導入後、新規登録設定を実施
新規登録に失敗した際にレイアウトが崩れてしまうことを確認
Googleの検証ツールを使用して確認したところ
元は「class="reg_info_field"」というクラスの親に
「div class="field_with_errors"」が追加されていた
見た目が変わるのはよくないため修正を行う

今回もQiitaさんに投稿されている方々にお世話になりました、
ありがとうございます

参考文献
field_with_errorsによるレイアウト崩れを防ぐ
Railsのバリデーションエラーで、「field_with_errors」によるレイアウト崩れを防ぐ

開発環境

  • Ruby 3.0.0
  • Rails 6.1.3.1
  • tailwindcss 2.0.4

改善前の状態

下記の画像は新規登録画面の一部を切り取ったもの
レイアウトが崩れてない状態
Googleの検証ツールで確認したところclass名「reg_info_field」
スクリーンショット 2021-04-03 13.png
スクリーンショット2.png

上記の状態が新規登録の失敗をすると・・・・
入力欄が狭くなっている
加えて「div class="field_with_errors"」が親になっているではないか・・・・
スクリーンショット3.pngスクリーンショット4.png

解決方法

@mogurinchuさんの投稿の「field_with_errorsによるレイアウト崩れを防ぐ
の情報によると下記の2点でいけるらしいのどっちも試す

  • 自動で読み込まれるfield_with_errorsタグを読み込まないように設定する
  • field_with_errorsに対してCSSをあてる

方法1

・自動で読み込まれるfield_with_errorsタグを読み込まないように設定する

config/application.rb
module TestTailwind
  class Application < Rails::Application
   /* 下記追記 */
  config.action_view.field_error_proc = Proc.new { |html_tag, instance| html_tag }
  end
end

解決!!

方法2

・field_with_errorsに対してCSSをあてる
こちらの記事「Railsのバリデーションエラーで、「field_with_errors」によるレイアウト崩れを防ぐ」を少し参考
「Tailwind css」だと少し異なる!!
Tailwind cssだと「display: contents;」は「contents;」だけとなる

app/javascript/css/tailwind.css
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

/* 下記追記 */
@layer components {
  /* 「field_with_errorr」にCSSをあてる */
  .field_with_errors {
    @apply contents;
  } 
}

解決!!

おわりに

レイアウトが崩れることに関しては上記の方法で問題はなさそう
しかし、どちらを採用したほうがいいのか判断が出来なかった・・・

  • 処理速度の違いがあるのか
  • webpackに影響でやすいのか
  • ローカル環境だから問題はないが、本番環境で影響がでるのか

知識がないため、これらなどを判断する術もなし・・・・
とりあえず、様子見してみる!!

参考文献

field_with_errorsによるレイアウト崩れを防ぐ
Railsのバリデーションエラーで、「field_with_errors」によるレイアウト崩れを防ぐ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

human_attribute_name(:カラム名)を使う Rails

初めに

railsのviewで度々見かけるが見かける度に毎回調べていたので、メモがわり。

概要

html.slim
Task.human_attribute_name(:name)
/#=> 名称

上記の形で参考書で使われていた。

前提

ja.yml
    models:
      task: タスク
    attributes:
      task:
        name: 名称

config/locales/ja.ymlにおいて、このように記述がされている。

解説

html.slim
Task.human_attribute_name(:カラム名)

ja.ymlファイルの中でカラム名に対応する文字列が引っ張り出される。
ここでは'名称'がブラウザで表示されることになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【At corder】【初心者】ABC081B - Shift only をRuby で解いてみた

はじめに

AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~ - Qiita

こちらの記事を参考に初心者がAt corderに挑戦します。
目的としては、就職活動でのコーディングテスト対策です。
毎日1問を目標としてコツコツやってきます:thinking:

わからないことは調べる精神です。ちょっとでもわからないなぁ、と思ったことは調べて解説と参考文献を載せますので、同じ内容でわからない人がいれば参考にししてください:baby:

問題 ABC081B - Shift only

N個の正の整数$A_1 ,...,A_N$がある。この整数が全て偶数のとき、すべての整数を2で割ったものに置き換える。最大で何回操作を行うことができるか求めよ

入力 出力

入力

N
A1 A2 ... An

出力

最大で何回操作を行うことができるか出力せよ

入力例

3
8 12 40

出力例

2

1回目[4,6,10]、2回目[2,3,5]となり奇数が現れるため、答えは2となる

回答

N=gets.to_i
numbers=gets.chomp.split(" ").map(&:to_i)
min = 99

numbers.each do |sum|
  count = 0
  while sum % 2 == 0 do
    count = count + 1
        sum = sum/2
  end

    if (count < min ) then
        min = count:confused:
    end

end

puts min

解説

Nは入力を受け付けたが、プログラムの中には活用しませんでした。

受けた数字をそれぞれ偶数か判断し、偶数ならばcountを+1して、対象の値を2でわる。countの数字が一番小さいものをminに保存し、それを出力する。

感想

ここから難しくなってきました。問題文を読んだ段階では最小値を出力するという発想は出てきませんでした。プログラミングらしい!:confused:
最初sumをAとしていましたが、エラーが出てしまいました。どうしてダメだったんでしょうか。。わかる方がいらっしゃればご教示くださると嬉しいです:pray:
endの場所をputs ~の後ろにしていたせいで、出力がミスってました。気をつけよう!

参考文献

Arrayクラス

Array#each (Ruby 3.0.0 リファレンスマニュアル)

Atcoder Beginners SelectionをRubyで解いてみた - Kasasagi's memorandum

[AtCoder]ABC081B - Shift only [Ruby]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Base64を完璧に理解したい人以外、見ないでください。

はじめに

タイトルはちょっとウケ狙いなので、許してください。。
業務中に、Base64でエンコードして、デコードして、といった指示があり、初めて聞いた単語に「?」が浮かんだ。
ここでは、自分が調べた情報を共有して、自分のような0からスタートする人の助けになる記事を書きたいと思って書きました。

Base64とは

base64とは、64進数を意味する言葉です。
すべてのデータをアルファベット(a~z, A~z)と数字(0~9)、一部の記号(+,/)の64文字で表すエンコード方式のこと。
ただ、データ長を揃えるためにパディングとして末尾に記号の=を使用するので、厳密にはbase64は、65文字の英数字から表現されています。

エンコードの流れ

  1. 対象の文字列を 16 進数に変換する

  2. 16 進数に変換した数字をそれぞれ 2 進数に変換する

  3. 6 ビットごとに分割して、足りない部分は 0 で詰める

  4. 変換表にしたがって各 6 ビットをエンコードしていく

  5. 4文字ごとに分割して末尾が余る場合、"=" で詰める

  6. 最後にすべての文字をつなげて完成

※デコードは逆の流れ

ABCDEFG

41 42 43 44 45 46 47

0100 0001, 0100 0010, 0100 0011, 0100 0100, 0100 0101, 0100 0110, 0100 0111

010000 010100 001001 000011 010001 000100 010101 000110 010001 11

010000 010100 001001 000011 010001 000100 010101 000110 010001 110000

QUJDREVGRw

"QUJD", "REVG", "Rw"

"QUJD", "REVG", "Rw=="

QUJDREVGRw==

モジュール関数

decode64

与えられた文字列をBase64デコードしたデータで返します。

require 'base64'

str = 'Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4gUnVieQ=='
puts Base64.decode64(str)
#=> "Now is the time for all good coders\nto learn Ruby"

encode64

与えられた文字列をBase64エンコードしたデータで返します。

require 'base64'

str = "Now is the time for all good coders\nto learn Ruby"
Base64.encode64(str)
#=> Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4gUnVieQ==

おまけ

こちらのツールを使えば自動でエンコードした文字列を出力してくれるので、エンコード文字列が欲しい場合はこちらを使うのが便利です。

終わりに

ここまでで、0からの知識の人でもある程度は掴めたのではないでしょうか?
頻繁ではないにしろ、必要な知識ではあると思うので、使えるレベルまで持っていって損は無いです!

参考

Base64のしくみを理解するためにRubyで実装してみる

module Base64

base64でエンコードされた画像をActive Storageで保存する

Rails(Ruby)でBase64化された画像を取り回すときのSnippet

singleton method Tempfile.new

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsでLINE_Boot作成する

前提

LINE-Botの作成の方法を備忘録として残してます。

環境

  • Homebrow
  • ruby '2.6.3'
  • rails '6.1.3'

Messaging API | LINE Developers

Messaging API | LINE Developers
先にLINE Developers登録してください。

チャネルの作成

「新規プロバイダー作成」をして「Messaging API」をクリックします。

$ gem 'line-bot-api'

bundle installしましょう

ngrokのインストール

Homebrewを使ってngrokをインストールします。

$ brew install ngrok
$ ngrok --version

バージョンが表示されていたらインストール出来ています。

$ rails s

railsのサーバー起動して新しいターミナルでngrokを起動させます。
http://localhost:3000でアクセスしてますのでhttp 3000します。

$ ngrok http 3000

ngrokのターミナル上で表示されていますのでここからアクセスできます。
ですがrailsでDNSリバインディング攻撃の対策のためアクセスできないようになっています。

Forwarding  https://xxxxxxx.ngrok.io
config/environments/development.rb
  config.file_watcher = ActiveSupport::EventedFileUpdateChecker

  # Uncomment if you wish to allow Action Cable access from any origin.
  # config.action_cable.disable_request_forgery_protection = true
  config.hosts.clear #追加します
end

railsを再起動させてngrokのターミナルのhttps://xxxxxxx.ngrok.ioアクセスします。

app/controllers/line_bot_controller.rb
 protect_from_forgery except: [:アクション名]
 #CSRF対策を無効化するコードです。

.env作成

gem 'dotenv-rails'

bundle installします。

$ touch .env
.env
LINE_CHANNEL_SECRET='xxxxxxxx'
LINE_CHANNEL_TOKEN='xxxxxxxx'

LINE_CHANNEL_SECRET
基本設定のチャンネルシークレットを発行し、コピーします。

LINE_CHANNEL_TOKEN
メッセージングAPI設定のチャネルアクセストークンを発行し、コピーします。

gitignore
/.env #追加します

Webhook設定

表示させたページを設定します。
https://xxxxxxxxxx.ngrok.io/xxx
ngrokを起動するたびにURLが変わるため設定しなおす必要があります。

これでRailsとlineの連携の設定は完成です。
簡単な返答くらいなら可能な状態になりました。

gem 'httpclient'を使用し、WebAPI叩ける様にもできます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby each文の入れ子

初めに

初学者です。rubyの練習問題を私なりに解説することによって定着するのが目的です。間違いありましたらご指摘お願いします。

問題

以下のように、果物の名前と値段が入った配列があります。
この配列を用いて、果物の名前とそれぞれの合計額が出力される
コードを記述してください。

fruits_price = [["apple", [200, 250, 220]], ["orange", [100, 120, 80]], ["melon", [1200, 1500]]]
(出力)
appleの合計金額は670円です
orangeの合計金額は300円です
melonの合計金額は2700円です

まずはeachメソッドのおさらいからしていきます。

eachメソッド

eachとは、配列やハッシュの要素1つ1つに対して要素の数だけ繰り返し処理をすることができるメソッドです。そして繰り返す後にブロック変数の中に配列やハッシュの値が格納されていきます。
書き方としては以下のようになります。

(配列 or ハッシュ).each do |ブロック変数|
#処理
end

またハッシュのようにキーとバリューがある場合、ブロック変数を2つ用意することでキーとバリューの2つの要素を順番に格納することができます。

解説

今回の問題のポイントはeach文を1回書いただけでは全ての値を取り出すことができないことです。金額を取り出すにはeach文を2回使用することが必要になります。
要するに、まずはeach文を使用してappleという配列を取り出し、それからappleの配列の金額を取り出すし順番にプラスしていき金額の合計を出すという流れになります。なのでまずappleを取り出します。

fruits_price = [["apple", [200, 250, 220]], ["orange", [100, 120, 80]], ["melon", [1200, 1500]]]
fruits_price.each do |fruit|
end

上記の記述では変数fruitに["apple", [200, 250, 220]]という値が格納されます。
そしてappleからeach文を使い金額を1つずつ取り出してプラスしていきます。そしてその前に、合計値を保存できるように変数を作ります。今回の変数名はsumです。

fruits_price = [["apple", [200, 250, 220]], ["orange", [100, 120, 80]], ["melon", [1200, 1500]]]

fruits_price.each do |fruit|
  sum = 0
  fruit[1].each do |price|
    sum += price
  end
end

fruit[1].each do |price|の文では、変数fruitの1番目の値を一つずつ取り出しています。そして最後に出力する記述を書けば終了です。

fruits_price = [["apple", [200, 250, 220]], ["orange", [100, 120, 80]], ["melon", [1200, 1500]]]

fruits_price.each do |fruit|
  sum = 0
  fruit[1].each do |price|
    sum += price
  end
  puts "#{fruit[0]}の合計金額は#{sum}円です"
end

そしてappleの値を全て返した後に次のorengeが同じように繰り返されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] ActiveRecord::ValueTooLong (Mysql2::Error: Data too long for column 'content' at row 1):

なぜこのエラーが出たか

データ型の文字制限を超えた情報をDBに格納しようとしたから。

解決方法

データ型を変えることにした

    - t.string "content"
    + t.text "content"

migrateした後は、DBにしっかり格納された。

補足:
limitオプションでも条件を変えることができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PC購入後の環境設定(Mac Catalina)

シェルの設定

# zshをデフォルトに設定
% chsh -s /bin/zsh

# ログインシェルを表示
% echo $SHELL
# 以下のように表示されれば成功
/bin/zsh

Command Line Tools

% xcode-select --install

Homebrew

% cd  # ホームディレクトリに移動
% pwd  # ホームディレクトリにいるかどうか確認
% /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # コマンドを実行
# PasswordはMacのパスワード
# 最新状態に
% brew update

# 権限の変更
%  sudo chown -R `whoami`:admin /usr/local/bin

Ruby

# rbenvとruby-buildをインストール
% brew install rbenv ruby-build

rbenvのパスを通す
% echo 'eval "$(rbenv init -)"' >> ~/.zshrc

# zshrcの変更を反映
% source ~/.zshrc
readlineインストール
% brew install readline
% brew link readline --force
# Rubyインストール
% RUBY_CONFIGURE_OPTS="--with-readline-dir=$(brew --prefix readline)"
% rbenv install 2.6.5

# Rubyバージョン指定
% rbenv global 2.6.5

# rbenvを読み込んで変更を反映
% rbenv rehash

MySQL

% brew install mysql@5.6

# 自動起動設定
% mkdir ~/Library/LaunchAgents 
% ln -sfv /usr/local/opt/mysql\@5.6/*.plist ~/Library/LaunchAgents
% launchctl load ~/Library/LaunchAgents/homebrew.mxcl.mysql\@5.6.plist 

# パスを通す
% echo 'export PATH="/usr/local/opt/mysql@5.6/bin:$PATH"' >> ~/.zshrc # mysqlのコマンドを実行できるようにする設定
% source ~/.zshrc #  設定を読み込むコマンド
% which mysql # mysqlのコマンドが打てるか確認する

# 起動確認
% mysql.server status # MySQLの状態を確認するコマンド

shared-mime-info

% brew install shared-mime-info

Rails

# bundlerインストール
% gem install bundler --version='2.1.4'

# Railsインストール
% gem install rails --version='6.0.0'

% rbenv rehash

Node.js

brew install node@14

# パスを通す
% echo 'export PATH="/usr/local/opt/node@14/bin:$PATH"' >> ~/.zshrc
% source ~/.zshrc

yarn

% brew install yarn
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでハッシュタグ機能を実装してみた(gemなし) 後半

概要

前半ではRouteの作成とハッシュタグ別のViewを作成する手前まで書きましたが、その続きを書いています。
具体的には、

  • Routeの作成
  • ハッシュタグコントローラーを作成
  • ハッシュタグ別の投稿一覧を表示するView

上記の実装を後半でやっていきます。
まだ前半を見てらっしゃらない方は、下記のリンクからご覧ください。
https://qiita.com/Prog_taro/items/9dc5fe39eb6149acabd4

Routeを作成

config/routes.rb
get '/post/hashtag/:label_name', to: 'hashtags#index'

あまり日本語でURLを作るべきではないと思うのですが、今回はわかりやすくするためにタグの名前でURLを作成する事にしました。

ハッシュタグコントローラーを作成

$ touch app/controllers/hashtags_controller.rb

もちろん rails g コマンドで作成しても良いですが、今回は素直に作成しました。

ハッシュタグコントローラーを編集

app/controllers/hashtags_controller.rb
class HashtagsController < ApplicationController
  before_action :authenticate_user!
  def index
    @label = params[:label_name]
    hashtag = Hashtag.find_by(label: @label)
    if hashtag.nil?
      redirect_to root_path, alert: "##{@label}のタグがついた投稿は存在しません"
    else
      @posts = hashtag.posts.includes(:photos, :user, :likes, :comments).recent
    end
  end
end

上記のコードを説明させていただきます。

app/controllers/hashtags_controller.rb
before_action :authenticate_user!

これはログインしているユーザーしかこのページを見られないようにするためのdeviseを入れないと使えないメソッドです。
僕の場合、入れているので使用することができます。
アプリの設計上、ログインしているユーザーにしか見れないようにしたかったので導入しました。

app/controllers/hashtags_controller.rb
@label = params[:label_name]

クリックしたハッシュタグをビュー側にも表示させたいという意図があったためインスタンス変数にパラメータで渡ってきた:label_nameを入れています。

app/controllers/hashtags_controller.rb
hashtag = Hashtag.find_by(label: @label)

このコードはハッシュタグの名前がパラメータで渡ってきた名前と同一のもの、つまり、DBに登録されているハッシュタグの中から該当のハッシュタグを取り出しているコードです。

例えばパラメータで "こんにちは" というものが送られてきたら、DBに登録されているハッシュタグから "こんにちは" と同一のものを取り出すということです。

なぜ同一のものを取り出せるのかというと、前半部分を書いた記事でハッシュタグはuniqにしているからです。

app/controllers/hashtags_controller.rb
if hashtag.nil?
  redirect_to root_path, alert: "##{@label}のタグがついた投稿は存在しません"
else
  @posts = hashtag.posts.includes(:photos, :user, :likes, :comments).recent
end

この部分は先ほど書いたfind_byで該当ハッシュタグがない状態、つまりNilのことを許容しているのですが、Nilだった場合にエラーにならないよう書いている部分です。
find_byがNilを許容していることについては下記をご覧ください。
https://railsdoc.com/page/find_by

else の中身について説明します。

app/controllers/hashtags_controller.rb
@posts = hashtag.posts.includes(:photos, :user, :likes, :comments).recent

後ほどViewでも使う予定なのでインスタンス変数に入れています。
hashtag.postsとするだけでも該当ハッシュタグがついた投稿一覧が出るのですが、N+1問題が発生しているので .includs をつけています。

N+1問題についてものすごく簡潔に説明すると、DBにアクセスする回数が多くなれば多くなるほど接続に時間がかかってパフォーマンスが落ちる!という感じでしょうか。
(僕も初心者なので詳しくはしっかりと調べてみることをお勧めします)
もっと詳しく知りたい方は良い記事がありますので、下記を参照してください。
https://qiita.com/TsubasaTakagi/items/8c3f4317ad917924b860

一番最後の .recent は .order をscopeにしてまとめたものです。

Postモデルに追加

app/models/post.rb
scope :recent, -> { order(created_at: :desc) }

基本的にorderはスコープにしてまとめた方が良さそうですね。

Viewを作成

いよいよハッシュタグ別のViewを作成するんですが、僕はもともとあった投稿一覧画面をそのまま引用しました。
特別にそのページだけ仕様を変えたい場合は変えるべきですが、僕の場合はそのまま利用したのでここでの詳しいご紹介は割愛させていただこうと思います。
皆さんも特別何か変更を加えるつもりがない場合は、使用している変数などを変更した上で、再利用しても良いと思います。

変更した場所のみ簡潔にまとめさせていただこうと思います。

app/views/hashtags/index.html.erb
<h2 class="text-center text-secondary mt-5"><%= "#" + @label %>のタグを含む投稿一覧</h2>

Bootstrapを使用しているのでclassを付与しています。
先ほどインスタンス変数に入れた該当ハッシュタグの名前を表示させました。
こうする事によって、ユーザー目線でよりわかりやすくなるのではという意図です。

app/views/hashtags/index.html.erb
<% @posts.each do |post| %>

....中身は省略....

<% end %>

投稿知覧を表示するためにeachを使っていたのですが、元の変数を先ほどインスタンス変数に入れた該当タグの全投稿に変えました。

僕が変更したのはこれぐらいです...笑
ユーザー目線で考えると、チラチラ画面が変わってたら目が疲れるかな?という意図でわざとあまり変更は加えませんでした。

最後に...

初心者なりに書いて見ました!参考になった方が1人でもいらっしゃれば幸せです。
アウトプットにもなりましたし、またテーマを思いついたら書いてみようと思います。

間違っている部分などがありましたら、ご指摘いただければ加筆修正させていただきますのでお願いします!

ここまで読んでいただいて本当にありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【cloud9】Railsチュートリアルやってたら、cloud9で容量足りなくなって更新できなくなったので容量増やした【Rails】

症状

Railsチュートリアルをやっていたら、下記エラーが表示されてファイルが更新できなくなってしまいました。

Failed to write to '更新対象のファイルの中身'. ENOSPC: no space left on device, write .

翻訳すると、「書き込みに失敗しました。デバイスにスペースが残っていません」
容量が足りなくて、ファイルの更新ができなくっていると推測。

前半で作った「hello_app」「toy_app 」は既に削除していたので、EBS ボリューム(Amazon Elastic Block Storage)を増やしていこうと考えました。
念のため、df -h コマンドで、現在の容量をチェックし、100%になっていることを確認しました。

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1      9.7G  9.7G     0 100% /

解決策

Railsチュートリアルの2. EBS ボリュームを増やす方法を確認しながらEBSボリュームを増やして、ファイルを更新できるようにしました。

Railsチュートリアルの手順を下記に引用し、実際にやっていきます。

1.Amazon EC2 コンソールを開きます。
2.「ボリューム」を選択し、変更するボリュームを選択します。(複数あって分からない場合は、「インスタンス」の「実行中のインスタンス」の中からaws-cloud9から始まる Name のインスタンス ID をコピーし、ボリューム内で検索するとよいでしょう。)
3.上部にある「アクション」ボタンから「ボリュームの変更」を選択し、サイズを AWS が定める最大無料枠に設定し、変更します。(執筆時点では 30 でしたが、念のため確認しておくことをオススメします)
4.「インスタンス」にいき、該当のインスタンスを選択後、画面上部にある「アクション」ボタンから「インスタンスの状態」→「再起動(停止中なら開始)」をします。
5「Cloud9」に戻り、ターミナルからボリュームの拡張をします。現在のインスタンスに割り当てられているブロックデバイスの情報を確認するには、lsblkコマンドで表示します。ルートボリュームである/dev/xvdaは拡張されていますが、/dev/xvda1はまだ元のままです。

1から3でコンソールを対象のボリュームを選択し、アクションを押したの下記です。
image.png

キャプチャ.JPG

4.「インスタンス」に行き、街頭のインスタンスを再起動しました。
キャプチャ13.JPG

lsblk

xvda    202:0    0   30G  0 disk 
└─xvda1 202:1    0   10G  0 part /

5のコマンドを打とうとすると、「容量が足りないよ」と怒られてしまいました。
容量が足りないから増やそうとしているのに、その命令が出せないというパラドックス状態に。。。

 sudo resize2fs /dev/xvda
sudo: unable to resolve host ip-ホスト名: Resource temporarily unavailable
resize2fs 1.44.1 (24-Mar-2018)
resize2fs: Device or resource busy while trying to open /dev/xvda
Couldn't find valid filesystem superblock.

詰んだと思って放置して、後日確認したらボリュームが追加されていました。

ターミナル
df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1       30G  9.2G   20G  32% /

おそらくですが、ボリュームが変更ほやほやだったため、状態が黄色状態でうまくボリュームの変更が未完了できていなかったようです。
とりあえず、cloud9でボリューム追加できました!

参考

困ったときのヒント集-Railsチュートリアル
https://railstutorial.jp/help#volume-up-aws-ebs

No space left on deviceで、Volume増やしたけど、growpartコマンドすら実行できない状況を解決(docker containerを削除した)
https://qiita.com/gymnstcs/items/f535bdf8f85513580577

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]ActiveStorageで画像投稿機能を作成しよう!

前提条件

railsに関しての基礎的な知識があることのみです

事前準備

今回の画像投稿機能を作成するにあたり以下のものを準備します。
まずはそのツールに関しての説明をします
・ActiveStorage
・ImageMagick
・MiniMagick
・ImageProcessing

ActiveStorageとは

ActiveStorageとは「gem」のことです
インストールすることで、画像やファイルのアップロードを簡単にするメソッドを使用できたり、
画像を保存するテーブルを簡単に作成できるようにします。

ImageMagic

ImageMagicはgemではなく、ソフトウェアです。
画像の作成、サイズ変更、保存形式の変更を行います。
Homebrewからインストールします。

ちなみに、こいつ単体ではRails内で使用できません。
使用するためにはMinMagicが必要です。

MiniMagick

こいつは、ImageMagickがRails内で使用できるようにするためのgemです。
ただ、こいつも完璧ではないんですよね、、、↓

ImageProcessing

MiniMagickでは提供できない画像サイズを調整する機能を持ちます

実際にインストールをして行こう

ここからはただただ無心で進めましょう

①ImageMagickのインストール

bundle install imagemagick

②MinMagickとImageProcessingのインストール

Gemfile
gem 'mini_magick'
gem 'image_processing', '~> 1.2'

もちろん、記載するだけでなく
ターミナルでこれを打たないと意味ないですよ!!

bundle install

③ActiveStorageのインストール
※以下は一旦rails sでサーバは立ち上げた後に行ってください。

% rails active_storage:install
% rails db:migrate

DBに、「active_storage_attachments」と「active_storage_bolbs」が作成されていれば準備完了です
スクリーンショット 2021-04-02 23.54.55.png
(SequelProというツールでDBはみれますよ!!!調べてみてください)

実装していこう

ようやく準備が終了!!
ここからが本番。

まずは今回、Messageモデルに対して画像を保存させたいという前提で話を進めます。

MessagesテーブルとActiveStorageテーブルで管理された画像ファイルを1対1でアソシエーションします。(つまり1メッセージにつき1枚だけ添付可能とする)

models/message.rb
class Message < ApplicationRecord
  has_one_attached :image #ここはファイル名記載、なんでもOK!
end

:imageはファイル名なので:photoでもいいし、:pictureでもOKです

これでテーブル同士はつながりました。

ただし、画像を保持するカラムはMessagesテーブルにはないので(だからこそ、上記でアソシエーションした)、imageという名前でアクセスできるようになった画像ファイルの保存を許可する実装を行うために、ストロングパラメータにmergeしてあげましょう。

messages_controller.rb
 private
  def message_params
    params.require(:message).permit(:content, :image).merge(user_id: current_user.id)
  end

これでよし!!!

あとは画像を表示させるだけ

あとは、viewファイルで表示すれば完成です!!

たとえば、こんな感じです

image.html.erb
 <%= image_tag message.image, class: 'image_message' %>

ちなみ、このままでは画像なしの場合にエラーが発生しますので
attached?メソッドを使用するといいです

image.html.erb
<%= image_tag message.image, class: 'image_<%= image_tag message.image, class: 'message-image' if message.image.attached? %' if message.image.attached? %>

さて、ここまでで実際に投稿機能を実装してテスト投稿してみた方は思ったでしょう、、、、
サイズがバラバラじゃねーか!!!と、、、、.

そんなときは、variantメソッドで高さと幅を決めましょう!

モデル.ファイル名.variant(resize: '幅x高さ')

こんな感じで使ってみましょう↓

image.html.erb
<%= image_tag message.image.variant(resize: '500x500'), class: 'message-image' if message.image.attached? %>

終わりに

初めて使う方は難しく感じるかもしれませんが、触っているうちに慣れてきて便利さに気づくかと思いますので是非当記事を参考にしながら練習してみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む