2021/04/12

概略

今日は確定申告を完了した. とてもえらい. 今日明日くらいは働かなくても許されるんじゃないか?

というわけにも行かず. 今日も何故かそんなに重たいことはしていないが疲れた気持ちがある.

なんだろう. やることやってはいるんだけど, どこまで何やりゃええねんの迷子になっている気もする.

今日の競プロ

本日もランダム.

atcoder.jp

試合進めてったら2人残ったけど最初何人居たんよ. の範囲を求める問題.

後ろから順に計算していけるので, 計算していけばok.

たまにはRustで

Submission #21701159 - AtCoder Grand Contest 020

use proconio::input;

fn is_invalid(min: i64, max: i64, value: i64) -> bool {
  return (min / value) == (max / value) && min % value != 0
}

fn main() {
  input! {
    n: usize,
    a: [i64;n],
  }

  let mut min: i64 = 2;
  let mut max: i64 = 2;
  for i in (0..n).rev() {
    if is_invalid(min, max, a[i]){
      println!("{}", -1);
      return;
    }
    min = (min / a[i] + i64::from(min % a[i] != 0)) * a[i];
    max = (max / a[i] + 1) * a[i] - 1;
  }
  println!("{} {}", min, max);
}

2021/04/11

概略

日曜日. 明日から仕事... というか確定申告もしなくては.

今日は断髪をして飯を食いに行った. いつも行っている散髪屋の店員さんの当たりハズレがわかってきた気がする. 今日の人は当たり. 明らかに手際が良すぎる.

ご飯は久しぶりにオタクと肉を食べに行った. f:id:kilattoeruru:20210412002832j:plain

美味い肉は美味い.

明日も早くはないけどこの気分のまま眠りたい. お疲れ様.

2021/04/10

概略

今日は午前にGCJ, 午後は買った収納グッズが届いたのでオタクグッズの押し込み作業をした. 引っ越してからずっとダンボールに入れていたグッズたちもやっとまともな収納グッズに入れることが出来たし, 収納グッズの天板部分にアクリルフィギュアとかも飾れるようになった. これはQoLが上がる. その分部屋は狭くなったが.

actix-webを触ってるだけの話.

最近Androidの内部実装にも使われだしたとかでちょっと話題になったRust.

そのRustのweb-frameworkにactix-webがあってこいつの性能がすこぶる良いらしいと聞いて触ってみているというだけの話. 今日はログインAPIを書くトコまで.

Actix Web | A powerful, pragmatic, and extremely fast web framework for Rust.

使ってるパッケージはこんな感じ, 多分要らないのも混じっている.

[dependencies]
actix-web = "3.3.0"
actix-files = "0.5.0"
actix-http = "2.2.0"
actix-multipart = "0.3.0"
actix-web-actors = "3.0.0"
actix-web-codegen = "0.4.0"
actix-cors = "0.5.3"

actix-rt = "1.1.1"
actix-utils = "2.0.0"
actix-redis = "0.9.1"
actix-session = "0.4.0"
actix-identity = "0.3.1"

# for front
handlebars = { version = "3.5.0", features = ["dir_source"] }
serde = {version = "1.0", features = ["derive"]}
serde_json = "1.0"
serde_yaml = "0.8"

# for DB
diesel = { version = "1.4.5", features = ["postgres",  "r2d2", "chrono"] }
dotenv = "0.15.0"
chrono = { version = "0.4.6", features = ["serde"] }
bcrypt = "0.8"

# for logging
log = "0.4.11"
env_logger = "0.8.1"
derive_more = "0.99.11"

ORMとしてはdieselが有名.

Diesel

featuresにchronoを加えておくことで, timestampの扱いが楽になる. パスワードの暗号化とかはbcryptで.

dieselの設定でschemaの出力先を変えておくと後々が楽になる. (diesel.toml)

# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/repositories/schema.rs"

ついでにコマンド時にmigrationを置くディレクトリを指定出来る.

これについてはtomlに入れれば効くようにするPRがmergeされてるので, 近々tomlに入れられるかも => Configure the migration directory in diesel.toml by theredfish · Pull Request #2179 · diesel-rs/diesel · GitHub)

$ diesel setup --migration-dir db/migrations
$ diesel migration generate [migration-name] --migration-dir db/migrations

後は適当にSQLを書き書きしてmigration

CREATE TABLE user_roles (
  id INTEGER NOT NULL UNIQUE,
  role_name VARCHAR(20) NOT NULL PRIMARY KEY
);

-- create index
CREATE INDEX idx_user_roles_id ON user_roles ( id );

CREATE TABLE users (
  id SERIAL NOT NULL PRIMARY KEY,
  name VARCHAR NOT NULL,
  email VARCHAR(255) NOT NULL UNIQUE,
  password VARCHAR NOT NULL,
  role VARCHAR(20) NOT NULL
);

