おはやし日記

テーマ……バイク←プログラミング←旅

FIT形式のファイルをスマホでCSVに変換する-1

こんにちは。バイクで走ってる時の走行ログをとるのに中華サイコンを買いました。

o-treetree.hatenablog.com

↑この記事でも使っているのですが、データ処理をスマホ上で完結させるためにアプリを作ったので色々メモしておきます。

  • FITファイルとは
  • スマホで使うために
    • jarをプロジェクトに組み込む
    • アプリに外部ストレージの読み書き権限を与える
    • 実行
  • 参考

FITファイルとは

Garminのデバイス等で採用されている、スポーツ・フィットネスデバイスの情報共有プロトコル?です。詳しくは以下を見てください。

developer.garmin.com

今回僕が買った中華サイコン↑は、アプリを通してデータをFIT形式で出力できます。ただ、これはバイナリファイルなのでそのまま開いても人間には解読不能です。

そこで、FitCSVToolというありがたいプログラムが公式から配布されています。

developer.garmin.com

詳細は省きますが、javaを使える環境にダウンロードして

java -jar FitCSVTool.jar hogehoge.fit

hogehoge.csvが生成されるといった具合です。

続きを読む

AlertDialogとSnackbarの合わせ技

課題

ダイアログの中でSingleChoiceItemsをクリックした時にSnackbarをDialogの上に出したい

現状

だいたいこんな感じ

String[] strings = new String[]{"hoge", "fuga", "piyo"};
new AlertDialog.Builder(this)
                .setTitle("Before")
                .setSingleChoiceItems(strings, -1, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Snackbar.make(findViewById(R.id.coordinator), strings[i]+" was selected", Snackbar.LENGTH_SHORT).show();
                    }
                })
                .setNeutralButton("閉じる",null).show();

f:id:o-treetree:20210410042841p:plain

ダイアログの中のアイテムを押した時、Snackbarは出るがDialogの下にある

解決

androidDialogSnackbar

f:id:o-treetree:20210410043722p:plain

アイテムを押すと、Dialogの中にSnackbarが出ます。ちなみにSnackbarにアクションを設定しておけば直接押すことができます(以前のようにDialogの下に出てると、Dialogを消さないとSnaackbarのボタンに触れなかった)。

Snackbarが出る時にDialogがちょっと膨らんで動くが妥協範囲内。

ポイント

