こんにちは。
以下のような感じで、いかがでしょうか?
ruby
1# coding: utf-8
2
3sample_hash =
4 {"nest1-1" => "value1-1",
5 "nest1-2" =>
6 {
7 "nest2-1" => {
8 "nest3-1a" => "26513",
9 "nest3-1b" => "3"
10 },
11 "nest2-2" => {
12 "nest3-2a" => "317829",
13 "nest3-2b" => "50"
14 }
15 }
16 }
17
18def recursive_traverse(h, paths, pref='')
19 h.each do |k, v|
20 if v.is_a? String
21 paths["#{pref}#{k}"] = v
22 elsif v.is_a? Hash
23 recursive_traverse(v, paths, "#{pref}#{k}/")
24 end
25 end
26end
27
28paths = {}
29
30recursive_traverse(sample_hash, paths)
31
32paths.each do |k, v|
33 puts "#{k}: #{v}"
34end
35
36
37
上記を実行すると、以下が出力されます。
nest1-1: value1-1
nest1-2/nest2-1/nest3-1a: 26513
nest1-2/nest2-1/nest3-1b: 3
nest1-2/nest2-2/nest3-2a: 317829
nest1-2/nest2-2/nest3-2b: 50
参考になりましたら幸いです。
追記
以下のような別解を考えました。
(ただし、質問で問われている本題から離れてしまうと思うので、あくまで参考に留めて頂ければと思います)
所与のハッシュsample_hash
は、構造的に次のXMLと等価です。
xml
1<?xml version="1.0" encoding="UTF-8"?>
2<root>
3 <nest1-1>value1-1</nest1-1>
4 <nest1-2>
5 <nest2-1>
6 <nest3-1a>26513</nest3-1a>
7 <nest3-1b>3</nest3-1b>
8 </nest2-1>
9 <nest2-2>
10 <nest3-2a>317829</nest3-2a>
11 <nest3-2b>50</nest3-2b>
12 </nest2-2>
13 </nest1-2>
14</root>
ただしXMLにするためには、最上位のノードは1つなので、上記では
便宜的に <root>
要素をトップに追加しています。
XMLを扱う問題に置き換えれば、value1-1
や 26513
は XMLのテキストノードで、
これらはXPath で //text()
という指定でまとめて取得できます。
あとは、テキストノードのそれぞれについて、XPathを逆に求めれば、
テキストノードとそれに至るXPathの組を得ることができます。
この考えで作成したスクリプトが以下です。
ruby
1# coding: utf-8
2
3require 'nokogiri'
4
5sample_hash =
6 {'nest1-1' => 'value1-1',
7 'nest1-2' =>
8 {
9 'nest2-1' => {
10 'nest3-1a' => '26513',
11 'nest3-1b' => '3'
12 },
13 'nest2-2' => {
14 'nest3-2a' => '317829',
15 'nest3-2b' => '50'
16 }
17 }
18 }
19
20class Hash
21 def to_xml
22 map do |k, v|
23 text = Hash === v ? v.to_xml : v
24 '<%s>%s</%s>' % [k, text, k]
25 end.join
26 end
27end
28
29xml ='<?xml version="1.0" encoding="UTF-8"?><root>%s</root>' % [sample_hash.to_xml]
30
31Nokogiri::XML(xml).xpath('//text()').each do |item|
32 xpath = Nokogiri::CSS.xpath_for item.css_path
33 puts '%s=%s' % [xpath[0], item]
34end
上記では、XMLをパース(解析)するために、nokogiri
を使っており、
このスクリプトを実行すると以下が表示されます。
//root/nest1-1/child::text()=value1-1
//root/nest1-2/nest2-1/nest3-1a/child::text()=26513
//root/nest1-2/nest2-1/nest3-1b/child::text()=3
//root/nest1-2/nest2-2/nest3-2a/child::text()=317829
//root/nest1-2/nest2-2/nest3-2b/child::text()=50
このように与題をXMLのテキストノードの走査と考えて、//text()
でテキストノードをまとめて
取ってこれるパーサーを使えば、自分で再帰的にテキストノードを探すメソッドを作らなくても
よくなります。
上記のコードで、Hash#to_xml
を再帰メソッドとして実装しているので
「結局手間としては同じでは?」とお思いになるかもしれませんが、
ハッシュをXMLにするメソッドも自分で書かなくても、
以下のようなgemがあります。
この別解で、何が言いたいかと言えば、与件を
「XMLのテキストノードへのパスを取得する」問題と置き換えれば、
自分で再帰メソッドを書く手間をかけなくても欲しい情報が得られるのでは?
ということでした。
ご参考になれば幸いです。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。