Determine the order (GitHub) - 文字の順番
どんな問題?
Determine the order
http://www.checkio.org/mission/determine-the-order/
独自のアルファベット順に応じて、ワードの文字が並んでいる。ワードのリストから、独自のアルファベット順を探し出せ。
ただし、順番が決定できない場合は、通常のアルファベット順(a-z)を使うこと。
引数は、ワードのリスト。ワード数は10個未満。
戻り値は、並び順を文字列で返す。
例えば、ワードが "acb","bd","zwa" の場合、
- "acb"からは、a<c<b という順番がわかる
- "bd"からは、b<d
- "zwa"からは、z<w<a
まとめると、z<w<a<c<b<d という順が得られるので、戻り値は"zwacbd"となる。
例題:
checkio(["acb", "bd", "zwa"]) == "zwacbd" checkio(["klm", "kadl", "lsm"]) == "kadlsm" checkio(["a", "b", "c"]) == "abc" checkio(["aazzss"]) == "azs" checkio(["dfg", "frt", "tyg"]) == "dfrtyg"
どうやって解く?
文字ごとに優先度を割りあてて、例えば、最初のワードが"acb"なら a=1.0、c=2.0、b=3.0 としておき、次のワード"bd"では、b=3.0だからd=4.0としておいて・・・最後にソートするとか?
うーん、ちょっとややこしいか。
もっと簡単な方法はないかな。
最初の文字(一番小さい文字)を探すには、「自分より左に他の文字が来ない文字」を探せばいいかな?
例えば、"acb", "bd", "zwa"なら、acbdwの左には何らかの文字が来るけど、zの左に来る文字は無い。つまりzが最初の文字となる。次はzを除いて同じ事を繰り返せば、文字の順番が判明するはず。
これは、言い換えると、「ワードに文字が含まれている場合は、必ず先頭に来る文字」を探せばいい。
まず、ワードリスト(data)に出てくる文字をアルファベット順にソートしておく。
letters = sorted(set(''.join(data)))
すべてのワードで「ワードに文字が含まれている場合は、必ずワードの先頭に来る」を満たしている文字が、最初の文字となる。sorted()することで、最初の文字が複数あるときには、a-zの順番が優先される。
def get_first_letter(words, letters, ignore): # 最初の文字を取得(ただしignoreは除外する) for l in sorted(letters - set(ignore)): if all(next(c for c in w if c not in ignore) == l for w in words if l in w): return l raise Exception('first letter not found') def checkio(data): letters = set(''.join(data)) # dataに出てくる文字セットを取得 result = '' while len(result) < len(letters): result += get_first_letter(data, letters, result)
http://www.checkio.org/mission/determine-the-order/publications/natsuki/python-3/first/
他の人の答え
いくつかの解答のコメントで、「トポロジカルソート」という単語が出てきているのだが、どういうソートなんだろう。
http://www.checkio.org/mission/determine-the-order/publications/EnCuKou/python-3/first/
http://www.checkio.org/mission/determine-the-order/publications/kvas/python-3/literal-toposort/
Wikipediaの説明に沿ってコードを書いてみた。
def checkio(data): # グラフ(dict)を作成 # key = ノード(文字) # value = 入力辺(一つ前に来る文字)のset graph = {node: set() for node in ''.join(data)} for word in data: for i in range(1, len(word)): node, prev = word[i], word[i - 1] if prev != node: graph[node].add(prev) l = [] # ソートの結果 s = [node for node, inputs in graph.items() if not inputs] # 入力辺を持たないノード while s: n = sorted(s)[0] # アルファベット順に取り出す s.remove(n) l.append(n) # 入力辺にnが含まれるノード(=nからの出力先ノード)を探す for m in [node for node, inputs in graph.items() if n in inputs]: # mがn以外の入力辺を持たないならsに追加 graph[m].remove(n) # mの入力辺nを削除 if not graph[m]: # mの入力辺が空ならsに追加 s.append(m) if all(not inputs for inputs in graph.values()): # グラフが辺を持っていない return ''.join(l) else: raise Exception('graph has at least one cycle') # グラフに少なくとも1つの循環がある
入力辺を持たない、というのが、もっとも左側に来る文字、ということになるんだな。なるほど。
残る島は、Mine、HubSpot、Ice Baseの3つ。じゃあ、次はMineへ。