【Solr】Date Mathによる日付表現

はじめに

Solr では、pdate 等の時刻を扱うフィールドに対するクエリで使用できる Date Math という表現が用意されています。「今日からN日前」のような表現を使いたいときに便利です。 この記事ではDate Mathについてまとめました。

通常の日付表現

Solr では日付のフィールドは以下の形式で表現します。

2020-03-22T14:55:29Z

ミリ秒までの精度があるので、小数点表記も有効です。

2020-03-22T14:55:29.123Z

時刻は秒まできちんと表記しなければエラーになります。

ミリ秒よりも小さい桁が書いてあっても受付けますが、無視されます。

エラーになる例

2020-03-22
2020-03-22T14:55Z

Date Math

Date Mathは、特定の時点からの相対的な時刻を指定しやすくするための表記法です。
起点として良く使用される「今現在」を指定するために NOW という Date Math が用意されています。

Date Mathではまず起点となる日付を書き、それプラスN日という形で表記します。
「2ヶ月後」

NOW+2MONTH

「10日前」

NOW-10DAY

「1年後」

NOW-1YEAR

プラス/マイナスの部分は連続して書くことができます。

NOW+1YEAR+3MONTH+5DAY

起点として通常の日付を指定することもできます。

2020-03-22T10:15:18Z+7DAY

丸め表記

スラッシュ(‘/’)を併用することで、「〜の最初」を表現できます。
たとえば現在時刻が2020年3月22日11時22分33秒だとします。

NOW/HOUR

→2020年3月22日11時00分00秒

NOW/DAY

→2020年3月22日00時00分00秒

Date Math と組み合わせて使えるリクエストパラメータ

NOW

NOW パラメータにより、Date Math で記述した “NOW” を特定の日付に設定することができます。 NOW パラメータには、いわゆる UNIX TIME の Long 値を指定します。
NOW パラメータを指定することで、複数のノードにまたがる分散検索での NOW の値を揃えることができます。テストのときにも役に立ちそうです。

TZ

TZ パラメータを指定することで、デフォルト UTC である Date Math の日付計算を別のタイムゾーンとして扱うことができます。

【家で遊ぶ】Javascript で簡易サンプラー

Photo by Kofi Nuamah Barden on Unsplash

Javascript を使ってブラウザの Chrome で鳴らせる簡易サンプラーを作ってみます。

こんなの。

DJブースの隅やイベント会場に置いてあるやつです。

爆発音を鳴らしてみたり、録音機能があったり、パターンを保存して再生する機能が付いてあったり、利用場面に合わせて様々な使われ方をされます。

ビートボックス、リズムマシンとも呼ばれたりします。

Tone.js

今回は、簡易版ということで、音が3つ鳴るだけのサンプラーです。

作成に当たっては、偉大なる Web Audio framework の Tone.js を利用します。

A Web Audio framework for making interactive music in the browser.
https://github.com/Tonejs/Tone.js/

動作デモ

まずは見ていただいた方が早いと思うので、 作ったものを netlify に上げておきました。

netlify.app

Chrome 以外では確認していません。
音が出るので注意。
PC、最近のスマートフォンなら動くと思いますが、環境によってはブラウザの「音声」を許可する必要あり。

やってること

主要部分はこんな感じになります。

!function(){
	Tone.Transport.bpm.value = 80;
	Tone.Transport.loop = false;
	const sampler = new Tone.Sampler({
		'C1' : '/bd.mp3',
		'E1' : '/sd.mp3',
		'F#1': '/hh.mp3'
	}, ()=>{
		console.log('initialized')
	}).toMaster();
	const sound = note => {
		const cell = document.querySelector('[data-note="'+note+'"]');
		sampler.triggerAttackRelease(note, .3);
		cell.style.animationName = 'fade1';
		setTimeout(()=>{
			cell.style.animationName = '';
		}, 300)
	}
	const beatbox = document.getElementById('petit-beatbox');
	beatbox.querySelectorAll('div').forEach(div=>{
		div.onclick = e => {
			sound(e.currentTarget.dataset.note)
		};
	});
	document.body.onkeypress = e => {
		if (e.code === 'KeyA') {
			sound('C1');
		} else if (e.code === 'KeyS') {
			sound('E1');
		} else if (e.code === 'KeyD') {
			sound('F#1')
		}
	}
}();

