ためすう

assign を使ってみる (C++)

2020-07-18

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
#include <cassert>
using namespace std;

int main() {
    vector<int> v;
    // 5個の要素を値10で埋める
    v.assign(5, 10);

    for (auto val : v) {
        cout << val << " ";
    }
    cout << endl;

    cout << "---" << endl;
    // 2個の要素に置き換える
    vector<int> before_v = {5, 4, 3, 2, 1};
    v.assign(before_v.begin(), before_v.begin() + 2);
    for (auto val : v) {
        cout << val << " ";
    }
    cout << endl;
}

出力結果

10 10 10 10 10
---
5 4

参考

gem を複数バージョンでテストするため、appraisal を使ってみる

2020-07-12

やったこと

例えば、ある gem で複数の Rails で動かすために、 複数バージョンでテスト (rspec) を実行したいとします。

そんな時に、この gem を使います。

今回は、下記でテスト (rspec) を実行できるようにします。

  • Rails 5.1.0
  • Rails 5.2.3
  • Rails 6.0.0

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

$ rails --version
Rails 5.2.3

調査

Github にある通りにセットアップしていきます。

Appraisals ファイルの準備

Appraisals

appraise "rails-5.1.0" do
  gem "rails", "5.1.0"
end

appraise "rails-5.2.3" do
  gem "rails", "5.2.3"
end

appraise "rails-6.0.0" do
  gem "rails", "6.0.0"
  gem "sqlite3", "~> 1.4"
end

インストール

Gemfile

group :development do
  # 省略...
  gem 'appraisal'
end

In your package's .gemspec 今回は Gemfile に書きました。

$ bundle install
$ bundle exec appraisal install

テストファイル用意

spec/models/task2_spec.rb

require 'rails_helper'

RSpec.describe Task, type: :model do
  it '計算1 should' do
    (1 + 2).should eq 3
  end

  it '計算2 expect' do
    expect(3 + 4).to eq 7
  end
end

テスト実行

$ RAILS_ENV=test bundle exec appraisal rspec spec/models/task2_spec.rb

これで、Appraisals ファイルに記述してある3種類の Rails バージョンで rspec が実行されます。

ラジアンと角度の変換をする (C++)

2020-07-11

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

int main() {
    // degree -> radian
    double target_degree = 45;
    double radian = target_degree * M_PI / 180.0;
    printf("radian: %.10f\n", radian);

    // radian -> degree
    double degree = radian * 180.0 / M_PI;
    printf("degree: %.10f\n", degree);
}

出力結果

radian: 0.7853981634
degree: 45.0000000000

std::prev, std::next を使ってみる (C++)

2020-07-09

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> v = {5, 3, 2, 9, 8};

    auto iter = v.begin();
    cout << "begin(): " << *iter << endl;
    iter = next(iter);
    cout << "begin() + next1回: " << *iter << endl;
    iter = next(iter);
    cout << "begin() + next2回: " << *iter << endl;

    iter = prev(iter);
    cout << "begin() + next2回 + prev1回: " << *iter << endl;

    // この指定だと2回進む
    iter++;
    iter += 1;
    cout << "begin() + next2回 + prev1回 + 2回進む " << *iter << endl;
}

出力結果

begin(): 5
begin() + next1回: 3
begin() + next2回: 2
begin() + next2回 + prev1回: 3
begin() + next2回 + prev1回 + 2回進む 9

参考

vector::front, vector::back を使ってみる (C++)

2020-07-08

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

int main() {
    vector<int> v = {5, 3, 2, 9, 8};

    cout << "v.front(): " << v.front() << endl;
    cout << "v.back(): " << v.back() << endl;

    // インデックス直接指定でもできる
    cout << "v[0]: " << v[0] << endl;
    cout << "v[v.size() - 1]: " << v[v.size() - 1] << endl;
}

出力結果

v.front(): 5
v.back(): 8
v[0]: 5
v[v.size() - 1]: 8

参考

multiset を使ってみる (C++)

2020-07-07

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

multiset<int> init() {
    multiset<int> s;
    s.insert(2);
    s.insert(3);
    s.insert(2);
    s.insert(1);
    s.insert(2);
    return s;
}

void display(multiset<int> s) {
    for (auto v : s) {
        cout << v << " ";
    }
    cout << endl;
}

int main() {
    multiset<int> s = init();

    cout << "【ケース1】" << endl;
    cout << "変更前" << endl;
    display(s);

    // 3つとも消えてしまう
    s.erase(2);
    cout << "変更後" << endl;
    display(s);
    cout << "---" << endl;

    cout << "【ケース2】" << endl;
    cout << "変更前" << endl;
    s = init();
    display(s);

    cout << typeid(s.find(2)).name() << endl;
    s.erase(s.find(2));
    cout << "変更後" << endl;
    display(s);
    cout << "---" << endl;

    cout << "【ケース3】" << endl;
    cout << "変更前" << endl;
    s = init();
    display(s);

    auto iter = s.lower_bound(2);
    cout << typeid(iter).name() << endl;
    s.erase(iter);
    cout << "変更後" << endl;
    display(s);
}

出力結果

