Ruby解析输入文件并放入哈希

时间:2018-05-01 03:41:14

标签: ruby algorithm file hash nodes

我有一个文件来表示图形中节点的邻接列表,作为我需要解析的文本文件。第一行是节点总数。第二行是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

第1行:图表共有7个节点 line2:Node1连接到Node2,Node3 line3:Node2连接到Node1,Node3,Node4,Node5,Node7。

-1是没用的。

这是我目前的ruby实现。我正试图找到一种方法来设置它

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')

我的节点类有一个adjacency_nodes数组,我可以将node2和node3压入它。例如:node1.adjancency_nodes<<节点2

class Node
  attr_accessor :id, :state, :adjacent_nodes, :graph

  def initialize(id)
    @id = id
    @adjacent_nodes = []
  end

  def to_s
    "node #{@id}"
  end
end

循环遍历此文本文件,创建新节点并将其存储在散列中以及推送其所有邻接节点的最简洁方法是什么?

3 个答案:

答案 0 :(得分:2)

系统调用的使用是奇怪的;你真的不需要它来获得文件中的第一行。

第一行代表节点数。

后面的每一行代表给定节点的相邻节点。行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

答案 1 :(得分:2)

有人可能会利用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]>"]}>

答案 2 :(得分:0)

这有一个家庭作业问题的所有标志出错,但我会尽力帮助。

  

我的节点类有一个adjacency_nodes数组,我可以将node2和node3压入它。例如:node1.adjancency_nodes&lt;&lt;节点2

是否要将节点ID推送到阵列或节点引用本身?

  

# how come node_hash[Node.new(line_num)] = line does not work?

你是什么意思“它不起作用?”它不会在您的哈希中添加该行吗?

您正在构建一个散列,其中键是节点引用,值是相邻节点。您实际上并没有修改每个节点的adjacent_nodes属性。那是你想做的吗?此外,如果您有两行引用相同的节点ID,例如2,您要将该节点实例化两次,例如Node.new(2)将被调用两次。那是你想要做的吗?

看看你写的内容,我注意到了一些事情:

  • 当你真的只想在字符串末尾String#strip换行时,你正在使用String#chomp
  • 您忽略了文件顶部的有价值信息,即节点总数。
  • 更糟糕的是,你正在调用一个shell命令来获取那些信息(只是在Ruby中执行!)而你做错了:你在计算包含节点数的行作为一个节点定义,因此您的total_nodes变量设置为8(而不是7)。
  • 您忽略了每行末尾的有价值信息。是的,-1终结器有点奇怪,但您可以使用它来确定何时停止处理该行。我估计你的教授计划发送一些错误的输入,如2 3 4 -1 5,看看你的代码是否破裂。在这种情况下,您的代码应该只考虑相邻的节点2,3和4。
  • 在所有情况下,您都没有将包含数值的字符串转换为正确的整数。
  • 您希望parse_file方法返回什么?它可能不会返回您认为它正在返回的内容,因为它是目前编写的。

考虑到这一点,让我们做一些改变:

  • 让我们读取输入的第一行以确定节点的总数。
  • 让我们预先分配我们的节点,这样我们只需要实例化每个节点一次。
  • 让我们使用一个数组(带有从零开始的索引)来保存我们的节点引用。在实例化节点并对阵列执行查找时(基于一个节点ID),我们必须牢记这一点。
  • 当我们看到无效的节点ID时,让我们停止处理相邻的节点。
  • 让我们对所有输入使用String#chompString#to_i
  • 让我们实际附加相邻节点......
  • 让我们返回方法末尾的nodes数组,以便调用者返回一些有用的东西。
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