Ruby анализирует входной файл и помещает его в хеш

У меня есть файл для представления списка смежности узлов в графе в виде текстового файла, который мне нужно проанализировать. В первой строке указано общее количество узлов. Вторая строка - это 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

Какой самый чистый способ перебрать этот текстовый файл, создать новые узлы и сохранить его в хэше, а также протолкнуть все его узлы смежности?

Я совсем не разбираюсь в рубине, но я сделал нечто подобное, поэтому позвольте мне попробовать что-нибудь.

zenwraight 01.05.2018 05:58

Если вы можете изменить содержимое файла, вы можете подумать о внесении нескольких изменений. Во-первых, я предлагаю вам удалить количество узлов в первой строке, так как это можно определить путем подсчета строк. Во-вторых, начинайте каждую строку с номера узла. Это упрощает чтение файла людьми и избавляет от необходимости упорядочивать строки по номеру строки. В-третьих, для каждого узла укажите только смежные узлы с более высокими номерами. Поскольку граф неориентирован, обратные связи могут быть легко выведены. Это будет особенно полезно, если для тестирования используются файлы, созданные вручную.

Cary Swoveland 01.05.2018 09:02

Я согласен, входной файл такой странный. Мне не приходило в голову просто редактировать его прямо вместо того, чтобы использовать ruby ​​для синтаксического анализа!

the_answer_is_xyz 01.05.2018 16:12

Разработчики постоянно придумывают странные форматы ввода для своих программ. Также полезна гибкость обратного проектирования и написания читателя / писателя.

MxLDevs 01.05.2018 17:09
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
0
4
420
3

Ответы 3

Такое использование системного вызова странно; вам это действительно не нужно, чтобы получить первую строку в файле.

Первая строка представляет количество узлов.

Каждая строка после представляет собой соседние узлы для данного узла. линия 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 на новую строку в конце строки.
  • Вы игнорируете ценную информацию в верхней части файла, то есть общее количество узлов.
  • Хуже того, вы вызываете команду оболочки, чтобы получить эту информацию (просто сделайте это в Ruby!), И делаете это неправильно: вы считаете строку, содержащую количество узлов, как определение узла, так что ваша переменная 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.

the_answer_is_xyz 01.05.2018 16:17

Можно воспользоваться рубином, поддерживающим технически бесконечную перекрестную вложенность объектов:

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]>"]}>

Другие вопросы по теме