日記

のみろぐ

主に競プロ日記です

【Rails】test_helper.rb (LoadError)

起きたこと

  • Rails Tutorial 5章のラスト、herokuにデプロイする場面でバグった。
  • git push herokuをした後、ブラウザでサイトを見に行くと以下のようなエラー画面が表示される(画像張るのめんどいから文字になってる)
Application error
An error occurred in the application and your page could not be served. If you are the application owner, check your logs for details. You can do this from the Heroku CLI with the command
heroku logs --tail
  • よくわからんがとりあえずバグったっぽい

やったこと

  • herokuに表示されたエラー画面の指示通り、heroku logs --tailを見た
    • エラーが出てることはわかったが、原因まではわからなかった
  • heroku run rails consoleをした
    • heroku上でrails consoleを立ち上げるコマンド
    • 実行すると、最後の方にNo such file to load -- test_helper.rb (LoadError)というエラーが出てくる。どうやらtest_helper.rbというファイルが見つからないっぽい
    • 頑張ったら原因がわかった
    • 原因は、users_controller_test.rbというファイルを/app/controllers/内に作ってしまったことだった。本来は/test/controllers/内に置くべきファイルなのに。普通にミスった。ファイルの内容は以下の通り。このrequire 'test_helper'を実行した時に、LoadErrorが起こってたみたい。だから、app/controllers/users_controller_test.rbtest/controllers/users_controller_test.rbに移動した。
require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest
  test "should get new" do
    get signup_path
    assert_response :success
  end
end

まとめ

  • herokuにデプロイしたらエラーが起きた
  • 原因はusers_controller_test.rbファイルの配置ミスだった
  • app/controllers/users_controller_test.rbtest/controllers/users_controller_test.rbに移動することで解決した。

  • でも、rails serverで動かした時はエラー起きなかったんだよなぁ。なんでだろ?わからん

herokuにデプロイできない

Railsチュートリアルやってたらバグった。 1章目で詰まるとか泣ける。

git push heroku masterするとこんなの出てくる

$git push heroku master

....
(略)
....

remote: 
To https://git.heroku.com/hogehoge.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://git.heroku.com/hogehoge.git'

調べる

  • 調べてもよくわからん。
  • でも、heroku周りのエラーなのはわかった。

解決策

Herokuを消して、作り直した

  • Herokuのアプリ管理画面のSettings→Delete appからアプリを削除する
  • git remote rm herokuでremoteにあるherokuを削除する
  • heroku createでアプリを作り直す
  • git push heroku masterでデプロイ
  • 完了〜

多分もっといい解決方法があるんだろうけど、よくわからん。

gitも全然わからん。

とりまチュートリアルを終わらせたい。

参考記事

一人暮らしの風邪は詰み

風邪を引いた。つらい。

でもアドベントカレンダーは書きたい(わがまま。かわいいかよ)

やること

↓これをつくります

僕「お腹すいた」

ロボ「XXを食べると風邪にいいんだよ!!!!」

僕「へー…」

つまり、かぜ引いた時に何食べればいいかをオススメしてくれるやつを作る

流れ

  1. yahooショッピングで「風邪 食べ物」を検索
  2. 検索結果から食べ物の名前を抜き出す
  3. カウント数が多かった食べ物を食べる

1. 検索結果の取得

準備するもの

  • yahooショッピング検索API

    • 使うにはアプリ登録をしてアプリIDをゲットする必要があります。Yahooアカウントがあれば3秒でもらえます(ここ)
  • Pythonのrequestsモジュール

    • pip install requestsで入手

やること

  1. GETリクエストを送って検索結果のXMLを手に入れる。以上!

コード

import requests

def search(keyword):
        url = 'https://shopping.yahooapis.jp/ShoppingWebService/V1/itemSearch'
        url += '?appid=' + 'ここにアプリIDを入れる' # アプリのID
        url += '&query=' + keyword # 検索ワード
        url += '&hits=50' # 取得件数(最大50件)
        response = requests.get(url.encode('utf-8')) # utf-8にエンコードしないといけない
        return response.text

2. 文章から単語を抜き出す

準備するもの

  • MeCab

    • 文章を品詞ごとに分けてくれるやつ。商品説明文から名詞を抜き出すのに使う。
    • brew install mecabで入手
  • MeCab辞書

    • なんか欲しいらしい。
    • brew install mecab-ipadicで入手
  • PythonMeCabを使うためのモジュール

    • pip install mecab-python3で入手

やること

  1. XMLから全ての商品説明文を抜き出す
  2. 抜き出した商品説明文の名詞をリストに格納。以上!