【ケース1】
変更前
1 2 2 2 3
変更後
1 3
---
【ケース2】
変更前
1 2 2 2 3
St23_Rb_tree_const_iteratorIiE
変更後
1 2 2 3
---
【ケース3】
変更前
1 2 2 2 3
St23_Rb_tree_const_iteratorIiE
変更後
1 2 2 3

参考

struct のコンストラクタを使ってみる (C++)

2020-07-06

確認環境

$ g++ --version
g++ (Homebrew GCC 9.2.0) 9.2.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

調査

test.cpp

#include <bits/stdc++.h>
using namespace std;

struct person {
    string name;
    int age;

    // コンストラクタを定義
    person(string name, int age): name(name), age(age) {};
};

int main() {
    person p("person name", 20);
    cout << p.name << endl;
    cout << p.age << endl;
}

出力結果

person name
20

ディレクトリごと差分がないかどうかを調べる

2020-07-05

やったこと

拡張子がないファイルを検索します。

調査

ファイル準備

$ ls -lh sample-diff*
sample-diff1:
total 16
-rw-r--r--  1 hoge  staff     4B  7  4 14:21 aaa.rb
-rw-r--r--  1 hoge  staff     9B  7  4 14:21 bbb.rb

sample-diff2:
total 16
-rw-r--r--  1 hoge  staff    21B  7  4 14:25 aaa.rb
-rw-r--r--  1 hoge  staff    26B  7  4 14:24 bbb.rb

※ ファイルの中身は省略しますが、bbb.rb の中身に差分があります。

man diff のオプションを見てみます。

       -r  --recursive
              Recursively compare any subdirectories found.
$ diff -r sample-diff1 sample-diff2
diff -r sample-diff1/bbb.rb sample-diff2/bbb.rb
2c2
< p 'sample-diff1'
---
> p 'sample-diff2'

bbb.rb に差分があることが分かりました。

拡張子がないファイルを検索したい

2020-07-04

やったこと

拡張子がないファイルを検索します。

調査

ファイル準備

$ ls -la sample-file/
total 0
drwxr-xr-x  6 hogehoge  staff  192  7  4 13:39 .
drwxr-xr-x  4 hogehoge  staff  128  7  4 13:52 ..
-rw-r--r--  1 hogehoge  staff    0  7  4 13:38 aaa.rb
-rw-r--r--  1 hogehoge  staff    0  7  4 13:38 bbb
-rw-r--r--  1 hogehoge  staff    0  7  4 13:39 ccc
-rw-r--r--  1 hogehoge  staff    0  7  4 13:39 ddd.php

今回は find を使ってやってみます。

find [検索対象ディレクトリ] -type ファイルタイプ -not -name "検索パターン"

$ find ./sample-file/ -type f -not -name "*.*"
./sample-file//bbb
./sample-file//ccc

ファイル名に拡張子がある を . が存在すると言い換えると、. が存在しないファイルを抽出することになります。

MySQL で ON DUPLICATE KEY UPDATE を使ってたら、カンストした件

2020-06-20

MySQL の INSERT ... ON DUPLICATE KEY UPDATE 構文 は、大量のデータを1度に INSERT, UPDATE するという構文です。

しかし、この構文を使っている箇所について、下記の事象が発生しました。

  1. 意図せず AUTO_INCREMENT が進む
  2. int で保存できる最大値に達する (カンスト)
  3. 新しくデータが保存できなくなる

今回は、1. のところについての対処方法の共有をしたいと思います。

INSERT ... ON DUPLICATE KEY UPDATE 構文 について

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.5.3 INSERT … ON DUPLICATE KEY UPDATE 構文

この構文を使えば、1本のSQLで、INSERT、UPDATE を実行することができます。

意図せず AUTO_INCREMENT が進んでしまった理由を調べていたところ、

UPDATE 文になった場合でも、AUTO_INCREMENT が進むらしいということが分かりました。

それでは、検証を始めます。

確認環境

$ mysql --version
mysql  Ver 14.14 Distrib 5.6.43, for osx10.13 (x86_64) using  EditLine wrapper

検証

準備