そして

  1. 自作したViewをinflateで設定(final View v
  2. AlertDialogをdialog = ....create()までする
  3. SingleChoiceItemsOnClickListenerで出すSnackbarは先ほど設定したvに紐づける
  4. vの中のボタンandroid:id="@+id/dialog_button"のonClickでdialog.dismiss()をするようにする
  5. dialog.show()

おわり

Snackbarをスワイプした時にログに下記のエラーが出るが、一応動いているのでヨシ!

E/ViewDragHelper: Ignoring pointerId=0 because ACTION_DOWN was not received for this pointer before ACTION_MOVE. It likely happened because  ViewDragHelper did not receive all the events in the event stream.
E/ViewDragHelper: Ignoring pointerId=-1 because ACTION_DOWN was not received for this pointer before ACTION_MOVE. It likely happened because  ViewDragHelper did not receive all the events in the event stream.

mstdn.jpのアプリケーションのユーザー認証

メモ。

アプリの登録

  • client_name -> アプリ名
  • scopes -> アプリの権限。複数あるとき空白区切りでいいらしい
curl -X POST \
 https://mstdn.jp/api/v1/apps \
 -F 'client_name=ohaToot' \
 -F 'redirect_uris=urn:ietf:wg:oauth:2.0:oob' \
 -F 'scopes=write'

レスポンス

{"id":"602156","name":"ohaToot","website":null,"redirect_uri":"urn:ietf:wg:oauth:2.0:oob","client_id":"CLIENT_ID","client_secret":"CLIENT_SECRET","vapid_key":"VAPID_KEY"}

CLIENT_IDCLIENT_SECRET等は実際それぞれの値が返ってくる

アプリケーションの認証

https://mstdn.jp/oauth/authorize
?client_id=CLIENT_ID
&scope=write
&redirect_uri=urn:ietf:wg:oauth:2.0:oob
&response_type=code

にブラウザでアクセスする。

  • scope -> OAuth Scopes - Mastodon documentationにあるアプリの権限
  • redirect_url -> 自分でサーバー持ってればそのURLにしてどうこうできるらしいが、できないのでこの変なやつにしておく

f:id:o-treetree:20210319220107p:plain
認証画面

「承認」を押すと、認証コード(authorization code)が出てくる

f:id:o-treetree:20210319220356p:plain
認証コード

redirect_urlに自前のサイトがないのでここが手動コピペになる

アドレスバーを見るとhttps://mstdn.jp/oauth/authorize/native?code=AUTHORIZATION_CODEになっているのでそこでどうにかできるかもしれない。知らんけど。

アクセストークンの取得

  • scope -> アプリケーションに付与した権限内で。
curl -X POST \
https://mstdn.jp/oauth/token \
-F 'client_id=CLIENT_ID' \
-F 'client_secret=CLIENT_SECRET' \
-F 'redirect_uri=urn:ietf:wg:oauth:2.0:oob' \
-F 'grant_type=authorization_code' \
-F 'code=AUTHORIZATION_CODE' \
-F 'scope=write' 

レスポンス

{"access_token":"ACCESS_TOKEN","token_type":"Bearer","scope":"write","created_at":1616159761}

API利用

ようやく色々できるようになった

トゥート投稿statuses - Mastodon documentation

curl -X POST \
https://mstdn.jp/api/v1/statuses \
-H 'Authorization: Bearer ACCESS_TOKEN' \
-d 'status=ほげ' \
-d 'visibility=unlisted'

レスポンスはなんか長いJSONが返ってくる。

{"id":"105916667149699590","created_at":"2021-03-19T13:21:22.245Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"unlisted","language":"hi","uri":"https://mstdn.jp/users/ohys/statuses/105916667149699590","url":"https://mstdn.jp/@ohys/105916667149699590","replies_count":0,"reblogs_count":0,"favourites_count":0,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eほげ\u003c/p\u003e","reblog":null,"application"............

投稿成功。

AndroidアプリでHTTP通信(POST)

こんにちは。前にcurlでhttpリクエストが出来たので、今ハマっているアンドロイドアプリでも出来ないかと思い、いろいろ調べて実装してみました。

内容は、マストドン(mstdn.jp - mstdn.jp)への定型文のトゥートです。

下準備

APIを叩くときに必要なアクセストークンをゲットします。

  1. マストドンのどこか(自分はmstdn.jpで試しましたが他でも同様と思います)でアカウントを取得する
  2. メニュー→開発→新規アプリと進む
  3. アプリ設定
    • アプリの名前を入れる
    • ウェブサイトは空白でいい。リダイレクトURLもデフォルトのurn:ietf:wg:oauth:2.0:oobでいい
    • アクセス権はwrite:statusesがあれば十分
    • 送信、を押すと新しいアプリが登録される
  4. クライアントキー、クライアントシークレット、アクセストークンが発行されます。とりあえずアクセストークンが必要なものです(使える期間についてはよくわからん)

ここをアプリ内で処理するのが理想ではあります

続きを読む

AlertDialogとToastだけのAndroidアプリ

最近Androidアプリ作りにハマっています。

家計簿アプリを鋭意建設中です

github.com

まあ、これについて語ろうとすると長くなるので、今回はお試しで作った「Dialogを出してToastを出すだけのアプリ」を紹介します

続きを読む

座標圧縮備忘録

座標圧縮、なるアルゴリズムに出会いました。どうにかわかったつもりになったので記録しておきます。自分用メモ的な精度。

座標圧縮とは

詳しくは参考のサイトを見てください。

簡単に言うと、「座標の広い範囲に散らばっている点について、その相対的な位置を変えずに狭い範囲に集める」という技です。

さらに自己流に解釈すると、「注目したい点をリスト化する」という感じだと思いました。

例えば、座標軸にいくつか($ N $個)の点を載せたいとします。点が乗っている時true、無い時falseとしましょう。$ 0 \le \rm{座標} < 10000 $ とします。

int N;cin>>N;
vector<bool> z(10000, false);
for(int i=0; i<N; i++){
  int a;cin>>a;
  z[a] = true;
}
......
//input
5
3 19 11 24 13

範囲が小さければこれで済みますが、$-10^{9} \le \rm{座標} \le 10^{9}$ とかになるとどうしようもありません。

//input
5
24680 515 -12345678 3 12345678

このとき、

value = {-12345678, 3, 515, 24680, 12345678};
index = {3, 2, 0, 1, 4};

とすれば $i$ 番目の入力はvalue[index[i]]とやって求められます。

1次元だとあんまりありがたみがないような気がしますが、2次元の領域を扱うととても嬉しくなれます。

メモリ上で実際に触る範囲が要素数オーダーに減る!

実装

とりあえず1つの配列について。

template<class T>
vector<T> compress(vector<T> &V){
  //Vを圧縮後の値にする 元の値を得るために重複削除ソート列であるSを返す
  vector<T> S = V;
  sort(S.begin(), S.end());
  S.erase( unique(S.begin(), S.end()), S.end() );
  for(int i=0; i<V.size(); i++){
    V[i] = lower_bound(S.begin(), S.end(), V[i]) - S.begin();
  }
  return S;
}
  • vector S = V;
    入力されたVはインデックスに変換してしまうので、もともとの値を(ソートして)保存しておく配列が必要

  • sort(S.begin(), S.end());
    バラバラに入力された数字をソートする

  • S.erase( unique(S.begin(), S.end()), S.end() );
    uniqueは、配列の中で重複するものの2つ目以降を配列の後ろに押しやり、その「余り物」の先頭のイテレータを返す
    eraseは、指定された範囲の要素を削除する
    結果として、重複している要素が削除される

  • V[i] = lower_bound(S.begin(), S.end(), V[i]) - S.begin();
    lower_boundで、「ソート&重複削除」された配列の中でV[i]を指すイテレータを返す
    それから先頭のイテレータを引き算することで、「何番目か」がわかる

使用例1

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

/**
 * 座標圧縮
 * */

namespace /* ZahyoComp.cpp */ {
  template<class T>
  vector<T> compress(vector<T> &V){
    //Vを圧縮後の値にする 元の値を得るために重複削除ソート列であるSを返す
    vector<T> S = V;
    sort(S.begin(), S.end());
    S.erase( unique(S.begin(), S.end()), S.end() );
    for(int i=0; i<V.size(); i++){
      V[i] = lower_bound(S.begin(), S.end(), V[i]) - S.begin();
    }
    return S;
  }
}

#define rep(i, n) for(int i=0; i<(int)n; i++)

int main(){
  int N;cin>>N;
  vector<int> input(N);
  rep(i,N)cin>>input[i];
  
  vector<int> S = compress(input);

  cout<<"compressed : RawData"<<endl;
  for(int i: input){
    cout<<i<<" : "<<S[i]<<endl;
  }
}
//input
7
100 120 120 130 115 115 150

//output
compressed : RawData
0 : 100
2 : 120
2 : 120
3 : 130
1 : 115
1 : 115
4 : 150

*/

[追記] もともと入力が入っていたinputが、「その入力が(重複削除ソートの)何番目か」を表す配列に化けていることに注意!その代わりS[input[i]]で元々の値を復元できている。

続きを読む

AtCoder M-SOLUTIONS プロコンオープン 2020 反省会 [A,B,C,D完][C++]

久しぶりの参戦。

A

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

int main(){
  int x;cin>>x;
  if(x<600)cout<<8<<endl;
  else if(x<800)cout<<7<<endl;
  else if(x<1000)cout<<6<<endl;
  else if(x<1200)cout<<5<<endl;
  else if(x<1400)cout<<4<<endl;
  else if(x<1600)cout<<3<<endl;
  else if(x<1800)cout<<2<<endl;
  else cout<<1<<endl;
}

そのまま。

B

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

int main(){
  int A,B,C,K;cin>>A>>B>>C>>K;

  while(K--){
    if(A>=B)B *= 2;
    else if(B>=C)C *= 2;
  }

  cout<<( (A<B && B<C) ? "Yes" : "No")<<endl;
}

目標は $A < B < C$ なので、そうなるように $K$ 回の間倍々にしていく。

最後の最後で、$A < B < C$ が完成していたらOK!!

C

#include <bits/stdc++.h>
using namespace std;
using vi = vector<int>;

int main(){
  int N,K;cin>>N>>K;
  vi A(N);
  rep(i,N)cin>>A[i];

  for(int i=K; i<N; i++){
    cout<<(A[i-K]<A[i] ? "Yes" : "No")<<endl;
  }
}

評点比較は例1を使うと

$$ \{96, 98, 95, 100, 20\} $$

については

$96\times98\times95$ と $98\times95\times100$ の比較 → $96$ と $100$ の比較

$98\times95\times100$ と $95\times100\times20$ の比較 → $98$ と $20$ の比較

という風に単純化できる。あとはfor文のiとか添字に気をつけて順次判定していく。

D

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using vi = vector<int>;
#define rep(i, n) for(int i=0; i<(int)n; i++)

int main(){
  int N;cin>>N;
  vi A(N);
  rep(i,N)cin>>A[i];
  A.push_back(0);

  ll money = 1000;

  vi list;
  for(int i=0; i<N; i++){
    list.push_back(A[i]); //今日の株価を区域に追加

    if(A[i] <= A[i+1]){
      //明日上がる
      ; //なにもしない
    }else{
      //明日下がる
      ll buy = money / list.front();
      money -= buy * list.front();
      money += buy * list.back();
      list.clear();
    }
  }

  cout<<money<<endl;
}

中心の考え方は、株価が上昇(等価含む)していく区域を見つけて、その初日で最大限買って最終日で最大限売る。これでマックスの利益が出る。

区域をlistで作ってゆき、翌日も株価が上がるならなにもしない。翌日に株価が下がるならその日が「区域」の終わりなので売買を(過去に遡って)やる。そして区域のリセットをする。

入力例1

$$ A = \{ 100, 130, 130, 130, 115, 115, 150 \} $$

区域 $\{100, 130, 130, 130 \}$ まで見て、100円で買い130円で売る処理をする

区域 $\{ 115, 115, 150 \}$ を見て、115円で買い150円で売る処理をする


なお、入力例1のように上昇したまま最終日を迎えると、「売買しなきゃいけないのに『翌日』がない」となり売買し損ねるため、「最終日」の後ろに株価が0円の日を付け加えておく。こうすると、

$$ \cdots 200, 100, 150, 0 $$

のときは最終日にlist = {100, 150}、「明日下がる」条件なので100円で買って150円で売る。

$$ \cdots 200, 150, 100, 0 $$

のときは最終日にlist = {100}、「明日下がる」条件なので100円で買って100円で売る(実質何もしない)

ちなみに、株価が下がり続けているときは、上記のようにlistに1つだけ(その日の)株価が入るので「実質何もしない」状態になる。


最初、int型でやってたらWAが出たんでlong longにしたらACなった。80日間あって $100 \le 株価 \le 200$ なので、株価が $100, 200, 100, 200, \cdots$ となる所持金が2倍になる$\times$40回 で $2^{40} \times 1000 = 1099511627776 \times 1000 \approx 10^{15}$ まで膨れ上がるんですね。

まとめ

久々だったけどDまで通ったのでまあよし。

プライバシーポリシー ・お問い合わせはこちら