コード

import MeCab
import xml.etree.ElementTree as ET

# XMLから全ての商品説明文を取得する
descriptions = []
def get_descriptions(parent):
    for child in parent:
        if child.tag == "{urn:yahoo:jp:itemSearch}Description" and child.text != None: # なんか{urn:yahoo:jp:itemSearch}が必要みたい
            descriptions.append(child.text)
        get_descriptions(child)

# 文章から名詞を抜き出し、それをリストにして返す
def get_words_list(s):
    mecab = MeCab.Tagger ("-Ochasen")
    node = mecab.parseToNode(s)

    foods = []
    while node:
        if node.feature.split(',')[0] == '名詞' and node.surface >  '~': # 名詞抜き出し and 雑に記号を排除
            foods.append(node.surface)
        node = node.next
    
    return foods

# XMLを渡すと、重複ありの名詞単語リストが返される
def get_foods(xml_txt):
    root = ET.fromstring(xml_txt) # XMLの根
    get_descriptions(root) # XMLから説明文を抜き出す

    foods = []
    for description in descriptions:
        foods += get_words_list(description)
    
    return foods

3. 食べる

準備するもの

  • 特にない

やること

  1. 重複ありの単語リストから、出現頻度の高い順に表示する。以上!

    1. TF-IDFとかの方がいいんだろうけど面倒なので素直にカウントしました。

コード

import collections

def get_result(foods):
        most = collections.Counter(foods).most_common()
        result = '風邪を引いた時は\n'
        for i in range(5):
                result += '「' + most[i][0] + '」\n'
        result += 'を食べるといいよ!'
        return result

結果

風邪を引いた時は
「年」
「風邪」
「食」
「食べ物」
「日」
を食べるといいよ!
  • へー...

コード全体

import sys
import requests
import MeCab
import xml.etree.ElementTree as ET
import collections

# yahoo検索結果のXMLを取得
def search(keyword):
    url = 'https://shopping.yahooapis.jp/ShoppingWebService/V1/itemSearch'
    url += '?appid=' + 'アプリID' # アプリのID
    url += '&query=' + keyword # 検索ワード
    url += '&hits=50' # 取得件数(最大50件)
    response = requests.get(url.encode('utf-8')) # utf-8にエンコードしないといけない
    return response.text

# XMLから全ての商品説明文を取得する
descriptions = []
def get_descriptions(parent):
    for child in parent:
        if child.tag == "{urn:yahoo:jp:itemSearch}Description" and child.text != None: # なんか{urn:yahoo:jp:itemSearch}が必要みたい
            descriptions.append(child.text)
        get_descriptions(child)

# 文章から名詞を抜き出し、それをリストにして返す
def get_words_list(s):
    mecab = MeCab.Tagger ("-Ochasen")
    node = mecab.parseToNode(s)

    foods = []
    while node:
        if node.feature.split(',')[0] == '名詞' and node.surface >  '~': # 名詞抜き出し and 雑に記号を排除
            foods.append(node.surface)
        node = node.next
    
    return foods

# XMLを渡すと、重複ありの名詞単語リストが返される
def get_foods(xml_txt):
    root = ET.fromstring(xml_txt) # XMLの根
    get_descriptions(root) # XMLから説明文を抜き出す

    foods = []
    for description in descriptions:
        foods += get_words_list(description)
    
    return foods

def get_result(foods):
    most = collections.Counter(foods).most_common()
    result = '風邪を引いた時は\n'
    for i in range(5):
        result += '「' + most[i][0] + '」\n'
    result += 'を食べるといいよ!'
    return result

# 1. 検索結果を取得
xml_txt = search('風邪 食べ物')

# 2. 商品説明文の単語を取得
foods = get_foods(xml_txt)

# 3. これを食べます
result = get_result(foods)
print(result)

WindowsでGraphillionの環境構築

はじめに

  • これを参考に環境構築していく
  • 詰まった部分が多かったのでメモ。
  • 結局どういう手順を通るのが最適なのかわからないのでやったことを全部書いた。読みづらい。
  • もっと簡単にインストールするする方法があったら教えてほしい。

モチベーション

  • 組み合わせ爆発のおねえさんを救うことができる。
  • 動画では10×10の組み合わせの数え上げに25万年かかってるけどGraphillionを使えば一瞬で終わる。かなり古い動画だけど今更使うの?時代遅れじゃない?とか言わないでほしい。

環境

  • Windows 10
  • プログラミング系のソフトは何も入っていない状態(コンピュータをセットアップしたばかりなので)