-- create index
CREATE INDEX idx_users_email ON users ( email );
CREATE INDEX idx_users_role ON users ( role );

-- add foreing key
ALTER TABLE users 
  ADD CONSTRAINT fk_users_role
  FOREIGN KEY ( role )
    REFERENCES user_roles ( role_name )
    ON DELETE CASCADE 
    ON UPDATE CASCADE;
table! {
    user_roles (role_name) {
        id -> Int4,
        role_name -> Varchar,
    }
}

table! {
    users (id) {
        id -> Int4,
        name -> Varchar,
        email -> Varchar,
        password -> Varchar,
        role -> Varchar,
    }
}

joinable!(users -> user_roles (role));

allow_tables_to_appear_in_same_query!(
  user_roles, users,
);

DB接続プールを取得する関数を作って

//use diesel::prelude::*;
use diesel::pg::PgConnection;
use diesel::r2d2::{self, ConnectionManager};
use dotenv::dotenv;
use std::env;

pub type DbPool =
  r2d2::Pool<ConnectionManager<PgConnection>>;

pub fn build_db_pool() -> DbPool {
  dotenv().ok();

  let database_url = env::var("DATABASE_URL")
    .expect("DATABASE_URL must be set");
  let manager =
    ConnectionManager::<PgConnection>::new(database_url);
  r2d2::Pool::builder()
    .build(manager)
    .expect("Failed to create pool.")
}

行データに対応するモデルを作っておいて

use crate::models::user_roles::UserRole;
use crate::repositories::schema::users;
use diesel::Identifiable;

#[derive(
  Debug,
  Clone,
  Serialize,
  Deserialize,
  Eq,
  PartialEq,
  Identifiable,
  Associations,
  Queryable,
  AsChangeset,
)]
#[belongs_to(UserRole, foreign_key = "role")]
#[table_name = "users"]
#[primary_key("id")]
pub struct User {
  pub id: i32,
  pub name: String,
  pub email: String,
  pub password: String,
  pub role: String,
}

#[derive(
  Debug,
  Clone,
  Serialize,
  Deserialize,
  PartialEq,
  Insertable,
)]
#[table_name = "users"]
pub struct NewUser {
  pub name: String,
  pub email: String,
  pub password: String,
  pub role: String,
}


#[derive(Debug, Serialize, Deserialize)]
pub struct CreateSession {
  pub email: String,
  pub password: String,
}

RESTっぽいUtilityを実装しておいて

use crate::models::users::{NewUser, User};
use crate::repositories::schema::users::dsl::*;
use diesel::prelude::*;


impl User {
  pub fn find(
    q_id: &i32,
    conn: &PgConnection,
  ) -> Result<Option<User>, diesel::result::Error> {
    let user = users
      .filter(id.eq(&q_id))
      .first::<User>(conn)
      .optional()?;

    Ok(user)
  }

  pub fn find_by_email(
    q_email: &str,
    conn: &PgConnection,
  ) -> Result<Option<User>, diesel::result::Error> {
    let user = users
      .filter(email.eq(&q_email))
      .first::<User>(conn)
      .optional()?;

    return Ok(user);
  }

  pub fn create(
    new_user: &NewUser,
    conn: &PgConnection,
  ) -> Result<User, diesel::result::Error> {
    diesel::insert_into(users)
      .values(new_user)
      .get_result(conn)
  }
}

パスワード照合処理を書いて

use bcrypt::{hash, verify, BcryptError, DEFAULT_COST};

static COST: u32 = DEFAULT_COST;

pub fn hash_password(
  password: &str,
) -> Result<String, BcryptError> {
  hash(password, COST)
}

pub fn verify_password(
  password: &str,
  hash: &str,
) -> Result<bool, BcryptError> {
  verify(password, hash)
}

ログイン / ログアウト処理を書いてみる.

use actix_identity::Identity;
use actix_web::{web, HttpResponse, Result};

use crate::models::users::CreateSession;
use crate::models::users::User;
use crate::repositories::connection::DbPool;
use crate::services::crypt::verify_password;
use crate::services::errors::StatusError; // 自分で適当に書くカスタムエラー
// ** NOT SUPPORT SIGN UP **

pub async fn signin(
  info: web::Json<CreateSession>,
  id: Identity,
  pool: web::Data<DbPool>,
) -> Result<HttpResponse, StatusError> {
  let conn = pool.get()?;
  if let Some(user) =
    User::find_by_email(&info.email, &conn)?
  {
    if verify_password(&info.password, &user.password)? {
      id.remember(user.id.to_string());
      return Ok(HttpResponse::Ok().finish());
    }
  }
  Err(StatusError::Unauthorized)
}

