もう 12/20 なのに、チャレンジしているのは 4 日目。それで良いのかは甚だ疑問だが、まぁ、やらないよりいいだろう。

4 日目: Secure Container

さて、宇宙船は金星給油基地についたようだ。 しかし、補給コンテナはパスワードで保護されていた。 エルフがポストイットにパスワードを貼り付けていて、そしてどっかに行ったらしい。

1 問目:条件に合う数字はいくつある?

さて、パスワードは次のルールを満たしている。

  • パスワードは 6 桁の整数値。
  • パスワードは指定された 2 つの数値の間に存在する。
  • 隣接して同じ数字が 2 つ並びます。(例 122345 だと 22)
  • 数値は、左から順に見ると、同じ数字か、大きくなります。(例 111123, 135679)

さて、これらの条件を満たすパスワードがいくつあるか見つけなければならない。

今回、俺に与えられた範囲は 246515-739105。

そんなわけで、手っ取り早く範囲をループで回してチェックしてみよう。

中身は後で書くとして、形はこんな感じ。

#! /usr/bin/env ruby

range = 246515..739105

res = []
range.each do |n|
  res << n
end

p res.size

後は 2 つの条件をそれぞれ実装してみよう。

今回の問題では各数字を一桁ずつの配列に突っ込んで処理するのが良いだろう。 複数桁の数値を 1 桁ずつの配列にするには、log10 でやるのが正攻法だ。 ただ、 ruby ならそんなに気にせず文字列を経由して配列にバラせる。

n = 204923
n.to_s.split("").map(&:to_i) # [2, 0, 4, 9, 2, 3]

後は、こんな感じに条件を書けばよかろう。

# 隣接して同じ数字が 2 つ並びます。(例 122345 だと 22)
(na[0] == na[1]) || (na[1] == na[2]) || (na[2] == na[3]) || (na[3] == na[4]) || (na[4] == na[5])

# 数値は、左から順に見ると、同じ数字か、大きくなります。(例 111123, 135679)
(na[0] <= na[1]) && (na[1] <= na[2]) && (na[2] <= na[3]) && (na[3] <= na[4]) && (na[4] <= na[5])

後はこれにあう数値の数を数えればオッケーだ。

2 問目:さらに条件に合う数字はいくつある?

エルフ氏曰く、さらに条件があるとのこと。

  • 隣接する2つの一致する数字は、一致する数字の大きなグループの一部ではありません。(google翻訳)

つまり、下記のようなことになる。

  • 111234 はアウト。並んでいる数字 11 は 111 の一部
  • 119999 はセーフ。並んでいる数字は 11 と 9999 があり、99 は 9999 の一部ではあるが、11 が単独である
  • 234445 はアウト。並んでいる数字 44 は 444 の一部
  • 111199 はセーフ。並んでいる数字は 1111 と 99 があり、11 は 1111 の一部ではあるが、99 が単独である

さて、この条件を満たすためにはどうすれば良いか・・・

まず、同じ数字の並びを抜き出す。 数字並びのうち、大きい方が、この数字グループの最大より

ある数値の配列の中から、連続する数字のブロックを抜き出すメソッドを作ってみよう。

まずは配列を用意する。

buf = []; rand(1000..2000).times{ buf << rand(10) }

この配列を走査するわけだから、メソッドの構造はこんなかんじ。

def get_gropus(ary)
  groups = []

  idx = 0
  while idx < ary.size
    # 連続する数値を見つけたら groups に突っ込む
    idx += 1
  end

  groups
end

ループの中を考えていくと、次の要素と自分とを比較することになる。

i = 0
while idx < ary.size
  current = ary[idx]
  if current == ary[idx + 1]
    # 連続する数値を見つけたら groups に突っ込む
  end
  idx += 1
end

今回は連続する数値を見て行きたいので、ここももちろんループになる。 中のループで見た場所は、次のループで見なくて良いので、 idx += 1 の部分にも少し工夫をしよう。

i = 0
while idx < ary.size

  current = ary[idx]

  group = [current]
  j = 1
  # いくつ連続するかわからないので、無限ループで配列を走査していく。
  while true
    # 次の数値と違うなら、ループを抜ける
    break unless current == ary[idx + j]

    group << ary[idx + j]
    j += 1
  end

  idx += j # 上の無限ループで隣り合うところまでは配列の中身を見て行ったので、 `j` を足す。
end

最後に、グループを返すようにすれば良いだろう。すると、メソッドの全体像は下記のようになる。

def get_groupsl(ary)
  groups = []

  i = 0
  while idx < ary.size

    current = ary[idx]

    group = [current]
    j = 1
    # いくつ連続するかわからないので、無限ループで配列を走査していく。
    while true
      # 次の数値と違うなら、ループを抜ける
      break unless current == ary[idx + j]

      group << ary[idx + j]
      j += 1
    end

    groups << group if group.size > 1
    idx += j # 上の無限ループで隣り合うところまでは配列の中身を見て行ったので、 `j` を足す。
  end

  groups
end

後は、このメソッドで得られたグループのうち、長さが 2 になる、つまり隣り合う数字が 2 桁までのものが必ず含まれているかを確認すれば良い。

get_group(ary).any?{|g| g.size == 2}

これでできた。