У меня есть файл для представления списка смежности узлов в графе в виде текстового файла, который мне нужно проанализировать. В первой строке указано общее количество узлов. Вторая строка - это node1 вместе со списком узлов, к которым он подключается (неориентированный граф). Например
7
2 3 -1
1 3 4 5 7 -1
1 2 -1
2 6 -1
2 6 -1
4 5 -1
2 -1
line1: граф имеет всего 7 узлов.
line2: Node1 подключается к Node2, Node3.
line3: Node2 подключается к Node1, Node3, Node4, Node5, Node7.
-1 вроде бесполезно.
Вот моя текущая реализация рубина. Я пытаюсь найти способ настроить это
def parse_file(filename)
total_nodes = `wc -l "#{filename}"`.strip.split(' ')[0].to_i
node_hash = Hash.new
File.foreach(filename).with_index do |line, line_num|
# convert each line into an array
line = line.strip.split(" ")
# take out weird -1 at the end of txt file in each line
line = line[0...-1]
#puts "#{line_num}: #{line}"
# how come node_hash[Node.new(line_num)] = line does not work?
node_hash[Node.new(line_num)] = line
end
end
parse_file('test_data.txt')
В моем классе node есть массив adjacency_nodes, который я могу вставить в него node2 и node3. Например: node1.adjancency_nodes << node2
class Node
attr_accessor :id, :state, :adjacent_nodes, :graph
def initialize(id)
@id = id
@adjacent_nodes = []
end
def to_s
"node #{@id}"
end
end
Какой самый чистый способ перебрать этот текстовый файл, создать новые узлы и сохранить его в хэше, а также протолкнуть все его узлы смежности?
Если вы можете изменить содержимое файла, вы можете подумать о внесении нескольких изменений. Во-первых, я предлагаю вам удалить количество узлов в первой строке, так как это можно определить путем подсчета строк. Во-вторых, начинайте каждую строку с номера узла. Это упрощает чтение файла людьми и избавляет от необходимости упорядочивать строки по номеру строки. В-третьих, для каждого узла укажите только смежные узлы с более высокими номерами. Поскольку граф неориентирован, обратные связи могут быть легко выведены. Это будет особенно полезно, если для тестирования используются файлы, созданные вручную.
Я согласен, входной файл такой странный. Мне не приходило в голову просто редактировать его прямо вместо того, чтобы использовать ruby для синтаксического анализа!
Разработчики постоянно придумывают странные форматы ввода для своих программ. Также полезна гибкость обратного проектирования и написания читателя / писателя.
Такое использование системного вызова странно; вам это действительно не нужно, чтобы получить первую строку в файле.
Первая строка представляет количество узлов.
Каждая строка после представляет собой соседние узлы для данного узла. линия n
представляет узлы для node (n-1)
.
Так что вы можете просто перейти по очереди:
def parse_file(path)
# start
f = File.open(path, 'r')
# get node count. Convert to integer
num_nodes = f.readline.to_i
# create your nodes
nodes = {}
1.upto(num_nodes) do |id|
node = Node.new(id)
nodes[id] = node
end
# join them and stuff
1.upto(num_nodes) do |id|
node = nodes[id]
# for each line, read it, strip it, then split it
tokens = f.readline.strip.split(" ")
tokens.each do |other_id|
other_id = other_id.to_i
break if other_id == -1
# grab the node object, using the ID as key
other_node = nodes[other_id]
node.adjacent_nodes << other_node
end
end
# done
f.close
end
У этого есть все признаки того, что домашнее задание пошло наперекосяк, но я постараюсь помочь.
My node class has an adjacency_nodes array that I can push node2 and node3 into it. For example: node1.adjancency_nodes << node2
Вы хотите поместить идентификатор узла в массив или саму ссылку на узел?
# how come node_hash[Node.new(line_num)] = line does not work?
Что значит «не работает»? Не добавляет ли строчку к вашему хешу?
Вы строите хэш, в котором ключи являются ссылками на узлы, а значения - смежными узлами. Фактически вы не изменяете свойство adjacent_nodes
каждого узла. Это то, что ты хочешь сделать? Кроме того, если у вас есть две строки, которые относятся к одному и тому же идентификатору узла, например 2
, вы собираетесь создать экземпляр этого узла дважды, например Node.new(2)
будет вызываться дважды. Это то, что ты хочешь сделать?
Взглянув на то, что вы написали, я заметил несколько вещей:
String#strip
, когда действительно хотите просто перенести String#chomp
на новую строку в конце строки.total_nodes
установлена на восемь (а не семь).-1
довольно странный, но вы можете использовать его, чтобы определить, когда прекратить обработку строки. Я полагаю, ваш профессор планирует послать какой-нибудь плохой ввод, например 2 3 4 -1 5
, чтобы посмотреть, не работает ли ваш код. В этом случае ваш код должен учитывать только соседние узлы 2, 3 и 4.parse_file
? Вероятно, он не возвращает то, что, по вашему мнению, возвращается в том виде, в каком он сейчас написан.Имея это в виду, давайте внесем некоторые изменения:
String#chomp
и String#to_i
для всех входов.def parse_file(filename)
# open the file in read-only mode
file = File.new(filename, 'r')
# read the first line as an integer to determine the number of nodes
num_nodes = file.readline.chomp.to_i
# preallocate our nodes so we can store adjacent node references
nodes = Array.new(num_nodes) { |i| Node.new(i + 1) }
# read the remaining lines containing node definitions
file.each_line.with_index do |line, i|
# parse the adjacent node ids as integers
line.chomp.split(' ').map(&:to_i).each do |node_id|
# a sentinel node id of -1 means stop processing
break if node_id < 0
# TODO: What's supposed to happen when the node doesn't exist?
#raise "Unknown node ID: #{node_id}" if node_id == 0 || node_id > num_nodes
# add the node reference to the list of adjacent nodes
nodes[i].adjacent_nodes << nodes[node_id - 1]
end
end
nodes
end
Спасибо. Не думаю, что мне нужно беспокоиться о делах. Предположим, что весь входной файл будет правильным. Так что нет 1 -1 4 7 или что-то в этом роде. Цель состоит в том, чтобы просто создать узлы со списком других смежных узлов. Я буду использовать его для другого раздела, чтобы построить график. Гораздо проще очистить сам исходный файл, но тогда веселее попробовать оставить его нетронутым и посмотреть, смогу ли я поработать с ним в Ruby.
Можно воспользоваться рубином, поддерживающим технически бесконечную перекрестную вложенность объектов:
class Node
attr_accessor :id, :adjacents
def initialize(id)
@id = id
@adjacents = []
end
def to_s
"<#Node #{@adjacents.map(&:id).inspect}>"
end
end
class Graph
attr_accessor :nodes
def initialize(count)
@nodes = (1..count).map(&Node.method(:new))
end
def to_s
"<#Graph nodes: {#{@nodes.map(&:to_s)}}>"
end
end
input = "7\n2 3 -1\n1 3 4 5 7 -1\n1 2 -1\n2 6 -1\n2 6 -1\n4 5 -1\n2 -1"
graph, *nodes = input.split($/)
count = graph.to_i
result =
nodes.
each.
with_index.
with_object(Graph.new(count)) do |(line, idx), graph|
graph.nodes[idx].adjacents |=
line.split.map(&:to_i).
select { |e| e >= 1 && e <= count }.
map { |e| graph.nodes[e - 1] }
end
Теперь у вас есть бесконечно вложенный граф (вы можете вызвать adjacents
на любом узле все глубже и глубже, чтобы получить правильный результат).
Структура графа верхнего уровня может быть достигнута с помощью:
puts result.to_s
#⇒ <#Graph nodes: {["<#Node [2, 3]>",
# "<#Node [1, 3, 4, 5, 7]>",
# "<#Node [1, 2]>",
# "<#Node [2, 6]>",
# "<#Node [2, 6]>",
# "<#Node [4, 5]>",
# "<#Node [2]>"]}>
Я совсем не разбираюсь в рубине, но я сделал нечто подобное, поэтому позвольте мне попробовать что-нибудь.