Nuxt.jsでMaximum call stack size exceededというエラーに悩まされた
最近、nuxt.jsを使って個人サービス開発に励んでおります。
がしかし、
Javascriptをほとんど触ってこなかった人なのでこのエラーには大変苦戦しました。
その解決に至った経路だとか方法を備忘録として書き留めます。
どんな風にエラーが起きたか
エラーが生じたときの状態は
ヘッダーにあるv-list-item
から@click
でアクションを実行しログアウト。
トップページ的なところに遷移するはずがエラー画面に。
developer toolをみると、こんなエラーが
RangeError: Maximum call stack size exceeded at VueComponent.logout (header.vue?24d4:71) at VueComponent.logout (header.vue?24d4:72) at VueComponent.logout (header.vue?24d4:72) at VueComponent.logout (header.vue?24d4:72) at VueComponent.logout (header.vue?24d4:72) at VueComponent.logout (header.vue?24d4:72) at VueComponent.logout (header.vue?24d4:72) at VueComponent.logout (header.vue?24d4:72) at VueComponent.logout (header.vue?24d4:72) at VueComponent.logout (header.vue?24d4:72)
エラーからして、logoutが何回も実行されてるようにみえる。。
つまり何らかの理由で
無限ループしていてスタックサイズ超えちゃったよ、あーあ、
ってわけですね。
エラーが起きたソースコード
ソースコードの説明は省略してます。
index.js
// storeを定義してるモジュールを読み込むファイル import Vuex from 'vuex' import user from './modules/user' import posts from './modules/posts' export default () => new Vuex.Store({ modules: { user, posts } })
user.js
// userに関するstoreを定義してるモジュール const state = { user: null, loginStatus: true // ログイン状態を擬似的に表現してたので最初からtrue } const getters = { user: (state) => state.user, isLogin: (state) => state.loginStatus } const mutations = { setUser(state, { user }) { state.user = user }, login(state) { state.loginStatus = true }, logout(state) { state.loginStatus = false state.user = null } } const actions = { fetchUser({ commit }, user) { commit('setUser', {user}) }, login({ commit }) { commit('login') }, logout({ commit }) { commit('logout') } } export default { namespaced: true, state, getters, actions, mutations }
Header.vue
<!-- Header component 全くAtomic Design意識してないから参考にしないでください。 --> <template> <div> <v-app-bar color="primary" > <v-toolbar-title> <nuxt-link to='/'> <img src="~/static/weblogo.png"> </nuxt-link> </v-toolbar-title> <v-spacer></v-spacer> <v-menu offset-y> <template v-slot:activator="{ on }"> <v-btn icon color="transparent" v-on="on" > <v-app-bar-nav-icon></v-app-bar-nav-icon> </v-btn> </template> <v-list v-if="loginStatus"> <v-list-item nuxt to='#' > <v-list-item-title>マイページ</v-list-item-title> </v-list-item> <v-list-item nuxt to='/' @click="logout" inactive > <v-list-item-title>ログアウト</v-list-item-title> </v-list-item> </v-list> <v-list v-else> <v-list-item nuxt to='/login' > <v-list-item-title>ログイン</v-list-item-title> </v-list-item> <v-list-item nuxt to='/signup' > <v-list-item-title>会員登録</v-list-item-title> </v-list-item> </v-list> </v-menu> </v-app-bar> </div> </template> <script> import { mapState, mapGetters, mapActions } from "vuex"; export default { name: "Header", computed: { ...mapState({ user: state => state.user.user, loginStatus: state => state.user.loginStatus }) }, methods: { ...mapActions('user',[ "logout" ]), logout: function() { this.logout() } } } </script>
バックエンドばかり普段触ってる身からすると、
無限ループになりそうなところはないので非常に原因が何か分かりづらかったです。
(解決後には「確かに無限ループするじゃん!」ってなりましたw)
解決策
で、色々ググってたらこのサイトのおかげで解決ができました。
あ、確かに〜、と腑に落ちた原因でした。
何がいけなかったのかというと、
methods: { ...mapActions('user',[ "logout" // user storeで定義されてるmutation実行のためのアクション ]), logout: function() { //コンポーネントからログアウトを発火させるためのメソッド this.logout() // user storeで定義されてるmutation実行のためのアクション }
このような意図でコンポーネント内でStoreのアクションを実行するメソッドを定義していましたが、
実際this.logout()
はコンポーネント内のlogoutメソッドと解釈されてしまうようでした。
要するにlogout()が自分自身を呼び続けていたために起きてしまったエラーでした。
そりゃ無限ループになりますよね。
実際のところ、
...mapActions('user',[ "logout" ])
この宣言だけで、
コンポーネント内でStoreで定義してるアクションは使用できるようになってるみたいなので、
Header.vueのJavascriptの部分を下記のように直すことで解決でした。
<script> import { mapState, mapGetters, mapActions } from "vuex"; export default { name: "Header", computed: { ...mapState({ user: state => state.user.user, loginStatus: state => state.user.loginStatus }) }, methods: { ...mapActions('user',[ "logout" ]) } } </script>
ヤマレコAPIを用いて百名山の情報を集めてみた
表題の通り百名山の情報をヤマレコAPIを用いて集めてみました。
現在個人で使用することを目的とした百名山アプリを開発しています。 そこで百名山の情報をどこかから集めたいと思ってたところヤマレコAPIを見つけました。
今回はヤマレコAPIを用いた百名山の情報を取得する方法を備忘録として残しておきます。
この記事を読む前に
APIのレスポンス確認にターミナル(mac標準TerminalやiTerm2などのこと)とjqコマンドを使用します。
jqコマンドはjson形式のデータをパースして整形かつシンタックスハイライトしてくれる便利コマンドです。またkeyを指定して必要なvalueだけに絞り込めたりと便利なので、まだの方はぜひインストールしてください。
前提
- macOS Mojave
- jqコマンドでレスポンス確認できる
まだjqコマンドをインストールしてない方へ
jqコマンドはbrewでインストールできます。
brew install jq
brewをインストールしてない方はググってインストールしましょう笑
jqコマンドの使い方については、またAPIレスポンスを確認するときに説明します。
ヤマレコAPIについて
ヤマレコAPIとはヤマレコが提供しているAPIで、ヤマレコのデータにアクセスしたり、ヤマレコの機能をAPI経由で使用するモバイルアプリ開発をおこなうことができます。
ちなみにヤマレコは登山ルートを作成したり共有したりと登山愛好家が好むサービスの1つです。
ヤマレコAPIのマニュアルはこちらです。
ヤマレコAPIをつかってみる
さて、本題に入ります。
上記マニュアルの中で百名山の情報を取得できそうなAPIは地名データの検索(searchPoi)です。
searchPoiの仕様(各パラメータに指定する値はhttps://sites.google.com/site/apiforyamareco/api/api_otherを参照ください。)
パラメータ名 | 説明 |
---|---|
page | 一覧のページ番号。1ページあたり20件の記録が取得できる。 |
name | 検索したい地名の名称。 |
type_id | 検索したい地名のデータ種別 |
area_id | 検索したい地名のエリアID |
ptid | 表示したい地名のID |
各パラメータの説明がありますが、
これだけでは各パラメータがどんなことを意味してるのかわからないので他のAPIを使用してみてデータを確かめることにします。
下記からは百名山の情報を取得するために試行錯誤したことを参考程度に記載するので、結果の知りたい方はヤマレコAPIで百名山の情報を絞り込む方法を参照ください。
area_idってなんだろ
パラメータでarea_id
を指定できるが、
具体的にどんな値を使用できるかわからないのでエリアリストの取得(getArealist)で確認してみます。
ターミナルでcurlコマンドでgetArealist
を叩きます。
getArealistの結果1 (クリックで折りたたみを展開)
$ curl https://api.yamareco.com/api/v1/getArealist {"arealist":[{"area_id":"100","area":"\u5317\u6d77\u9053"},{"area_id":"101","area":"\u9053\u6771\u30fb\u77e5\u5e8a"},{"area_id":"102","area":"\u5927\u96ea\u5c71"},{"area_id":"103","area":"\u5341\u52dd\u9023\u5cf0"},{"area_id":"104","area":"\u82a6\u5225\u30fb\u5915\u5f35"},{"area_id":"105","area":"\u65e5\u9ad8\u5c71\u8108"},{"area_id":"106","area":"\u7a4d\u4e39\u30fb\u30cb\u30bb\u30b3\u30fb\u7f8a\u8e44\u5c71"}, . . .
はい、
なんかUnicodeになっていて読めないし、整形されてないのでみにくいです。
そこでjqコマンドを使用します。
getArealistの結果2 (クリックで折りたたみを展開)
$ curl https://api.yamareco.com/api/v1/getArealist | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 4800 100 4800 0 0 10983 0 --:--:-- --:--:-- --:--:-- 10983 { "arealist": [ { "area_id": "100", "area": "北海道" }, { "area_id": "101", "area": "道東・知床" }, { "area_id": "102", "area": "大雪山" }, { "area_id": "103", "area": "十勝連峰" }, . . .
jqコマンドにより、unicode文字も読めるようになり、かつjsonも整形されましたね。getArelist
のレスポンスをみるとarea_id
は山のある地域名・地帯名?に対応してるidみたいです。
今回の目的は百名山を絞り出したいのでarea_id
は関係なさそうです。
type_idってなんだろ
次に百名山を絞りこめそうなパラメータとして気になるのが、type_id
です。type_id
についても詳しい記述がないので、値を指定してレスポンスをみることにより何を意味するのか確認することとします。
searchPoiの結果 (クリックで折りたたみを展開)
$ curl -X POST https://api.yamareco.com/api/v1/searchPoi -d "page=1&type_id=1" | jq . # 参考になるレスポンスだけ抜粋 { "ptid": "17", "name": "月山", "yomi": "がっさん", "name_en": "Mt. Gassan", "elevation": "1984", "lat": "38.549028", "lon": "140.026917", "detail": "", "area": "206", "types": [ { "type_id": "1", "name": "山頂", "detail": "" }, { "type_id": "6", "name": "山小屋", "detail": "" }, { "type_id": "14", "name": "トイレ", "detail": "" }, { "type_id": "7", "name": "水場", "detail": "" }, { "type_id": "8", "name": "食事", "detail": "" }, { "type_id": "11", "name": "展望ポイント", "detail": "" } ], "photo_url": "https://yamareco.info/modules/yamainfo/upimg/pt/c6ac7ecb1c91c93dfea4460d5d6f2d8b.JPG", "page_url": "http://www.yamareco.com/modules/yamainfo/ptinfo.php?ptid=17" },
レスポンスの内容からしてtype_id
は山にある設備とかポイントなどを意味してそうなので、これも特に百名山を絞り込むには関係なさそうですね。
ヤマレコAPIで百名山の情報を絞り込む方法
上記の試行錯誤の結果、searchPoi
じゃ百名山の情報絞り込めないのかと諦めていましたが、ヤマレコの百名山一覧ページにヒントが隠れていました。
下記リンクはヤマレコで百名山で絞り込んだヤマリストのページとなっております。
このページが作れてるんなら何か方法はあるはず、
パラメータgroupidの検証
urlをみると、groupid=1
で絞り込みをおこなっています。
上記ページのurl https://www.yamareco.com/modules/yamainfo/ptlist.php?groupid=1
APIリファレンスにはgroupidが指定できるとは書いてなかったですが試しに叩いてみます。
groupidを追加したsearchPoiの結果 (クリックで折りたたみを展開)
$ curl -X POST https://api.yamareco.com/api/v1/searchPoi -d "page=1&type_id=0&groupid=1" | jq . % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 20104 0 20078 100 26 45425 58 --:--:-- --:--:-- --:--:-- 45381 { "err": 0, "poilist": [ { "ptid": "1", "name": "利尻山", "yomi": "りしりざん", "name_en": "Mt. Rishirizan", "elevation": "1721", "lat": "45.178583", "lon": "141.242028", "detail": "", "area": "109", "types": [ { "type_id": "1", "name": "山頂", "detail": "" }, { "type_id": "14", "name": "トイレ", "detail": "携帯トイレブース有り ※携帯トイレ必須" } ], "photo_url": "https://yamareco.org/modules/yamainfo/upimg/pt/c0a89f6b8d8dd5faaa137b9a52a437e1.JPG", "page_url": "http://www.yamareco.com/modules/yamainfo/ptinfo.php?ptid=1" }, { "ptid": "2", "name": "羅臼岳", "yomi": "らうすだけ", "name_en": "Mt. Rausudake", "elevation": "1661", "lat": "44.075778", "lon": "145.122333", "detail": "", "area": "101", "types": [ { "type_id": "1", "name": "山頂", "detail": "" }, { "type_id": "11", "name": "展望ポイント", "detail": "" } ], "photo_url": "https://yamareco.org/modules/yamainfo/upimg/pt/e6034fb1754e32a5351d63827166dea7.JPG", "page_url": "http://www.yamareco.com/modules/yamainfo/ptinfo.php?ptid=2" }, . . . { "ptid": "20", "name": "西吾妻山", "yomi": "にしあづまやま", "name_en": "Mt. Nishiadumayama", "elevation": "2035", "lat": "37.738278", "lon": "140.140833", "detail": "日本百名山・吾妻山(吾妻連峰)の最高峰。\r\n山頂は樹林に囲まれ展望は無い。", "area": "208", "types": [ { "type_id": "1", "name": "山頂", "detail": "" } ], "photo_url": "https://yamareco.org/modules/yamainfo/upimg/pt/d21657cd97bcf40432bcd8df1ee98220.JPG", "page_url": "http://www.yamareco.com/modules/yamainfo/ptinfo.php?ptid=20" } ] }
まず与えたパラメータの意味を述べておくと、
- page=1: 20個表示
- type_id=0: typeを検索条件に入れない
- groupid: 百名山?←検証対象
次にレスポンスからgroupid=1
が百名山を指すか検証です。
レスポンスとして返ってきた20個の山データのうち、1個目(利尻山)と20個目(西吾妻山)が百名山のデータであるかhttps://www.yamareco.com/modules/yamainfo/ptlist.php?groupid=1で確認します。
あってますね。 それではjqコマンドで20個の山データを名前一覧に加工して比較してみましょう。
{ "err": 0, "poilist": [ { { "ptid": "1", "name": "利尻山", "yomi": "りしりざん", "name_en": "Mt. Rishirizan", "elevation": "1721", "lat": "45.178583", "lon": "141.242028", "detail": "", "area": "109", "types": [ { "type_id": "1", "name": "山頂", "detail": "" }, { "type_id": "14", "name": "トイレ", "detail": "携帯トイレブース有り ※携帯トイレ必須" } ], "photo_url": "https://yamareco.org/modules/yamainfo/upimg/pt/c0a89f6b8d8dd5faaa137b9a52a437e1.JPG", "page_url": "http://www.yamareco.com/modules/yamainfo/ptinfo.php?ptid=1" }, . . . ] }となってます。今回は山データにアクセスしたいのでkeyはpoilistとなります。 pilistのvalueは配列になっており、その要素もkey-value形式になっています。 なので例えば利尻山というvalueだけに絞り込みたい場合は、
jq '.poilist[0].name'のようにアクセスします。.poilist[0]は配列poilistの先頭要素を表し、それもkey-value形式になっているので、さらに.nameでkeyを指定してvalueを取り出す感じです。
仕切り直して、jqコマンドで20個の山データを名前一覧に加工して比較してみます。
groupidを追加したsearchPoiの結果 (クリックで折りたたみを展開)
$ curl -X POST https://api.yamareco.com/api/v1/searchPoi -d "page=1&type_id=0&groupid=1" | jq '.poilist[].name' % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 20105 0 20079 100 26 59581 77 --:--:-- --:--:-- --:--:-- 59658 "利尻山" "羅臼岳" "斜里岳" "雌阿寒岳" "羊蹄山" "大雪山" "トムラウシ山" "十勝岳" "幌尻岳" "岩木山" "八甲田大岳" "八幡平" "岩手山" "早池峰山" "蔵王山" "鳥海山" "月山" "大朝日岳" "飯豊山" "西吾妻山"
うん、あってますね。
100個のデータがあってるか検証
百名山ということなので、山は100個あります。ですので、groupid=1
かつpage
が1~5のデータが百名山になりますので。
一旦名前だけ取得してみましょう。
百名山の名前だけ取得する (クリックで折りたたみを展開)
$for i in `seq 1 5` do curl -X POST https://api.yamareco.com/api/v1/searchPoi -d "page=$i&type_id=0&groupid=1" | jq '.poilist[].name' done % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 20124 0 20098 100 26 57259 74 --:--:-- --:--:-- --:--:-- 57170 "利尻山" "羅臼岳" "斜里岳" "雌阿寒岳" "羊蹄山" "大雪山" "トムラウシ山" "十勝岳" "幌尻岳" "岩木山" "八甲田大岳" "八幡平" "岩手山" "早池峰山" "蔵王山" "鳥海山" "月山" "大朝日岳" "飯豊山" "西吾妻山" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 19644 0 19618 100 26 56863 75 --:--:-- --:--:-- --:--:-- 56939 "安達太良山" "磐梯山" "那須岳" "男体山" "奥白根山" "会津駒ヶ岳" "越後駒ヶ岳" "平ヶ岳" "巻機山" "燧ヶ岳" "至仏山" "谷川岳" "武尊山" "苗場山" "筑波山" "皇海山" "赤城山" "両神山" "雲取山" "丹沢山" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 22093 0 22067 100 26 55305 65 --:--:-- --:--:-- --:--:-- 55232 "甲武信ヶ岳" "金峰山" "瑞牆山" "大菩薩嶺" "雨飾山" "妙高山" "火打山" "高妻山" "草津白根山" "四阿山" "浅間山" "白馬岳" "五竜岳" "鹿島槍ヶ岳" "剱岳" "立山" "薬師岳" "黒部五郎岳" "水晶岳" "鷲羽岳" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 25895 0 25869 100 26 41127 41 --:--:-- --:--:-- --:--:-- 41168 "白山" "荒島岳" "富士山" "美ヶ原" "霧ヶ峰" "蓼科山" "赤岳" "槍ヶ岳" "穂高岳" "常念岳" "笠ヶ岳" "焼岳" "乗鞍岳" "御嶽山" "木曽駒ヶ岳" "空木岳" "恵那山" "甲斐駒ヶ岳" "仙丈ヶ岳" "鳳凰山" % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 17251 0 17225 100 26 40339 60 --:--:-- --:--:-- --:--:-- 40306 "北岳" "間ノ岳" "天城山" "塩見岳" "悪沢岳" "赤石岳" "聖岳" "光岳" "伊吹山" "大峰山" "大台ヶ原山" "剣ヶ峰" "剣山" "石鎚山" "阿蘇山" "九重山" "祖母山" "霧島山" "開聞岳" "宮之浦岳"
見事、百名山に絞り込めましたね!
まとめ
まとめると下記のようになります。
値 | |
---|---|
method | POST |
url | https://api.yamareco.com/api/v1/searchPoi |
リクエストパラメータ名 | 値 |
---|---|
page | 1~5 |
type_id | 0 |
groupid | 1 |
レスポンスパラメータは上記で紹介したAPIマニュアルを参照ください。
上記のリクエストパラメータとjqコマンドを組み合わせて百名山の欲しい情報のみを抽出しましょう!
補足ですが、groupid=2を指定すると二百名山のデータが得られます。なのでgroupidを使うことで他にもたくさんの情報にアクセスできますね。
Good MounTech Lifeをお過ごしくださいー!
ここまで読んでいただきありがとうございますー!
Flutterの勉強どうはじめればいいか、モバイル開発未経験のwebエンジニアが紹介してみた
表題の通り、ここ一週間くらいFlutterを触り始めました。 Webエンジニアということもあり、モバイルネイティブなアプリ開発をこれまでしたことがなく、 環境構築、どうやってアプリ実行して動作確認するの?とかわからないことだらけでした。
Flutterはここ最近だと新しい方の技術なはずなので、これから始めてみたいなって方がいるはずです。 せっかくなのでそんな方の役に立てればと思い、どうやってFlutterを始めたか紹介します。
この記事を読む前に
まずはじめに、ざっくりと自分の前提知識とこの記事で述べることを書いておきます。
私の前提知識
- バックエンドエンジニアとして働いている
- フロントエンドの技術(Javascript等)には疎い
- これまで今までスマホ向けアプリの開発をしたことがない
この記事で述べること
この記事では一旦何で勉強したかまでにとどめておきます。 次回からの記事にてFlutterの整理的なことをしていきますのでよろしくお願いします。
- Flutterとは
- Webエンジニアの自分がどのようにFlutterを勉強しはじめたか(手をつけだしたか)
Flutterとは
そもそもFlutterって何?と思っている人向けに書いておきます。
Flutterとは、googleが開発したクロスプラットフォーム向け(iOSもAndroidもWebもいけますよ)のモバイルアプリケーションフレームワークです。正確にはGoogleはFlutterのことをこう呼んでます。
Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.
なのでフレームワークという言葉が難しいかもしれませんが、簡単に言うとGoogleが美しいモバイルアプリを作るためにUI(ユーザインタフェース)をたくさん用意してくれてると解釈していただければいいかと思います。
公式のリンクを貼っておきますので詳細はそちらからご覧ください。
まだリリースされたのが2017年と比較的新しいオープンソースのフレームワークとなっております。 開発で使用する言語はDart言語というもので、こちらもGoogleが開発したものとなってます。
Javaを触ったことある人は、Javaに似てるって感覚を抱く言語だと思いました。
どのように勉強したか?
Webエンジニアということもあり、モバイルアプリの開発については一度もしたことなかったので環境構築からおこないました。
ここでは環境構築から実際にFlutter開発まで何を教材にして勉強したのかを紹介します。
環境構築について
環境構築については公式がおすすめです。
(英語だけどコマンド打ったりするだけなので是非チャレンジしてみるといいです)
インストールの手順はWindows, macOS, Linuxでわかれており、 それぞれの環境で下記モバイルアプリが対象となっております。
英語わからなくて何やったらいいかわからないよってなったら、とりあえずflutterをインストールしたあと
flutter doctor
を実行し、
~/m/cupertino_app ❯❯❯ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel beta, v1.12.13+hotfix.6, on Mac OS X 10.14.5 18F132, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2) [✓] Xcode - develop for iOS and macOS (Xcode 11.3) [✓] Chrome - develop for the web [✓] Android Studio (version 3.5) [✓] VS Code (version 1.40.2) [✓] Connected device (3 available) • No issues found! ~/m/cupertino_app ❯❯❯
こんな感じに出力されるリストを順に対応していけば大丈夫です。これはもう全て対応済みの画面になってますが、 例えばandroidの準備まだだよって場合は、
[-] Android toolchain - develop for Android devices • Android SDK at /Users/obiwan/Library/Android/sdk ✗ Android SDK is missing command line tools; download from https://goo.gl/XxQghQ • Try re-installing or updating your Android SDK, visit https://flutter.dev/setup/#android-setup for detailed instructions.
このように表示されるはずです。表示されるうち、Try re-installing ...
を順番にやっていけば終わるはずです。この例だとhttps://flutter.dev/setup/#android-setup for detailed instructionsにvisitしろーーって言ってますね。
エディタの設定は、Android Studio, IntelliJそしてVisual Studio Codeの手順が載っております。 私自身はIDEにあまりなれていないのでVS codeの環境設定をおこないました。
Flutter公式ドキュメントのGet Started (上のリンク)は5つの構成になってますが、1のInstall、2のSet up an editorをやれば環境構築はおわりです。あとは動作確認のようなものです。
何を教材にした?個人的おすすめのFlutterチュートリアル
公式ドキュメントのチュートリアル的なのをやろうとしましたがよくわからなかったので、何かな行かなと探していたら、 「codelabs」というものを偶然見つけました。
codelabs.developers.google.com
これもまたGoogleさんが無料で提供しているもので、文章に従ってHands-onでテーマを勉強できるというなかなかいいものです。Flutter以外にも読者さんがきになるTopicがあれば是非検索してみてください。
ただこちらも英語です。もう新しい技術勉強しようと思ったら英語っすね。頑張りましょう。笑
本題ですが、このうちの「Write Your First Flutter App, part 1」と「Write Your First Flutter App, part 2 」をやってみました。
この二つをやることで、
- dartの書き方
- リストの作りたい
- 画面遷移先に変数を渡したいとか
- お気に入りボタン設置してお気に入りしたものだけのリストを表示したい
などなど一般的なアプリでも応用できそうな基本を勉強しつつflutterの学習をすることができます。
まだ2つしかやってませんが下の写真の通り
- Google mapをflutterでつかう
- 写真シェアアプリを作る
- chatアプリ的なUIを作る(←Building Beautiful UIs with Flutter)
- Apple storeアプリみたいなiOSっぽいデザインのECアプリの作成
などのチュートリアルが揃ってます。
まとめ
まだ簡単なチュートリアル2つしか終えたばかりで、Flutterについての整理ができてないので、 どう勉強始めたらいいかの簡単な紹介になってしましました。
またFlutterのDartの書き方だったり提供されてるWidgetの種類だったりとか整理するのに記事書くかもです。
ここまで読んでくれて本当にありがとうございましたー!
スコープの認識を高めて安全なコードを書こう
こんにちは、
突然ですが、あなたは次のプログラムの最後のecho文で何が出力されるかわかりますか?
<?php $fruits = array( 'apple', 'banana', 'lemon' ); foreach($fruits as $fruit){ echo $fruit, PHP_EOL; } echo $fruit, PHP_EOL; //ここ! ?>
実行結果は、
apple banana lemon lemon //これ!
最後のecho文によってlemon
が出力されます。
僕はこの事実にびっくりしてしまいました。
この事実は知っていて損はないので説明していきます。
知った経緯
phpをつかうことになりそうなので、
体系的かつ実践的なものが学べると噂のパーフェクトPHPで勉強しています。
勉強開始いきなり、スコープの話がでてきたのですが衝撃を受けてしまいました。
スコープとは
スコープとは、一言で言うと、
のことです。
なぜ衝撃をうけたか
最初に例をあげたプログラムでは、
最後のecho文で変数$fruit
を参照しています。
僕の勘違いはこの変数のスコープでした。
$fruit
のスコープはforeach文の中だけだという認識でいました。
これが実は違うのです。
foreach文で宣言された$fruit
はグローバルスコープなのです。
グローバルスコープと言ってしまうと誤解を招きそうなので補足しておきます。
関数定義などのローカルスコープでforeach文で定義した場合は、
foreach文内だけでなく、関数内であれば参照できます。
関数内のforeach文で定義された変数はグローバルスコープではないでしょう。
僕が言いたいのは、
ってことです。
まとめ
今回はパーフェクトPHPを読んでいて衝撃を受けたことをアウトプットしました。
てっきり変数が自分の思ってたスコープ内だけだと思っていると、思いも寄らないエラーを引き起こしそうですね。
みなさんもスコープの認識を高めて安全なコードを書きましょう!
Rubyで天気予報のプログラムを作成
こんにちは。
今回はRubyで天気予報のプログラムを作成してみました。
この記事を読む前に
もし読者の方が、次の節で述べている背景に合致しているのであれば、ネタバレになってしまいます。
ですので、もし自力で挑戦したい場合は、プログラム全体をさっと飛ばして、アルゴリズムを参考にするなどしてください。
背景 なぜ天気予報?
僕がTwitterでフォローしているゲスエンジニアとださんがこのようなツイートをしていました。
noteでプログラミングの問題出してみましたー!
— とだこうき@整形済のCTO (@cohki0305) February 24, 2019
解答と解説を有料にしているのは、答えを見ずに頑張ったほうが勉強になると思うからですーどうしてもうまく出来ない人は見てください
プログラミング(Ruby)の問題出してみた - 天気予報|note(ノート) https://t.co/52m8zf85vO
noteは問題とヒントのみ無料公開されています。
ちょうど最近チェリー本(プロを目指す人のためのRuby入門)を読んだばかりなので、 腕試しに挑戦してみました。
実現したい事
天気予報なので、
〇〇日、どこどこは晴れでしょう。
のように直近の日にち(今日、明日、明後日)の任意の場所の天気を知る事ができたら嬉しいです。
なので実装したいポイントとしては、
- 任意の地名の天気がわかる。
- 今日、明日、明後日の天気がわかる。
作成プログラム
作成したプログラムを次に示します。
require 'uri' require 'open-uri' require 'net/http' require 'json' require 'kconv' require "rexml/document" require 'active_support' require 'active_support/core_ext' # key:地名, value:city idのハッシュを作成 # http://kzfm-s.hateblo.jp/entry/2015/06/07/014735の記事を参考 xml_url = open("http://weather.livedoor.com/forecast/rss/primary_area.xml").read.toutf8 xml_doc = REXML::Document.new(xml_url) xml_hash = Hash.from_xml(xml_doc.to_s) city_id_hash = {} xml_hash["rss"]["channel"]["source"]["pref"].each do |key| count = 0 #ハッシュ作成 while key["city"][count] != nil do city_id_hash[key["city"][count]["title"]] = key["city"][count]["id"] count += 1 end end #任意の地名をコマンドライン入力より受け取る while true p "日本のどこの天気が知りたいですか?" city_name = gets.chomp! #getsで含まれた\nは削除して扱う break if city_id_hash.has_key?(city_name) #実際にハッシュに登録されてたらループを抜ける puts "正しい地名を述べてください" end city_id = city_id_hash[city_name] p "いつの天気が知りたいですか?今日なら0、明日なら1、明後日なら2を入力してください" day_number = gets.to_i #取得したcity idで見たい天気の情報を取得する url = "http://weather.livedoor.com/forecast/webservice/json/v1?city=#{city_id}" uri = URI.parse(url) res = Net::HTTP.get(uri) res_json = JSON.parse(res) # res_jsonから必要な情報 # 日にち # 場所 # 天気 を抜き出す day = res_json["forecasts"][day_number]["date"].split("-")[-1] location = res_json["location"]["city"] telop = res_json["forecasts"][day_number]["telop"] puts "#{day}日、#{location}は#{telop}でしょう"
実行結果例 岐阜の明日の天気
※今日が2/28であることを仮定
"日本のどこの天気が知りたいですか?" 岐阜 ←コマンドライン入力 "いつの天気が知りたいですか?今日なら0、明日なら1、明後日なら2を入力してください" 1 ←コマンドライン入力 01日、岐阜は晴れでしょう
実行結果例 東京の明日の天気
"日本のどこの天気が知りたいですか?" 東京 "いつの天気が知りたいですか?今日なら0、明日なら1、明後日なら2を入力してください" 1 01日、東京は曇のち晴でしょう
上二つの実行例は http://weather.livedoor.com/ で正しい結果であると確認できます。
プログラムの解説
今回の天気予報プログラムのアルゴリズムは
ステップ1, 2について
該当プログラムは次の部分です。
# key:地名, value:city idのハッシュを作成 # http://kzfm-s.hateblo.jp/entry/2015/06/07/014735の記事を参考 xml_url = open("http://weather.livedoor.com/forecast/rss/primary_area.xml").read.toutf8 xml_doc = REXML::Document.new(xml_url) xml_hash = Hash.from_xml(xml_doc.to_s) city_id_hash = {} xml_hash["rss"]["channel"]["source"]["pref"].each do |key| count = 0 #ハッシュ作成 while key["city"][count] != nil do city_id_hash[key["city"][count]["title"]] = key["city"][count]["id"] count += 1 end end
考える際にはこちらの記事を参考にさせていただきました。ありがとうございました。
この部分の解説については、Qiitaに投稿していますのでそちらをご参照ください。
ステップ3, 4について
該当プログラムは次の部分です。
#任意の地名をコマンドライン入力より受け取る while true p "日本のどこの天気が知りたいですか?" city_name = gets.chomp! #getsで含まれた\nは削除して扱う break if city_id_hash.has_key?(city_name) #実際にハッシュに登録されてたらループを抜ける puts "正しい地名を述べてください" end city_id = city_id_hash[city_name] p "いつの天気が知りたいですか?今日なら0、明日なら1、明後日なら2を入力してください" day_number = gets.to_i
while文はエラー回避のために用いています。
例えば、岐阜の天気が知りたいから、岐阜県と入力したとします。 しかしながら岐阜県の天気のキーは”岐阜”と”高山”のみ存在しており、 ”岐阜県”というキーはハッシュには存在してません。
city_name="岐阜県"
のまま実行を継続するとエラーとなってしまいます。
そのため任意の地名がハッシュのキーとして存在する場合のみ、ループを抜けれるようにすることでエラーを回避しています。
次に、いつの天気を知りたいか入力してもらうとき、 今日なら0を、明日なら1を、明後日なら2を入力させてますが、この理由については次に説明します。
ステップ4, 5について
該当プログラムは次の部分です。
#取得したcity idで見たい天気の情報を取得する url = "http://weather.livedoor.com/forecast/webservice/json/v1?city=#{city_id}" uri = URI.parse(url) res = Net::HTTP.get(uri) res_json = JSON.parse(res) # res_jsonから必要な情報 # 日にち # 場所 # 天気 を抜き出す day = res_json["forecasts"][day_number]["date"].split("-")[-1] location = res_json["location"]["city"] telop = res_json["forecasts"][day_number]["telop"] puts "#{day}日、#{location}は#{telop}でしょう"
まずステップ5についてですが、APIの使い方をnoteのヒントを参考にしたので説明は必要ないと思います。
式展開を使うことで、任意のcity idを用いて天気の情報を取得できます。 (これがしたいがために、ステップ1, 2でめんどくさいことをしました笑)
最後に天気予報をputs
で出力するのですが、必要な情報をres_jsonから抜き出します。
必要な情報を抜き出すためにはres_jsonがどのようなデータ構造になっているか調べる必要があります。
ということで、岐阜(city_id=210010)を参照した場合のres_jsonのデータ構造を示します。 データ構造は次のようなハッシュになっていました。わかりやすいよう出力フォーマットを変更しています。
※2月26日のデータになってます。日付が変わっても同様に動作します。
# キー、 バリュー pinpointLocations, [{"link"=>"http://weather.livedoor.com/area/forecast/2120100", "name"=>"岐阜市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2120200", "name"=>"大垣市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2120400", "name"=>"多治見市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2120500", "name"=>"関市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2120600", "name"=>"中津川市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2120700", "name"=>"美濃市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2120800", "name"=>"瑞浪市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2120900", "name"=>"羽島市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121000", "name"=>"恵那市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121100", "name"=>"美濃加茂市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121200", "name"=>"土岐市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121300", "name"=>"各務原市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121400", "name"=>"可児市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121500", "name"=>"山県市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121600", "name"=>"瑞穂市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121800", "name"=>"本巣市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2121900", "name"=>"郡上市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2122100", "name"=>"海津市"}, {"link"=>"http://weather.livedoor.com/area/forecast/2130200", "name"=>"岐南町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2130300", "name"=>"笠松町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2134100", "name"=>"養老町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2136100", "name"=>"垂井町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2136200", "name"=>"関ケ原町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2138100", "name"=>"神戸町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2138200", "name"=>"輪之内町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2138300", "name"=>"安八町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2140100", "name"=>"揖斐川町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2140300", "name"=>"大野町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2140400", "name"=>"池田町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2142100", "name"=>"北方町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2150100", "name"=>"坂祝町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2150200", "name"=>"富加町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2150300", "name"=>"川辺町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2150400", "name"=>"七宗町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2150500", "name"=>"八百津町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2150600", "name"=>"白川町"}, {"link"=>"http://weather.livedoor.com/area/forecast/2150700", "name"=>"東白川村"}, {"link"=>"http://weather.livedoor.com/area/forecast/2152100", "name"=>"御嵩町"}] link, http://weather.livedoor.com/area/forecast/210010 forecasts, [{"dateLabel"=>"今日", "telop"=>"晴れ", "date"=>"2019-02-26", "temperature"=>{"min"=>nil, "max"=>nil}, "image"=>{"width"=>50, "url"=>"http://weather.livedoor.com/img/icon/1.gif", "title"=>"晴れ", "height"=>31}}, {"dateLabel"=>"明日", "telop"=>"曇り", "date"=>"2019-02-27", "temperature"=>{"min"=>{"celsius"=>"4", "fahrenheit"=>"39.2"}, "max"=>{"celsius"=>"13", "fahrenheit"=>"55.4"}}, "image"=>{"width"=>50, "url"=>"http://weather.livedoor.com/img/icon/8.gif", "title"=>"曇り", "height"=>31}}, {"dateLabel"=>"明後日", "telop"=>"曇のち雨", "date"=>"2019-02-28", "temperature"=>{"min"=>nil, "max"=>nil}, "image"=>{"width"=>50, "url"=>"http://weather.livedoor.com/img/icon/13.gif", "title"=>"曇のち雨", "height"=>31}}] location, {"city"=>"岐阜", "area"=>"東海", "prefecture"=>"岐阜県"} publicTime, 2019-02-26T17:00:00+0900 copyright, {"provider"=>[{"link"=>"http://tenki.jp/", "name"=>"日本気象協会"}], "link"=>"http://weather.livedoor.com/", "title"=>"(C) LINE Corporation", "image"=>{"width"=>118, "link"=>"http://weather.livedoor.com/", "url"=>"http://weather.livedoor.com/img/cmn/livedoor.gif", "title"=>"livedoor 天気情報", "height"=>26}} title, 岐阜県 岐阜 の天気 description, {"text"=>" 本州付近は、大陸に中心を持つ高気圧に緩やかに覆われています。\n\n 岐阜県は、おおむね晴れています。\n\n 今夜は、高気圧に覆われて、晴れるでしょう。\n\n 明日は、はじめ高気圧に覆われて晴れますが、気圧の谷や南から湿った空\n気の影響で朝から曇りとなるでしょう。", "publicTime"=>"2019-02-26T16:37:00+0900"}
まずdayとtelopのデータはどこに埋もれているか説明します。 上記データ構造を見ると、res_jsonの"forecasts"キーの値に[ "今日の天気のハッシュ", "明日の天気のハッシュ", "明後日の天気のハッシュ"]があります。
そのため今日の天気が欲しい場合は、res_json["forecasts"][0]
を参照すれば良いとわかります。
そしてres_json["forecasts"][0]
自体がハッシュとなっているため、dayとtelopを該当するキーを用いることで取り出せます。
ここまで聞くと、今日、明日、明後日の情報をコマンドライン入力で、0, 1, 2で指定してた意味が理解していただけると思います。
dayの取り出し方はday = res_json["forecasts"][day_number]["date"].split("-")[-1]
となっており、少し複雑になっているので簡単に説明しておきます。
res_json["forecasts"][0]["date"]
の値は2019-02-26
です。
欲しい情報は26
のみです。
嬉しいことに"date"の値のフォーマットが"yyyy-mm-dd"となっていますので、splitメソッドを使うことで配列[yyyy, mm, dd]
が作成できます。そして"dd"は配列末尾にあるため、-1番目の要素を参照することでdayの情報を取ることができます。
実装の問題点
ステップ1, 2の部分の実行速度が遅い。
解答と自分のプログラムを比較してみた感想
書き方は十人十色ですね。
特にday情報取得の実装方法は個人差がでるかもしれません。
気になる方はぜひ挑戦してみましょう。
参考書籍
あと挑戦してみて自分がRubyを使えていることに嬉しく思っています。
Ruby の知識は、伊藤 淳一さんのプロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法までで勉強したのですが、非常に力がついているような気がします。
プログラミング経験者で新たにRubyを始めようと思っている方はぜひお手にとってみてはいかがでしょうか。
大学院と大学の違い 経験談から語る大学院生について
様々な場面で
「いま大学生さんですか?」とか「お仕事何されてるんですか?」って質問をされるのですが、
「大学院生です。」と答えると、
大抵の返しは、
「えっ賢いんですね」、「大学院生って何やってるんですか」です。
ということで、大学院生とは何かを知らない人が多いと思いますので、
現在大学院卒業間近の僕が、卒業して大学院の経験を忘れないうちに文字に起こしておこうと思います。
- 大学院生と大学生の違いを知りたい人
- 大学院生の彼氏・彼女がいる人
- 大学院進学を考えている人
さきに結論から述べてしまうと、
ですが、それだと違いがわからないと思いますので、僕が在籍した情報科学系の大学院にフォーカスして説明します。
前提条件
まずは僕がどういった大学院にいるのかまとめておきます。
- 情報科学研究科・情報システム専攻(2019年3月卒業)
- 学部から同じ大学の大学院に進学
- 学部と同じ研究室所属
大学院生って何してるの?
この答えは所属研究室、研究内容に依存してしまいます。
僕の経験から大学院生が学内で何をしているのかリストアップしてみると、
- 研究
- 研究室のゼミや輪講
- TA(Teaching Assistant)
- 講義を受ける
それぞれについて掘り下げていきましょう。
研究って?
おそらく大学院生が何しているかを知らない人の疑問点の多くが、「研究」ではないでしょうか。
世間の研究のイメージってこんな感じになんか液体が入った試験管とにらめっこしてって感じなのでは?
たしかに化学系研究科の方であればそうかもしれません。
まず研究ってどんなことをするのって疑問を解いていきたいと思います。
研究は大きく
- テーマ決め
- 文献調査
- 仮説を立て検証
- 検証結果をもとに論文にまとめる
- 学会発表する・論文誌に投稿する
こんな感じです。
これらを順にやっていくというよりは、
テーマ決めのために、興味ある分野の文献調査をしたり、
こういう問題に昔から着目しているから、それに関する論文を調査したり、
文献調査と仮説検証を繰り返したり、
仮説検証しながら論文を書いたり、
やりかたは多様だと思います。
そして研究テーマは、研究室の特色や、個人が問題視している視点も多様なので仮説検証の仕方も異なります。
もしどのような研究をしているのか興味ある方は、
google scholarで興味あるキーワードを調べてみてください。
例えばブログと検索すると2018年以降にでた論文が584件ヒットしました。
横にpdfと表示されているところをクリックすれば論文が読めますので、ぜひ目を通していただければ、
どんなことをやっていて、論文ってこういうものなんだってことが少し理解していただけると思います。
あとこんなことも研究のテーマとして扱っているんだと驚きや面白さを感じていただけると思います。
研究室のゼミや輪講
ゼミ?、輪講?
これは学部生も共通してご存知だと思います。
ただ大学生以外の方にはイメージがつきにくいと思いますのでそれぞれ説明していきます。
ゼミ
僕が所属する研究室のゼミは研究の進捗や今週やることを指導教員の教授や所属学生に共有をします。
「研究進捗を報告し議論し意見交換をする場」ととらえていただくのがいいかもしれません。
もちろん学会発表用のスライドショーを確認したり、修士論文の相談等もします。
輪講
輪講は「輪を囲んだ講義」ってイメージなんですけど、
輪講は何か一つの専門書をみんなで精読し勉強するようなイメージでとらえていただくとわかりやすいと思います。
僕たちの研究室では確率過程や待ち行列モデルに関する英語で書かれた専門書をつかって勉強していました。
TA(Teaching Assistant)
TAとは学部生向けの講義のアシスタントをします。
座学中心の講義では、配布プリントの準備、配布、ミニッツペーパーやテストの回収・整理等が主な仕事です。
実験系の講義では、学生の質問に答えたり、支援したりします。
基本、TAはアルバイトのようなものと考えていただければいいと思います。
なのでお金をいただけます。
TAに関しては大学院生に限らず、大学によっては学部4年から任せられる子もいます。
僕の研究室では学部4年と大学院1年生がよくTAを任せられています。
講義を受ける
大学院に進学しても講義は受ける必要があります(卒業に必要な単位取得のため)。
僕が所属する大学院では、学部の講義とがらっと形式が変わりました。
- 講義形式
- 成績評価
- 学生規模
の3点から違いを述べます。
講義形式
学部までの講義は、座学が多く、先生が淡々と語る形式が多いです。
大学院では、座学形式ではなく、
学生が主役の形式が多くなります。
どういうことかというと、
毎時間、あるテーマについて1人か2人が発表をします。
それについて先生からの質問に答えたり、学生からの質問に答えるなど、議論を繰り返してテーマに関する知識を深めていく形式です。
成績評価
学部の講義の成績はテスト、レポートの割合がほとんどではないでしょうか。
大学院になるとテストはあまりないような印象でした。 僕は大学院の講義でテストが一回もありませんでした。
その代わり、発表が成績評価の対象となったり、発表したテーマや他の学生が発表したテーマを含め何かレポートを書くことがありました。
学生規模
学生規模については、学部と大学院の学生数がそもそも違うので、講義に参加する学生の人数も異なります。
僕が所属していた学部は 90人ほどだったので、90人前後が講義に参加していました。
大学院の定員は30人でしたが、3つの専攻に分かれています。
共通科目であれば、30人マックスで参加していますが、
ほとんどの講義が10人弱でした。
少ない講義では3人とかありました。笑
まとめ
今回は大学生と大学院生の違いについて紹介しました。
まとめると、 大学院生がやっていることは、
- 研究
- 研究室のゼミや輪講
- TA(Teaching Assistant)
- 講義を受ける
大学生との違いは
研究など自主的な活動がメインで自律性が求められるのかもしれません。
正直、だらけようと思えばだらけれるし、めちゃくちゃ頑張ろうと思えば頑張れます。
もし大学院進学するかどうか悩んでいるのであれば、
- 研究したいことがあるか
- 何かを突き詰めていくことが好きか
- 論文執筆を通して日本語のライティングスキルや論理的思考力を身に付けたい
以上の項目に合致する点が多いのであればぜひ大学院に進学してみてはいかがですか?
C++言語で少数第n位で四捨五入する
少数第3位で四捨五入をする必要があったので、メモがてらシェアしておきます。
今回やりたいことは、
例えば0.165895235であれば、0.17に四捨五入をしたい。
というように、C++言語で小数点以下のn番目で四捨五入をするにはどうすればいいかシェアします。
C++言語の四捨五入のためのライブラリについて
C++にはcmath.h
に四捨五入をする関数が含まれています。
こちらの関数の問題は、小数点以下を四捨五入して整数に丸めている点です。
そのため、小数点第n位で四捨五入することはできません。
round関数を使った関数実装
では小数点以下のn番目で四捨五入をする関数を実装していきます。
考え方は次の通りです。
- 四捨五入したい数に10の(n-1)乗かける。
- その値をround関数で小数点以下を四捨五入する。
- 最後に10の(n-1)乗を割る。
この考え方を例でやってみます。
例えば、0.165895235を少数第3位で四捨五入したいとき。
- 0.165895235を100倍します(10の(3-1)乗)。すると16.5895235になる。
- round関数で小数点以下を四捨五入する。すると17.0になる。
- 最後に1/100倍する。すると0.1700になる。
こんな感じに期待した値になってくれそうです。
以上を実装した関数は次の通りです。
#include <stdio.h> #include <stdlib.h> #include <cmath> /* 小数点n以下で四捨五入する */ float round_n(float number, double n) { number = number * pow(10,n-1); //四捨五入したい値を10の(n-1)乗倍する。 number = round(number); //小数点以下を四捨五入する。 number /= pow(10, n-1); //10の(n-1)乗で割る。 return number; } /* 以下テスト */ int main(int argc, char const *argv[]) { float f1=0.165895235; float f2=0.173455; printf("%fを少数第3位で四捨五入すると%f\n",f1,round_n(f1,3.0)); printf("%fを少数第3位で四捨五入すると%f\n",f2,round_n(f2,3.0)); }
上記プログラムの実行結果は
0.165895を少数第3位で四捨五入すると0.170000 0.173455を少数第3位で四捨五入すると0.170000
となり、期待する出力が得られました。
上記プログラムはパッと見た時に何をやっているかわかりやすく冗長に書いたものです。
リファクタリングしがいがありますが、もし同じことをやりたい場合はご参考に!!