CREATE DATABASE test;
CREATE TABLE `tmp_a` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `uniq` varchar(20) NOT NULL,
  `cnt` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx01` (`uniq`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

現在の AUTO_INCREMENT を確認します。

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              1 |
+----------------+
1 row in set (0.00 sec)

INSERT 文が発行される場合 (id カラムに値を指定しない)

変更前

mysql> SELECT * FROM tmp_a;
Empty set (0.00 sec)
mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              1 |
+----------------+
1 row in set (0.00 sec)

実行

mysql> INSERT INTO tmp_a (id, uniq, cnt)
    -> VALUES
    ->     (null,'u1', 1), (null,'u2', 2), (null,'u3', 3)
    -> ON DUPLICATE KEY UPDATE
    ->  uniq=VALUES(`uniq`),
    ->  cnt=VALUES(`cnt`)
    -> ;
Query OK, 3 rows affected (0.01 sec)
Records: 3  Duplicates: 0  Warnings: 0

変更後

データが登録されたことが確認できます。

mysql>  SELECT * FROM tmp_a;
+----+------+-----+
| id | uniq | cnt |
+----+------+-----+
|  1 | u1   |   1 |
|  2 | u2   |   2 |
|  3 | u3   |   3 |
+----+------+-----+
3 rows in set (0.00 sec)

AUTO_INCREMENT も 3 進みました。

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              4 |
+----------------+
1 row in set (0.00 sec)

UPDATE 文が発行される場合 (id カラムに値を指定する)

変更前

mysql>  SELECT * FROM tmp_a;
+----+------+-----+
| id | uniq | cnt |
+----+------+-----+
|  1 | u1   |   1 |
|  2 | u2   |   2 |
|  3 | u3   |   3 |
+----+------+-----+
3 rows in set (0.00 sec)
mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              4 |
+----------------+
1 row in set (0.00 sec)

実行

cnt を 1000 倍にして、更新します。

mysql> INSERT INTO tmp_a (id, uniq, cnt)
    -> VALUES
    ->     (1,'u1', 1000), (2,'u2', 2000), (3,'u3', 3000)
    -> ON DUPLICATE KEY UPDATE
    ->  uniq=VALUES(`uniq`),
    ->  cnt=VALUES(`cnt`)
    -> ;
Query OK, 6 rows affected (0.01 sec)
Records: 3  Duplicates: 3  Warnings: 0

変更後

cnt が更新されました。

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1000 |
|  2 | u2   | 2000 |
|  3 | u3   | 3000 |
+----+------+------+
3 rows in set (0.00 sec)

AUTO_INCREMENT も変更されずそのままでした。

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              4 |
+----------------+
1 row in set (0.01 sec)

これは意図通り。

UPDATE 文が発行される場合 (id カラムに値を指定しない)

変更前

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1000 |
|  2 | u2   | 2000 |
|  3 | u3   | 3000 |
+----+------+------+
3 rows in set (0.00 sec)
mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              4 |
+----------------+
1 row in set (0.01 sec)

実行

mysql> INSERT INTO tmp_a (id, uniq, cnt)
    -> VALUES
    ->     (null,'u1', 1111), (null,'u2', 2222), (null,'u3', 3333)
    -> ON DUPLICATE KEY UPDATE
    ->  uniq=VALUES(`uniq`),
    ->  cnt=VALUES(`cnt`)
    -> ;
Query OK, 6 rows affected (0.01 sec)
Records: 3  Duplicates: 3  Warnings: 0

変更後

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1111 |
|  2 | u2   | 2222 |
|  3 | u3   | 3333 |
+----+------+------+
3 rows in set (0.01 sec)

AUTO_INCREMENT が先に進んでしまいました。

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              7 |
+----------------+
1 row in set (0.00 sec)

ここで少し休憩です。

INSERT ... ON DUPLICATE KEY UPDATE 構文 を使うとき UPDATE する場合、AUTO_INCREMENT を進めないためには、下記カラムの値を指定する必要があることが分かりました。

  • id (AUTO_INCREMENT)
  • uniq (DUPLICATE KEY)

INSERT, UPDATE 文が発行される場合

今回の問題発生箇所での使われ方です。

変更前

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1111 |
|  2 | u2   | 2222 |
|  3 | u3   | 3333 |
+----+------+------+
3 rows in set (0.01 sec)
mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|              7 |
+----------------+
1 row in set (0.00 sec)

実行

mysql> INSERT INTO tmp_a (id, uniq, cnt)
    -> VALUES
    ->     (1,'u1', 1000), (2,'u2', 2000), (3,'u3', 3000),
    ->     (null,'u4', 4), (null,'u5', 5), (null,'u6', 6)
    -> ON DUPLICATE KEY UPDATE
    ->  uniq=VALUES(`uniq`),
    ->  cnt=VALUES(`cnt`)
    -> ;
Query OK, 9 rows affected (0.02 sec)
Records: 6  Duplicates: 3  Warnings: 0

変更前

登録、更新されたデータは意図通りでした。

mysql> SELECT * FROM tmp_a;
+----+------+------+
| id | uniq | cnt  |
+----+------+------+
|  1 | u1   | 1000 |
|  2 | u2   | 2000 |
|  3 | u3   | 3000 |
|  7 | u4   |    4 |
|  8 | u5   |    5 |
|  9 | u6   |    6 |
+----+------+------+
6 rows in set (0.00 sec)

6 つ進んでしまいました!!!

mysql> SELECT AUTO_INCREMENT FROM information_schema.tables WHERE table_schema = 'test';
+----------------+
| AUTO_INCREMENT |
+----------------+
|             13 |
+----------------+
1 row in set (0.01 sec)

どうやら、登録しようとしているデータ全部を更新しなければ、AUTO_INCREMENT が進むようです。

対処方法

id カラムを削除する

DB を利用している側の制約上、削除できない可能性もありますが、

id カラムを削除すれば AUTO_INCREMENT のことを考えなくて良くなります。

INSERT文とUPDATE文を分ける

UPDATE 文のみであれば、AUTO_INCREMENT が更新されないことが分かったので、

SQL を分割するのが良さそうです。