赤、黄、緑の四角をクリックするか、キーボードのASDでそれぞれ鳴らしたい音が再生されるようイベントリスナーをセットします。

ほぼこれだけです。

ボタンを追加して効果音や音色を設定していけば色々遊べます。

Javascript なので、その気になれば、その場でソースコードごと変更してしまうことも可能です。

(ラグがある場合は音自体の軽量化までやらないと駄目かも…)

家で遊ぶ

Tone.js には様々な API が用意されているので、頑張ればエフェクターやシーケンサーとしての機能も追加できます。

Tone.js っょぃ。

逆に、Tone.js のない標準の Web Audio API は、かなりしんどいです。
「シンプルなサイン波を440Hzで3秒間ほどほどの音量にして鳴らす」だけでもう再起不能に陥り寝込んでしまいます。

おうちで自家生産、サンプラーを作って遊んでみるのはいかがでしょうか。

おうち時間で爆弾解除をしてみた話

こんにちは。デザイナーのMです。

新型コロナウィルスの影響で、弊社は約1ヶ月前頃から在宅での勤務体制となっています。
プライベートでも、週末は近所を少し散歩するぐらいしか外出をしておらずほとんど家の中で過ごす、いわゆる「おうち時間」を実践しています🏡
ただ、1ヶ月近く家の中で過ごしていたらさすがにやることも少なくなってきます。。
「ウォーキング・デッド」という海外ドラマをずっと観ていたのですが、それも最新エピソードまで追いついてしまい次に観るのを何にしようか悩んでいるところです😂

そんなとき後輩デザイナーが日報に、あるゲームを紹介しているのを読んで興味が湧き、やってみようと思いました。

それがこちら、
「完全爆弾解除マニュアル:Keep Talking and Nobody Explodes

https://keeptalkinggame.com/ja/