1. Anacondaをインストールする

手順

  1. windows用のAnacondaをダウンロードする。

  2. AnacondaのPATHを通してどこからでもPythonを使えるようにする。

2.Graphillionの実行に必要なツールをインストールする(これいらない)

  • WindowsではVisual C++ 2015 Redistributable Packagesをダウンロードする必要があるみたい。
  • 確かGraphillionは後ろでC++が動いてるから必要なんだと思う。
  • 結局後でアンインストールするのでインストールする必要はなかった。本当に必要ないかどうかはわからないが、こいつをアンインストールしてもGraphillionが動くのでたぶん問題ない。たぶん。
  • 一応手順を書いとく

手順

  1. ダウンロードをクリックすると「vc_redist.x64.exe」と「vc_redist.x86.exe」のどれをダウンロードするするかというチェックボックスが出てくる。

  2. x64は64ビット版でx86は32ビット版(この数字クソみたいにわかりづらいので何とかしてほしい)。

  3. このコンピュータは64ビットなので「vc_redist.x64.exe」を選択する。するとインストーラーがダウンロードされる。

  4. インストーラーを実行するとセットアップが完了する。

3. Graphillionをインストールする(これが大変)

手順(これが理想らしいけどこんなスムーズに進まなかったんだけど)

  1. ソースコード(tar.gz または zip ファイル)を https://github.com/takemaru/graphillion からダウンロードする
  2. アーカイブを展開し、ソースコードディレクトリ(setup.py があるディレクトリ)に移動する
  3. python setup.py build を実行してビルドする
  4. (任意) python setup.py test -q でテストを行う
  5. sudo python setup.py install を実行してインストールする

僕がたどった手順

