web-dev-qa-db-ja.com

一緒になってしまうペアが与えられた単語のセットをどのように分割できますか?

スペースまたはコンマで区切られた2つの列を持つテーブルがあり、各行は2つの単語の同等性を表します。

A B  
B C  
B D  
C E  
F G

私が欲しいのは、各行が相互に同等の単語をすべてリストしたテーブルです。

A B C D E  
F G 

つまり、入力の同じ行に2つの単語が出現した場合、それらは出力の同じ行に含まれる必要があります。

どんなツールでもかまいません。

3
Vinay

Pythonでは、引数として入力ファイルから始めます。

_import sys

res = []  # list of lists
for line in open(sys.argv[1]):
    try:
        x, y = line.split()  # split on space
    except ValueError:
        line = line.rstrip()
        x, y = line.split(',')  # retry with comma
    for l in res:
        if x in l:
            if y not in l:
                l.append(y)
            break
    else:
        res.append([x, y])

for line in res:
    print ' '.join(line)
_

テスト_if y not in l:_は、同じ値を2回追加することをスキップします。それが必要かどうか、またはソースにそのような異常があるかどうかはわかりません。テストを省略して、常にl.append(y)を実行できます。

コードは最初にスペースで分割を試み、次にコンマを再試行します。これは、コンマで区切られた行にスペースがないことを前提としています(つまり、_A, B_ではありません)。

ネストされたforループは(AFAIK)を使用しますpython特殊性:elseは、forループが枯渇によって終了した場合にのみ実行されます。これは、xが見つからない場合、ペアが新しいリストとしてresに追加されることを意味します。

3
Anthon

理論

この問題は 集合の分割 から 同値類 として知られており、入力ファイルにはペアごとの同値がリストされています。 disjoint-set データ構造の助けを借りて解決することができます。

あまり抽象的な例は、例えばです。同義語のペアを指定して、単語を同義語のグループに分割します。

large big
big great
great vast
small little
little tiny

になります:

large big great vast
small little tiny

Rubyソリューション

互いに素なセットはRuby標準ライブラリでは利用できないので、Ruby Hash(他では「連想配列」として知られています)を使用してエミュレートします、 "辞書"、 "マップ")。

#!/usr/bin/env Ruby
# new elements end up in "singleton subsets"
subsets = Hash.new { |subsets, element| subsets[element] = [element] }
ARGF.each do |line|
  x, y = line.scan(/[^\s,]/)
  # these two emulate disjoint-set's "find" operation
  x_set = subsets[x]
  y_set = subsets[y]
  # and this loop implements disjoint-set's "union"
  y_set.each do |element, _|
    subsets[element] = x_set << element
  end unless x_set == y_set
end
puts subsets.values.uniq.map{|set| set.join(" ")}

使用法

これは、コマンドラインのファイル名またはstdinのデータを想定しています。

$ Ruby so-162730.rb input.txt
A B C D E
F G

$ Ruby so-162730.rb < input.txt
A B C D E
F G

awkソリューション

おそらくこのサイトにより適しています。

ここでは、disjoint-setのわずかに異なる実装を使用します。各サブセットは、その要素の1つ(「リーダー」)によって表されます。これにより、和集合の操作が遅くなりますが、awkの単純なデータ型を使用すると実装が簡単になります。

{
  union(find($1), find($2));
}

END {
  format_subsets();
  for(i in subsets)
    print subsets[i];
}

function find(element) {
  if (!leaders[element])
    leaders[element] = element;
  return leaders[element];
}

function union(leader_1, leader_2) {
  for(i in leaders)
    if (leaders[i] == leader_2)
      leaders[i] = leader_1;
}

function format_subsets() {
  for(element in leaders) {
    leader = leaders[element]
    subsets[leader] = (subset = subsets[leader]) ? (subset OFS element) : element;
  }
}

使用法

$ awk -f so-162730.awk < input.txt
A B C D E
F G

または、空白またはカンマ区切りの入力の場合:

$ awk -f so-162730.awk -F '[[:space:]]+|,' input.txt
A B C D E
F G
2
artm