バイセル Tech Blog

バイセル Tech Blogは株式会社BuySell Technologiesのエンジニア達が知見・発見を共有する技術ブログです。

バイセル Tech Blog

お店の空き時間をビット演算の論理和で算出する

こちらは バイセルテクノロジーズ Advent Calendar 2021 の17日目の記事です。前日の記事は赤川さん の「Smart Proxy Managerを用いたクローラー実行時のアクセス遮断への対策」でした。

こんにちは、テクノロジー戦略本部 開発2部の藤本です。

新しく始まったリユースプラットフォームプロジェクトのあるチームでPLをやっております。そちらがメインで最近はあまり実装に参加できていないのですが、少し前にお店の空き時間をビット演算の論理和を使って算出する実装を行い、個人的に面白いと思ったので紹介します。

バイセルの店舗買取について

バイセルでは買取の方法として出張訪問買取・店舗持込買取・宅配買取の3つがあり、店舗持込買取は比較的最近始まったものです。お客様に商品を店舗まで持ち込んでいただき、その場で査定・買取をさせていただきます。いまは5店舗ほど展開しております。

お店でもWeb予約をしたい

弊社の出張訪問買取はWebからも予約できるのですが、お店の予約は弊社コールセンターに電話する方法のみでした。 そこで、お店もWeb予約できるようにしたいという要望が上がりました。具体的な要望をまとめると以下のとおりです。

  • 各店舗毎に翌日〜2週間後までの空き時間がわかるようにしたい
  • 1時間枠(9:00-10:00など)で空き時間が表示されるようにしてほしい
  • 1時間枠に少しでも予定が入っているときは、空き時間として表示しないでほしい

実装方法検討

まずは実装方法のイメージを考えました。以下の図のように、店舗スタッフの一人でも1時間枠に空きがあったらそこを空き時間として算出する感じです。白枠の予定と、各スタッフの稼働時間(シフトの情報)はすでにDB上にテーブルとして存在するのですが、それぞれデータ型が異なるため、同じものに変換して計算しやすくすれば実装できそうと考えました。

(店舗のシフトのイメージ図)

ここから実装に落とし込む具体的な方法を検討しました。ちなみにこのシステムのバックエンドはRailsが使われているため、言語はRubyです。

1.RubyのRange型を利用

RubyにはRangeというデータ型があり、範囲を表すオブジェクトとして利用できます。それぞれの予定をRangeに置き換えればできそうと思ったのですが、複数のRangeオブジェクト同士で計算をすることができなさそうでしたので、こちらの利用は諦めました。

2.ビット演算の論理和を利用

上記の図の白枠の予定と各スタッフの稼働時間をすべてビット文字列に変換することでデータ型を統一し、さらにそれらの論理和をとれば、うまい具合に空き時間が算出できそうなので、この方法を採用しました。 例えば、ある日に10:00-20:00で稼働してすでに予定が10:00-12:00と13:00-15:00に入っている場合は以下のような感じになります。1が予定ありで、0が予定なしです。

時間 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23
稼働 1  1  1  1  1  1  1  1  1  1  0  0  0  0  0  0  0  0  0  0  0  1  1  1 
予定 0  0  0  0  0  0  0  0  0  0  1  1  0  1  1  0  0  0  0  0  0  0  0  0
空き 1  1  1  1  1  1  1  1  1  1  1  1  0  1  1  0  0  0  0  0  0  1  1  1

具体的な実装

1.店舗スタッフの予定を取得

各店舗のスタッフの稼働時間と予定はすでにデータベースに保存されています。指定の店舗・日時のそれぞれのデータを取得します。(以下のデータはサンプルで、実際のデータとは異なります。)

# 稼働時間
[#<Schedule:0x00005589948ea198
  id: 1,
  staff_id: 2,
  store: "有楽町店",
  start: Tue, 14 Dec 2021 10:30:00 JST +09:00,
  end: Tue, 14 Dec 2021 19:00:00 JST +09:00]

# 予定
[#<Plan:0x00005589948e9978
  id: 1,
  staff_id: 2,
  start: Tue, 14 Dec 2021 11:00:00 JST +09:00,
  end: Tue, 14 Dec 2021 12:00:00 JST +09:00>,
 #<Plan:0x00005589948e9478
  id: 2,
  staff_id: 2,
  start: Tue, 14 Dec 2021 15:00:00 JST +09:00,
  end: Tue, 14 Dec 2021 17:00:00 JST +09:00]
2.各データをビット文字列に変換

1で取得した勤務日のスケジュールと店舗スタッフの稼働日をすべて0,1のビットの文字列に変換します。それぞれのデータを以下のコードに渡して0,1のビットに変換するイメージです。

def build_time_bits
  duration = @start_time.beginning_of_day
  # 24時間分のiterationをしたいので、1日を1時間で等分した範囲オブジェクトを作る
  range = Range.new(1, 1.day / @time_span)

  range.inject('') do |bits, _|
    bits += if @start_time <= duration && duration < @end_time
              @occupied_bit
            else
              @free_bit
            end

    duration += @time_span
    bits
  end
end

これによって以下のようなビット文字列の配列を取得します。

@time_bits = [
  "111111111110000000011111", # 稼働時間
  "000000000001000000000000", # 予定1
  "000000000000000110000000"  # 予定2
]
3.論理和を用いて空き時間ビットを計算

論理和によって稼働時間と予定のどちらかが1だったら1、どちらも0なら0にします

def get_free_time_bits
  @time_bits.inject('') do |result, bit|
    next result = bit if result.empty?

    (result.to_i(2) | bit.to_i(2)).to_s(2)
  end
end
# -> "111111111111000110011111"
4.空き時間ビットを日時に戻し、フロントに返却

3のビットを日時に戻します

def convert_bits_to_times(free_time_bits)
  free_time_bits.each_char.each_with_object([]) do |bit, result|
    result << { start: @day, end: @day + @time_span } if bit.include?('0')
    @day += @time_span
  end
end

日時戻すと以下のようになりますので、それをフロントに返却します

[{:start=>Tue, 14 Dec 2021 12:00:00 JST +09:00, :end=>Tue, 14 Dec 2021 13:00:00 JST +09:00},
 {:start=>Tue, 14 Dec 2021 13:00:00 JST +09:00, :end=>Tue, 14 Dec 2021 14:00:00 JST +09:00},
 {:start=>Tue, 14 Dec 2021 14:00:00 JST +09:00, :end=>Tue, 14 Dec 2021 15:00:00 JST +09:00},
 {:start=>Tue, 14 Dec 2021 17:00:00 JST +09:00, :end=>Tue, 14 Dec 2021 18:00:00 JST +09:00},
 {:start=>Tue, 14 Dec 2021 18:00:00 JST +09:00, :end=>Tue, 14 Dec 2021 19:00:00 JST +09:00}]

実際の画面

FEの実装は割愛しますが、返却されると以下のようになり、1時間ごとの予約時間がぱっと見でわかります。

最後に

バイセルでは今回紹介したようなものを含め、色々な機能を実装する機会があります。 また、最近はリユースプラットフォームプロジェクトも始動しており、技術的にとても面白いものがたくさんあります。 ご興味をお持ちいただけた方はぜひ以下からコンタクトしてみてください。

herp.careers

明日の バイセルテクノロジーズ Advent Calendar 2021 は 酒井さんによる 「GAを導入してみた」 です。

参考にさせて頂いたサイト