error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/
  • どうやらMicrosoft Visual C++ 14.0ってやつが必須らしい。とりあえず表示されたURL(https://visualstudio.microsoft.com/downloads)にアクセスする。
  • そのページでは、Visual Studio 2019のダウンロード画面が表示される。これをダウンロードしろってことか?よくわからんがこれをダウンロードすればpython setup.py buildを実行するのに必要な周辺ツールが全部インストールされるんだろう。ということでVisual Studio 2019をダウンロードする。
  • VS2019のインストーラーを実行するとオプションとして何かインストールするかを尋ねられる。なんかいろんなやつが表示されてよくわからんなぁ。そういえばさっきのエラーメッセージで「Microsoft Visual C++ 14.0 is required」って言われたしそれぽいやつをインストールするかぁ。ということで、表示された項目の中で「MSVC v140 - VS 2015 C++ ビルド ツール (v14.00)」が一番それぽいのでこの項目にチェックをつけてインストールを開始した(これが必要なのかどうかは最後までよくわからなかったけどたぶんいらない)。
  • さて、さっきエラーメッセージで言われたツール(VS2019)は入れたし完璧。python setup.py buildをもう一度実行する。すると、メッセージがたくさん出てきた。インストールが進んでるっぽい画面。お!これは成功したんじゃね?適当にツール入れるだけで成功させるとか天才かもしれんわ。
  • 失敗する。
  • メッセージはさっきとかわらずerror: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/だった。や、お前さっきツール入れたやん。なんでそこで失敗するねん。

  • よくわからんくなって適当にググってたらこの記事にたどり着いた。文章量多いけどとりあえず「Windows 10 SDK 」ってやつを入れないとだめらしい。僕は「MSVC v140 - VS 2015 C++ ビルド ツール (v14.00)」が必要そうだと思って入れたけど、本当に必要なのは「Windows 10 SDK 」だったみたい。ノリで環境構築を進めるとこういうことが起きる。VS2019の画面で新しいツールをインストールする画面に移動する。Windows 10 SDK を探すと同じような名前のやつがいくつか出てくるけど、とりあえず「Windows 10 SDK (10.0.17763.0)」を選択してインストールする

  • そして、python setup.py buildを実行する。成功してくれ~
  • 失敗する。
  • でもさっきまで出てたerror: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/は消えた。どうやらこの問題は解決したようだ。

  • 今回出たエラーは以下の通り。たぶんAnaconda3のファイルが開けないっていうエラーメッセージ。なので、c:\users\<name>\anaconda3\include\にパスを通す。なんか成り行きでc:\users\<name>\anaconda3\include\C:\Users\<name>\Anaconda3\ScriptsC:\Users\<name>\Anaconda3\DLLsにもパスを通した。これはグラフ列挙アルゴリズムのp152に書いてあったパス。必要かどうかわからんけど追加しといた。

c:\users\<name>\anaconda3\include\pyconfig.h(203): fatal error C1083: include ファイルを開けません。'basetsd.h':No such file or directory
  • そして、python setup.py buildを実行。4回目なのでそろそろ成功してくれ。

  • 失敗する。

  • 今回のエラーメッセージは以下の通り。rc.exeが実行できないらしい。なんじゃそのファイル。
LINK : fatal error LNK1158: 'rc.exe' を実行できません。
  • いろいろググるこの記事が出てくる。なんかrc.exeが競合してるらしい。この記事にはパスを変えて優先度を変えるみたいなことがかいてある。でも僕の環境には記事に書いてあるようなパスがそもそも存在しない...でも、競合という単語を聞いて一応思い当たる節があった。それは、たぶん必要だろうと思ってインストールしたいろんなツール。たぶん似たようなツールを入れたから競合を起こしてるんだろなぁと思ったので、とりあえずVisual C++ 2015 Redistributable Packagesでインストールしたものをアンインストールした。これはインストーラーをダブルクリックするだけで「アンインストールする」という選択肢が出てきたので3秒で完了。
  • そしてpython setup.py buildを実行する。5回目だぞ。わかるよな?そろそろ空気読んで成功してくれ。
  • 失敗する。
  • エラーメッセージはさっきと変わらずrc.exeが実行できないというもの。うーん、なんでだろ。
  • ググりまくると、この記事が出てきた。僕が知りたいことドンピシャじゃん。神か?

  • やることは、あるフォルダにあるrc.exercdll.dllを別のフォルダに移動させることらしい。

  • 僕の場合はC:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x64にあるrc.exercdll.dllC:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\binにコピーした。
  • そしてpython setup.py buildを実行する。6回目の挑戦。頼む~。成功してくれ~
  • 成功~
  • 手順3まで終了したので、次は手順4のpython setup.py test -qを実行する。任意らしいけど一応やっておく。そんで、これも成功する。
  • 手順5はsudo python setup.py installの実行らしいが、cmdではこれを実行できない。sudoが存在しないって言われる。なので、python setup.py installを実行する。成功する。やったぜ!!
  • これでインストールのすべての手順が終了した。2,3時間くらいかけてしまった。疲れた。

使ってみる

  • NetworkXとMatplotlibを入れた。任意らしいけど、チュートリアルで使うらしい。本当に必要かはよくわからんがとりあえず入れておいた。
pip install networkx
pip install matplotlib
from graphillion import GraphSet
import graphillion.tutorial as tl
GraphSet.set_universe(tl.grid(10, 10)) # 10×10のグリッドをセットする
paths = GraphSet.paths(1, 121) # グリッドの左上と左下を指定してパスを探索する
paths.len() # パスの総数を出力する。1568758030464750013214100と出力されるはず
  • 動画と同じ結果が出力されたのでGraphillionをちゃんと動かせてるみたい。やったぁ

コイン問題

考察

  • 状態変数を考える
  • dp[i円の最小枚数]でいけそう。50000円がMaxだから制約的にも行けそう
  • 次に遷移を考える
  • dp[i] ← dp[i-c[j]] + 1みたいな感じで行けそう。計算量も$O(nm)$で余裕がある。最大で$106$ステップだし

解法

  • dp[i円の最小枚数]という状態を持つ
  • dp[i] ← dp[i - c[j]] + 1という遷移を行う

コード

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

#define int long long

const int INF = 1e15;

int dp[55555];
int c[22];

int n, m;

signed main() {
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    cin >> c[i];
  }

  fill(dp, dp+n+5, INF);
  dp[0] = 0;
  for (int i = 0; i <= n; i++) {
    for (int j = 0; j < m; j++) {
      if (i - c[j] >= 0) {
        dp[i] = min(dp[i], dp[i-c[j]] + 1);
      }
    }
  }

  cout << dp[n] << endl;

  return 0;
}

メモ化再帰で解いてみる

再帰

  • これはTLE
  • 計算量は$O(mn)$
#include <bits/stdc++.h>
using namespace std;

#define int long long

const int INF = 1e15;

int dp[55555];
int c[22];

int n, m;

int rec(int money) {
  if (money == 0) {
    return 0;
  }

  int ans = INF;
  for (int i = 0; i < m; i++) {
    if (money - c[i] < 0) continue;
    ans = min(ans, rec(money - c[i]) + 1);
  }

  return ans;
}