タイトルだけで面白そうじゃないですか?(私だけでしょうか👀
PS4やNintendo Switch、PCのマルチプラットフォームに対応しているインディーゲームです。

どんなゲームか簡単に説明すると、『2人以上で会話をしながら爆弾解除にチャレンジする独特なシミュレーターゲーム!』です。
1人が「処理担当者」となり、ゲーム画面を見ながら画面内の爆弾の状況を説明して、その他の人は「分析担当者」となり「処理担当者」から説明された爆弾の状況を<爆弾解除マニュアル>を見ながら解除方法を「処理担当者」に伝えて、協力しながら一つの爆弾を解除するのです。
要するに、いかに相手に情報をうまく伝えることができるかを試されるゲームです。

「処理担当者」はマニュアルを見れない、「分析担当者」は爆弾が見れないので目で見た情報を丁寧に分析して相手に伝える必要があるので本当に難しいです。
デザイナーたるもの「伝える」ことが仕事だと思っているので、やっている最中はいかにわかりやすく相手に伝えられるかを意識してプレイしました😂

価格は1,600円(Nintendo Switch版)で、<爆弾解除マニュアル>はこちらから無料でダウンロードすることができます。
印刷したほうが雰囲気は味わえるのですが、スマホやタブレットで閲覧しても特に問題なく利用できました。

爆弾の状況を分析する際にメモは必須です。マニュアルに書いてあるヒントをもとに、必死に解除方法をメモ帳に状況を書き殴りながら考えました(笑)


以下、プレイ画面です。


爆弾には「モジュール」という部品が付いていて、この「モジュール」をマニュアルに書いてあるヒントをもとに解除していきます。
爆弾に付いているすべての「モジュール」を解除すれば、爆弾解除となります。
また、アラーム音やBGMが程よく緊張感を与えてくれます。
1回間違えるごとに残り時間の進みが少しだけ早くなるのでさらに焦ります(笑)


「ノーマルモード」と「フリーモード」の2つのゲームモードがあります。

「ノーマルモード」は、ミッション形式になっていて進むごとに解除が必要なモジュールが増えて難しくなります。(私は4つめのミッションぐらいから詰みはじめました笑)

「フリーモード」は、モジュール数や制限時間を自由に設定できるモードです。好きなミッションを作って遊べます。

プレイしているとあっという間に時間が過ぎて、暇になりそうだったおうち時間がはかどりました🏡
パズルゲームやパーティーゲームが好きな方にはとてもおすすめなので、ぜひプレイしてみてください🎮スマホアプリ版があればもっと手軽にできるかなと思いました。開発会社さんよろしくおねがいします(笑)

コロナで皆さん大変かと思いますが、少しでもおうち時間が楽しい時間になるように、いろいろなものを今後も取り入れていきたいと思います👌

コミュニケーションって難しい

こんにちわ。
リエです。

突然ですが、日常生活の中でなんかこの人と話が噛み合わないな〜と感じることってありませんか?私はあります(小声)
話が噛み合わない=コミュニケーションが取れていないので、改善する必要があるのですが、なぜ共通の話をしているのに噛み合わないのでしょうか。
色々な原因がありますが、割とポピュラーなケースを今回はご紹介したいと思います。

話が整理されておらず、相手に目的が伝わっていない

「なんで話が通じないんだろう」というとき、自分の伝え方に問題はないでしょうか。相手のコミュニケーション能力が高い場合は、話が整理されていなかったり結論がまとまっていなくてもある程度内容を汲み取ってくれますが、整理がされていない話を聞くのは疲れるし困ってしまいます。
俗に言う[何を話したいのかがわからない]状態です。
ビジネスの上ではこれは割と致命的。かつ忙しい相手に話すのであればなおさら話を整理し簡潔に伝える必要があります。
きちんと自分が何を伝えたいか整理して伝えるようにすると円滑にコミュニケーションがとれます。

まわりくどい言い方をしている

思わず結論は!?と聞いてしまうほど、まわりくどい言い方する人いますよね。。私はビジネスメールの書き方や話し方は結論から先にというのを教わりました。お仕事を続けていく上でこれは本当に大事だなと感じています。
こちらから発信するというのは相手の時間を頂戴しているという事なので、簡潔にわかりやすく紛らわしい表現はしないようにというのを常に意識しています。話の組み立て方で「PREP法」という法則があるのですが、うまく組み立てられない〜!となった時はこの法則を使うといいと思います。
〜PREP法とは?〜
PREP法は主にビジネスシーンで用いられる文章構成方法であり、簡潔かつ説得力のある文章を作成する際に用いられる。
Point(結論)
Reason(理由)
Example(事例、具体例)
Point(結論を繰り返す)
参考:https://ja.wikipedia.org/wiki/PREP%E6%B3%95

後はメールは話し言葉で書かないことも大事かなと個人的に思っています。※時と場合による

相手が話を聞いていない

これは相手側に原因がある場合ですね。もちろん話を聞いていないというのは時と場合にもよりますが、(相手が忙しい時に話した、そもそも他人の意見を聞かないタイプなど)その場合は別のアプローチを考える必要があります。
自分の意見が100%正しいと思っていて、他人の意見を受け入れない人もこのタイプですね。この場合改善するのは大変ですが、諦めず相手の性格的にどのような方法だったら話を聞いてくれるか考えてみましょう。

話の内容を理解してくれようとしない

一生懸命話しをわかりやすく伝わるように努力しても、理解してくれない場合があります。相手によって理解の方法は異るので(例えば文字や図を交えて説明した方が理解してもらえるなど)模索しながら相手にも理解してもらう方法を考える必要があります。

まとめ

話が通じない・噛み合わない原因は色々ありますが、

色々考えたり経験した中で「話が通じない・噛み合わない→自分はちゃんと伝えているのに・・・」ではなく「一歩引いてこの人にはどうやってアプローチしたらいいんだろう」と考えるようになりました。
コミュニケーションを取ることを諦めるのは簡単ですが、そうすると何も解決しません。なので諦めず柔軟にコミュニケーションを取っていきたいと思います。

SolrのDateRangeFieldで日時の範囲を扱う

はじめに

Solrで日時を扱うためのフィールドタイプとして、通常使う DatePointField の他にもう一つ DateRangeField があります。その名の通り幅の有る時間を取り扱えます。

DateRangeFieldとDatePointField

店舗の営業時間を扱うことを考えてみます。

<fieldType name="pdate" class="solr.DatePointField" docValues="true"/>
<field name="start" type="pdate" indexed="true"/>
<field name="end" type="pdate" indexed="true"/>

与えられた時刻(たとえば2020年3月1日19時)に営業している店舗を調べるには、営業開始時刻(start)と営業終了時刻(end)のフィールドを定義して、「startが2020年3月1日19時よりも前」かつ「endが2020年3月1日19時よりも後」である店舗を検索します。 (話を簡単にするためこの記事ではタイムゾーンのことは考えないことにします)

start:[* TO 2020-03-01T19:00:00Z] AND end:[2020-03-01T19:00:00Z * TO]

対して DateRangeFieldを使えば、営業時間のフィールドが1つで済みます

<fieldType name="rdate" class="solr.DateRangeField" docValues="true"/>
<field name="start_end" type="rdate" indexed="true" multiValued="true"/>

投入するデータは以下のような形式です。

[
    {
     "id":"1",
     "start_end":[
	 "[2020-03-01T18:00Z TO 2020-03-01T23:00Z]"
     ]
    }
]

以下のクエリで2020年3月1日19時に営業している店舗を検索できます。

start_end:"2020-03-01T19:00:00Z"

DateRangeFieldを使うパターンだと営業時間を複数登録するのも簡単です。

[
    {
     "id":"2",
     "start_end":[
	 "[2020-03-01T11:00Z TO 2020-03-01T14:00Z]",
	 "[2020-03-01T18:00Z TO 2020-03-01T23:00Z]",
	 "[2020-03-02T18:00Z TO 2020-03-02T23:00Z]",
	 "[2020-03-04T11:00Z TO 2020-03-04T14:00Z]",
	 "[2020-03-04T18:00Z TO 2020-03-04T23:00Z]"
     ]
    }
]

クエリは先程と同じもので大丈夫です。

start_end:"2020-03-01T19:00:00Z"

DatePointFieldでstartとendの2つのフィールドを使うパターンだと、どうでしょう?
start と end を multiValued にしても解決しません。

[
    {
     "id":"10",
     "start":["2020-03-01T11:00Z","2020-03-01T18:00Z"],
     "end":["2020-03-01T14:00Z","2020-03-01T23:00Z"]
    }
]

と登録したとして、startのどちらがendのどちらに対応しているのかが検索時に区別できないからです。したがって、別のレコードに分けなければなりません。

[
    {
     "id":"10",
     "start":"2020-03-01T11:00Z",
     "end":"2020-03-01T14:00Z"
    },
    {
     "id":"11",
     "start":"2020-03-01T18:00Z",
     "end":"2020-03-01T23:00Z"
    }
]

営業時間だけを扱っている分にはこれでも良さそうに見えますが、他の店舗情報(店舗名、住所、電話番号等)もインデックスに含めるとすると、以下のどちらかを考えなければならなくなります。

  • すべてのレコードに店舗情報を含める(すごく冗長)
  • 店舗情報と営業時間情報を分けてJOINする(処理が複雑になる)

どちらにしても、DateRangeField を使った場合のシンプルさには比べるべくもありません。

検索時のオプション

インデックス側とクエリ側のどちらも時間範囲である場合、以下の5通りの組み合わせが考えられます。

  1. 全く重ならない(例: インデックス「1時から2時」クエリ「4時から5時」)
  2. 完全に一致する(例: インデックス「1時から2時」クエリ「4時から5時」)
  3. 一部分が重なる(例: インデックス「1時から2時」クエリ「1時半から3時」)
  4. インデックス側がクエリ側に含まれる(例: インデックス「1時から2時」クエリ「0時から3時」)
  5. クエリ側がインデックス側に含まれる(例: インデックス「1時から2時」クエリ「1時20分から1時40分」)

検索クエリとしてヒットしたかどうかの判定時に、1はヒットしない、2はヒットしたでいいのですが、3,4,5は要件によって判定を変えたいことがあります。そこで、以下のオプションが用意されています。

  • Intersects(デフォルト): 2,3,4,5がヒット
  • Within: 2,4がヒット
  • Contains: 2,5がヒット

と思ったら、8.4.0の実際の挙動は以下の通りでした。

  • Intersects(デフォルト): 2,3,4,5がヒット
  • Within: 4がヒット
  • Contains: 2,5がヒット

具体的には

[2020-03-01T01:00Z TO 2020-03-01T02:00Z]

というデータに対して

{!field f=dateRange op=Within}[2020-03-01T01:00:00Z TO 2020-03-01T02:00:00Z]

というクエリがヒットしませんでした。opがContainsやIntersectsならヒットしました。Withinの場合は両端のどちらかが一致しているとヒットにはならず、インデックス側が完全に内側に無いといけないようです。

おわりに

DateRangeFieldを使うと、幅の有る時間のデータをシンプルに取り扱うことができます。「日付データ→pdate」と短絡せず、要件に合わせて使い分けるようにしたいところです。