pub async fn signout(id: Identity) -> Result<HttpResponse> {
  id.forget();
  Ok(HttpResponse::NoContent().finish())
}

これでroutingを書いて

use actix_web::{web};
use crate::controllers;

// 使えるのはservice, route, scope, resource等だけ, default_serviceとかは使えないので注意
pub fn route_configure(cfg: &mut web::ServiceConfig) {
  cfg.service(
    web::scope("/auths")
      .service(
        web::resource("/signin").route(
          web::post().to(controllers::apis::auths::signin),
        )
      )
      .service(
        web::resource("/signout").route(
          web::delete().to(controllers::apis::auths::signout),
        )
      ),
  );
}

identityの設定を書いて

use actix_identity::{
  CookieIdentityPolicy, IdentityService,
};
use actix_http::cookie::SameSite;

pub fn identity_configure() -> IdentityService<CookieIdentityPolicy>{
  IdentityService::new(
    CookieIdentityPolicy::new(&[0; 32]) // Cookieに認証情報を持つ
      .name("auths")  // Cookieの名前
      .path("/") // Cookieを送信対称とするパス
      .domain("localhost:8088") // Cookieを送信対称とするドメイン
      .same_site(SameSite::Strict) // Cookieを送信するポリシー設定
      .max_age(3600) // 有効期限
      .secure(true), // Secureをつけるかどうか
  )
}

mainを書く

#[macro_use]
extern crate serde;
#[macro_use]
extern crate serde_json;
extern crate serde_yaml;
#[macro_use]
extern crate diesel;

use actix_web::{web, App, HttpServer};

use std::env;
use std::io::{Error, ErrorKind};
use env_logger as logger;

mod models;
mod repositories;
mod controllers;
mod services;
mod configs;

#[actix_rt::main]
async fn main() -> std::io::Result<()> {
  // logger setting
  std::env::set_var(
    "RUST_LOG",
    "actix_web=info,diesel=debug",
  );
  logger::init();
  
  let db_pool = repositories::connection::build_db_pool();

  // start server
  HttpServer::new(move || {
    let identity = configs::identity::identity_configure();

    App::new()
      .wrap(identity)
      .data(db_pool.clone())
      .configure(configs::routes::route_configure)
      .default_service(
        web::route().to(controllers::not_found),
      )
  })
  .bind("127.0.0.1:8088")?
  .run()
  .await
}

これでセッション情報をCookieに保持するログインAPIの出来上がり(?)