signed main() {
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    cin >> c[i];
  }

  cout << rec(n) << endl;

  return 0;
}

メモ化再帰

  • 蟻本のナップサックのメモカ再帰っぽく書ける
  • 計算量はたぶん$O(nm)$
#include <bits/stdc++.h>
using namespace std;

#define int long long

const int INF = 1e15;

int dp[55555];
int c[22];

int n, m;

int rec(int money) {
  // 終了条件
  if (money == 0) {
    return dp[money] = 0;
  }

  // 計算済みならその値を返す
  if (dp[money] != INF) {
    return dp[money];
  }

  int ans = INF;
  for (int i = 0; i < m; i++) {
    if (money - c[i] < 0) continue;
    ans = min(ans, rec(money - c[i]) + 1);
  }

  // 計算した値をメモする
  return dp[money] = ans;
}

signed main() {
  cin >> n >> m;
  for (int i = 0; i < m; i++) {
    cin >> c[i];
  }

  fill(dp, dp + n + 10, INF);
  cout << rec(n) << endl;

  return 0;
}

観音堂

考察

  • 状態変数を考える
  • dp[i段目までの組み合わせの個数]で出来そう
  • 次に状態遷移を考える
  • 初期位置から1, 2, 3段目は1通りあるので、dp[1] = dp[2] = dp[3] = 1で初期化する。
  • 遷移はdp[i+1] += dp[i]とかで出来そう

解法

  1. dp[i段目までの組み合わせの個数]という状態を持つ
  2. dp[1] = dp[2] = dp[3] = 1で初期化する
  3. 遷移は以下の通り
dp[i] → dp[i+1]
            → dp[i+2]
            → dp[i+3]

コード

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

#define int long long

int dp[50];
int n;

// 前計算
void init() {
  dp[0] = 1;
  for (int i = 0; i <= 30; i++) {
    for (int j = 1; j <= 3; j++) {
      dp[i+j] += dp[i];
    }
  }
}

signed main() {
  init();
  while (1) {
    cin >> n;
    if (n == 0) break;
    int mult = 3650;
    cout << (dp[n] + mult - 1) / mult << endl; // 切り上げ
  }

  return 0;
}

自分で書いたコード

  • 思いついたまままに書いたコード
  • min(1LL, hoge)は、一度提出してWAが出たから直した部分(ローカルでちゃんと確認しなかった)
  • 切り上げ、最近使わないのですっかり忘れてた

メモ化再帰で解いてみる

再帰のコード

  • これ、$O(3N)$だから通らないと思ったけど通っちゃったよ。えー、なんでー?
#include <bits/stdc++.h>
using namespace std;

#define int long long

int n;

int rec(int stage) {
  // 終了条件
  if (stage == 0) {
    return 1;
  }

  int ans = 0;

  for (int i = 1; i <= 3; i++) {
    if (stage - i < 0) continue;
    ans += rec(stage - i);
  }

  return ans;
}

signed main() {
  while (1) {
    cin >> n;
    if (n == 0) break;
    int ans = (rec(n) + 3650 - 1) / 3650;
    cout << ans << endl;
  }

  return 0;
}

メモ化再帰のコード

  • 蟻本のナップサックのメモ化再帰みたいに書ける
#include <bits/stdc++.h>
using namespace std;

#define int long long

int n;
int dp[55];

int rec(int stage) {
  // 終了条件
  if (stage == 0) {
    return dp[stage] = 1;
  }
  
  // 一度求めた値の場合、メモしたやつを返す
  if (dp[stage] != -1) {
    return dp[stage];
  }

  int ans = 0;

  for (int i = 1; i <= 3; i++) {
    if (stage - i < 0) continue;
    ans += rec(stage - i);
  }

  // 値を求めたらメモる
  return dp[stage] = ans;
}

signed main() {
  // 前処理
  fill(dp, dp+33, -1);
  rec(30);

  while (1) {
    cin >> n;
    if (n == 0) break;
    int ans = (dp[n] + 3650 - 1) / 3650;
    cout << ans << endl;
  }

  return 0;
}

感想

  • 状態変数考えて遷移書いたらなんか上手くいった。問題文に「値が大きくなる」と書いてあったので、この遷移で値大きくなるのか〜?みたいに思った(実際大きくなったけど)
  • なんでただの再帰で解けるんだ?計算量的に無理では?

react-native log-iosがしたい

問題

  • react-native log-iosをしてもログが取れない

解決策

react-native-log-iosの使い方

$ yarn global add react-native-log-ios # インストール
$ react-native-log-ios <project-name> # ログ取り開始