summer_tree_home

Check iOでPython3をマスターするぜっ

Buildings visibility (Mine) - 建物の投影図

どんな問題?

Buildings visibility
http://www.checkio.org/mission/buildings-visibility/

建物の配置データから、南側から見える建物の数を調べよ。

引数は、建物の配置データで、建物ごとに、座標(x1,y1,x2,y2)と高さがリスト形式で指定される。座標はintだが、高さはfloatなので注意。
戻り値は、南側から見える建物の数を返す。

建物は軸に平行に建っており、視野角などは考えない。(平行投影で正面図を表示させたイメージ)

例題:

checkio([
    [1, 1, 4, 5, 3.5],
    [2, 6, 4, 8, 5],
    [5, 1, 9, 3, 6],
    [5, 5, 6, 6, 8],
    [7, 4, 10, 6, 4],
    [5, 7, 10, 8, 3]
]) == 5 #"First"
checkio([
    [1, 1, 11, 2, 2],
    [2, 3, 10, 4, 1],
    [3, 5, 9, 6, 3],
    [4, 7, 8, 8, 2]
]) == 2 #"Second"
assert checkio([
    [1, 1, 3, 3, 6],
    [5, 1, 7, 3, 6],
    [9, 1, 11, 3, 6],
    [1, 4, 3, 6, 6],
    [5, 4, 7, 6, 6],
    [9, 4, 11, 6, 6],
    [1, 7, 11, 8, 3.25]
]) == 4 #"Third"

どうやって解く?

建物の座標は整数値なので、X=1のときに見える建物、X=2のときに見える建物、というようにX軸にそって調べていけばよさそうだ。

この例で言えば、
f:id:summer_tree:20140418235110p:plain
まず、X=nに存在する建物を調べる。例えば、

  • X=1のときは、高さ3.5の建物
  • X=2のときは、高さ3.5と5の建物
  • X=5のときは、高さ6、8、3の建物

となる。

次に、南側から見える建物を数える。手前(南側)から順に建物の高さを調べて、手前より高い建物だけをカウントする。X=5のときは3つの建物があるが、南側から見えるのは、高さ6と8の建物の2つ。

まとめ

from collections import namedtuple

Building = namedtuple('Building', 'x1 y1 x2 y2 height')

def checkio(buildings):
    # 引数をBuildingのリストに変換
    bs = [Building(*b) for b in buildings]

    result = set()
    for x in range(min(b.x1 for b in bs), max(b.x2 for b in bs)):
        height = 0
        # 手前(南側)から順に高さをチェック
        for b in sorted([b for b in bs if b.x1 <= x < b.x2], key=lambda b: b.y1):
            # 前の建物より高ければ resultに追加
            if b.height > height:
                result.add(b)
                height = b.height
    return len(result)

http://www.checkio.org/mission/buildings-visibility/publications/natsuki/python-3/first/

他の人の答え

def checkio(b):
    def v(d,r=False,z={}):
        for x in range(d[0],d[2]):
            if d[4]>z.get(x,0): r,z[x]=True,d[4]
        return r
    return sum(map(v,sorted(b,key=lambda d: d[1])))

http://www.checkio.org/mission/buildings-visibility/publications/veky/python-3/closure/

最初、なんでこれで動くのかがよくわからなかったが、ポイントは引数の z={} だった。

Pythonのデフォルト引数は、最初に一度だけ初期化される仕様になっている。だから、デフォルト引数にmutableな値を指定しないのがお約束なんだが、この仕様を逆手にとってデフォルト引数をグローバル変数のように利用することもできる。

zは、最初に{}に初期化されて、あとは使い回されるので、これでうまく動くのだ。なるほどなぁ。