> curl -X POST -v http://localhost:8088/auths/signin -d '{"email":"kilattoeruru@gmail.com","password":"hogefugafoobar"}' -H "Content-Type: application/json"
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8088 (#0)
> POST /auths/signin HTTP/1.1
> Host: localhost:8088
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 61
> 
* upload completely sent off: 61 out of 61 bytes
< HTTP/1.1 200 OK
< content-length: 0
< set-cookie: auths=xxxxx; HttpOnly; SameSite=Strict; Secure; Path=/; Domain=localhost:8088; Max-Age=3600
< date: Sat, 10 Apr 2021 14:11:39 GMT
< 
* Connection #0 to host localhost left intact

今日はここまで

GCJ

Round 1 Aは敗退, 2445位. 1問目完答, 2問目部分点まで.

ボーダー的には1問目完答, 2問目部分点, 3問目部分点が速く出せれば行けてたようだ. 残念.

解答上げて良いのか不明なのでまだ伏せておく. 多分全部終わったらgithubに投げる.

今日はGCJやったし競プロはおやすみで良いかなぁ.

2021/04/09

概略

一週間が終わってしまった. 今週は過去のクソコードと戦う週だった. やはりクソコードはクソである.

ということを強く感じて以前に買ったリファクタリングを再履修することにした.

少なくとも自分だけでも読めるコードを書いていきたい.

今日の競プロ

もはや競プロ精進日記になりつつあるなぁとちょっと思っている. 明日はCodeJamのRound1がある. 参加できたらしよう.

今日は

atcoder.jp

ヒューリスティックに解けるタイプの400点. これ400点なのか...? とか言っていたら計算ミスってWAを出した. 反省.

前から'a'に出来るならして, それ以外はスルー, 余った分を最後の文字に適用したことにすればよい.

Submission #21586222 - CODE FESTIVAL 2016 qual A

#include <bits/stdc++.h>
using namespace std;
int main(){
  string s;
  int k, n;
  cin >> s >> k;
  n = s.size();
  // 先頭から'a'に出来るならaにする
  for(int i=0; i<n-1; i++){
    if(s[i] == 'a') continue;
    int mv = 'z' - s[i] + 1;
    if(mv <= k){
      s[i] = 'a';
      k -= mv;
    }
  }
  // 末尾を余った分処理したことにする
  s[n-1] = char((int(s[n-1] - 'a') + k) % 26) + 'a';
  cout << s << endl;
}

2021/04/08

概略

今日は新卒の子諸々と共にお昼に行った. 四川料理を調子に乗って中辛で頼んで痛い目を見た. しんどい.

(本当に中辛なのか? これ) f:id:kilattoeruru:20210408235334j:plain

新しいWebFrameworkが出ていた

GitHub - ntex-rs/ntex: framework for composable networking services

webframeworkのベンチマーク的にはactix-webを超えているらしいがはてさて. 一通りactix-webを使い倒してみたら触ってみようかなぁと思う. drogonも結局Hello worldしか出来ていないし, 圧倒的時間不足を感じる

今日の競プロ

ランダム

今日は atcoder.jp

サイトの埋め込みがなんかいい感じに出来ることにやっと気付いた. これで毎回タイトルを手打ちしなくて良い. エンジニア力に欠けていた.

ある数式がある値になるような変数の値を探す数値最適化問題.

一個答え分かれば良いので, にぶたんで出来る. 計算量が見積もれれば焼きなましとかでも解けるのだろうか. 雑に投げたニュートン法だと落ちた. (それはそう)

上限はa, bが最大100で, 相反する挙動をしていたとしてもt = 200までで必ずどこかで100になる瞬間があるので, 201からくらいにしておいてok.

 O(\log 10^{10})くらい

正答: Submission #21573927 - AtCoder Beginner Contest 026

#include <bits/stdc++.h>
using namespace std;
double a, b, c;

double calc(double t){
  return a * t + b * sin(c * t * M_PI) - 100.0;
}

int main(){
  cin >> a >> b >> c;

  double lp=0, up=201;
  double eps = 1e-8;
  while(abs(calc((lp + up)/2)) > eps) {
    double mid = (lp + up)/2;
    if(calc(mid) < 0) lp = mid;
    else up = mid;
  }
  cout << setprecision(15) << (lp + up) / 2 << endl;
}

2021/04/07

概略

今日はとても疲れたので中トロの寿司を食べてさっさと寝ることとする.

なんだろう, 自分が出来ないのが悪いのは確かなのだけれど, 出来ない原因が自分だけかと言われれば絶対にNoであるというか他因の方が大いに多くて腹が立ってしまうなどしている.

お疲れ様.

2021/04/06

概略

今日は新卒の方と懇親会? みたいなのがありました. PJの闇を多く語り過ぎた気もする.

昨日からエヴァの過去映画を見始めているが, なるほどわからん.

今日の競プロ

本日もランダムなりて

今日はABC192D Base n

整数Xがmax_element(X)より大きなN進数で解釈した場合にM以下の何種類の数字として読めるかを求める物

Xが1桁の場合は進数によらず数値はXのため, XがM以下かどうかで1 / 0 分岐のみ, 2桁以上の場合は, 進数ごとに値が確実に異なるため, 実質的に「条件に合うN進数でXを解釈した時に値がM以下になる進数の数」が解答になる.

2桁以上の場合は最上位桁が0ではない制約があるので, Nは単調増加. よって二分探索が使える.

それぞれのNについてN進数で解釈してM以下になるかを検証していけばok.

2桁かつ最上位桁が1の場合でもM+1進数ではMを超えるので, 上限はM+1で十分. ちなみに初め上限を1e+18 + 1にして落ちた. この記法結構使っていたのだけれど, よくよく考えればdoubleの有効桁数は15桁くらいのはずなので, 19桁 + 1すると確かに計算誤差が凄い. 10000....0LLとかなら通る. 気をつけよう.

オーバーフローしないよう, 値の確認は割り算で.

 O(Length(X) \log M)

submit result (書き終えてから気付いたけどaccumulateのlambdaの中charじゃないな...)

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

int main(){
  string xs;
  ll m;
  cin >> xs >> m;
  int n = xs.size();

  // 面倒なので数値に直しておく
  vector<ll> x(n);
  for(int i=0; i<n; i++) x[i] = xs[i] - '0'; 

  // 1桁の場合比較で完結
  if(x.size() == 1){
    cout << (x[0] <= m ? 1 : 0) << endl;
    return 0;
  }

  ll d = *max_element(x.begin(), x.end());

  // にぶたん
  ll lp = d, rp = m + 1;
  while(rp - lp > 1) {
    ll mid = (rp + lp) / 2;
    ll result = accumulate(x.begin(), x.end(), 0LL, 
      [m, mid](ll acc, char cur) {
        return (acc > (m - cur) / mid ? m + 1 : acc * mid + cur);
      });
    if(result > m) rp = mid;
    else lp = mid;
  }
  cout << lp - d << endl;
}