Python:树结构和数字代码?

Posted

技术标签:

【中文标题】Python:树结构和数字代码?【英文标题】:Python: tree structure and numerical codes? 【发布时间】:2010-09-20 16:58:25 【问题描述】:

我正在使用 Python,我想将一些数据放入树格式并为其分配代码。以下是一些示例数据:

Africa    North Africa    Algeria
Africa    North Africa    Morocco
Africa    West Africa     Ghana
Africa    West Africa     Sierra Leone

什么是适合该数据的树结构?

另外,有没有一种方法可以让我从这个树结构中检索数字代码,这样我就可以查询数据并获取如下示例的代码?

def get_code(place_name):
    # Python magic query to my tree structure
    return code
get_code("Africa") # returns 1
get_code("North Africa") # returns 1.1
get_code("Morocco") # returns 1.1.2

感谢您的帮助 - 关于 Python,我还有很多东西要学习 :)

【问题讨论】:

我们对 python 的了解也很多。:) 【参考方案1】:

我建议,假设您可以指望名称之间没有重复,例如:

class Node(object):
    byname = 

    def __init__(self, name, parent=None):
        self.name = name
        self.parent = parent
        self.children = []
        self.byname[name] = self
        if parent is None:  # root pseudo-node
            self.code = 0
        else:  # all normal nodes
            self.parent.children.append(self)
            self.code = len(self.parent.children)

    def get_codes(self, codelist):
        if self.code:
            codelist.append(str(self.code))
            self.parent.get_codes(codelist)

root = Node('')

def get_code(nodename):
    node = Node.byname.get(nodename)
    if node is None: return ''
    codes = []
    node.get_codes(codes)
    codes.reverse()
    return '.'.join(codes)

您是否还想查看 Python 代码,了解如何在给定名称的分层序列(例如 ['Africa', 'North Africa', 'Morocco'])的情况下添加节点?我希望考虑到上述结构,它会很清楚,所以你可能想自己做一个练习,但当然要问你是否更愿意看到一个解决方案;-)。

从文本行(字符串)中获取名称的分层序列取决于分隔符是什么——在您的示例中,它看起来只是出于与排列列相关的纯粹审美原因而添加的一堆空格(如果这是在这种情况下,我会推荐一个简单的基于re 的方法来拆分两个+ 空格的序列),但如果它实际上是(例如)制表符作为分隔符,Python 标准库中的csv 模块会更好地为您服务。我只是无法从您在 Q 中发布的简短示例中看出!-)

编辑:OP 说他们可以很好地获取名称序列,但希望查看添加相关节点的代码 - 所以,这里开始!-)

def addnodes(names):
    parent = root
    for name in names:
        newnode = Node.byname.get(name)
        if newnode is None:
            newnode = Node(name, parent)
        parent = newnode

了解为什么节点名称是唯一的对于使上述类正常工作很重要?由于Node.byname 是每个类的单个dict,它只能为每个给定名称记录一个“对应节点”——因此,在层次结构中的两个或多个位置重复的名称将“冲突”并且只有一个两个或多个节点中的一个将被正确记录。

但是话又说回来,OP 所说的函数get_code 是如果名称可能模棱两可,整个设备无法按预期工作的主要原因,因为 OP 的规范要求它只返回 one 字符串。所以,一些地理列表,如

America   United States    Georgia
Europe    Eastern Europe   Georgia

(其中两个完全不相关的区域恰好都被命名为'Georgia'——不幸的是,这种事情在现实世界的地理中经常发生,如上例所示!-)会破坏整个方案(取决于 get_code 的规范如何更改以处理不明确的名称参数,当然,类结构肯定可以相应地更改并适应新的、截然不同的规范!)。

将这些设计决策封装在一个类中的好处(尽管在这种情况下有几个附带的函数——当然,它们可以优雅地制成类方法,但是 OP 的规范严格要求 get_code是一个函数,所以我决定,在这种情况下,addnodes 也可能是一个!-) 是具体的设计决策大多隐藏在其余代码中,因此可以很容易地改变(只要规范当然,永远不要改变——这就是为什么花时间和注意力来定义一个人的 API 规范如此重要的原因,比设计和编码的任何其他部分更重要!-)重构内部行为(例如用于优化、易于调试/测试等),同时保持 API 指定的语义完整,从而使应用程序的所有其他部分保持原始状态(实际上甚至不需要重新测试,当然只要实现的部分API 经过了非常彻底的单元测试——不难做到,因为它们很好地隔离和独立!-)。

【讨论】:

re的使用有必要吗? csv 可以用skipinitialspace 处理多个空格,但更简单:''.split 可以很好地分割。 这看起来很棒,谢谢! :) 是的,请您举例说明如何在给定列表的情况下添加节点?我可以很好地从字符串中获取列表,但我正在为如何将它变成一个节点而摸不着头脑。 @AP257: 提示:上面的构造函数__init__parent 作为参数。所以你可能想要构造父节点,然后是它的子节点,依此类推。 @Muhammad,写下您的第一条评论:按空格、任意空格序列或什至在带有跳过初始空格的 CSV 中分割,例如将WestAfrica 制作成单独的 字符串——难道你不明白OP 打算将'West Africa' 作为一个节点名称是多么彻底的灾难吗?!跨度> @Alex:是的,我错过了 2+ 个空格。如果简单的字符串方法有效,我将避免使用re,但这一次我被误导了。对不起! 躲在岩石下【参考方案2】:

一个表示树的临时 POD(“普通旧数据”)类就可以了,类似于:

class Location(object):
  def __init__(self, data, parent)
    self.data = data
    self.parent = parent
    self.children = []

现在分配/读取data 属性,或添加/删除子项,可能使用辅助方法:

def add_child(self, child):
  self.children.append(child)

现在,要将您的数据实际划分为树级别,一个简单的算法是查看所有具有共同级别数据的地方(例如非洲)并为其分配一个位置,然后递归地获取下一个级别的数据。

因此,对于非洲,您创建一个数据 = 非洲的位置。然后,它将有一个北非、西非等的 Location 子节点。

对于“获取代码”,有一个字典将每个国家/地区映射到其位置节点,并使用节点中的父链接。在每个级别从节点遍历到顶部(直到父节点为无),将代码部分分配为父节点的子节点列表中的索引。

【讨论】:

【参考方案3】:

我不确定,如果我做对了。如果我们将每个对象都保存在一个全局字典中,那么它就违背了使用树的目的,树仅用于构建编号方案。 但是基于树的表示看起来像这样:

class Location(object):

    allLocation = 

    def __init__(self, name):
        self.name = name
        self.parent = None
        self.number = "0"
        self.children = 

    def putChild(self, childLocation):
        if childLocation.name not in self.allLocation.keys():
            # Now adjust the number scheme
            #if self.number is "0":
            # this is root
            numScheme = str(len(self.children) + 1)

            childLocation.setNumber(numScheme)

            # Add the child
            self.children[childLocation.number] = childLocation
            self.allLocation[childLocation.name] = childLocation
            childLocation.parent = self
            return 0
        else:
            return 1 # Location already a child of the current clocation

    def setNumber(self, num):
        if self.number is not "0":
            # raise an exception, number already adjusted
            pass
        else:
            # set the number 
            self.number = num


    def locateChild(self, numScheme):
        # Logic should be to break up the numScheme and pass the token successively 
        numSchemeList = []

        if type(numScheme) is str:
            numSchemeList = numScheme.split(".")
        else:
            numSchemeList = numScheme


        if len(numSchemeList) >= 1:
            k = numSchemeList.pop()
            # if the child is available 

            if k in self.children.keys():
                childReferenced = self.children[k]


                # Is child of child required
                if len(numSchemeList) >= 1:
                    return childReferenced.locateChild(numSchemeList)
                else:
                    return childReferenced
            else:
                # No such child
                return None
        else:
            # The list is empty , search ends here
            return None

    def getScheme(self, name):
        if name in self.allLocation.keys():
            locObj = self.allLocation[name]
            return locObj.getNumScheme(name, "")
        else:
            return None

    def getNumScheme(self, name, numScheme="0",):
        if not self.parent:
            return numScheme
        if numScheme != "":
            return self.parent.getNumScheme(name, self.number + "." + numScheme)
        else:
            return self.parent.getNumScheme(name, self.number )



root = Location("root")
africa = Location("Africa")
asia = Location("Asia")
america = Location("America")
root.putChild(africa)
root.putChild(asia)
root.putChild(america)

nafrica = Location("North Africa")
africa.putChild(nafrica)

nafrica.putChild(Location("Morrocco"))

obj = root.locateChild("1.1.1")
print obj.name

print root.getScheme("Morrocco")

【讨论】:

【参考方案4】:

这段代码可能很可怕。但是,我只是想粘贴它,因为我已经花了一些时间:)

tree = file_to_list_of_tuples(thefile)
d = 
i = 1
for continent, region, country in tree:
   if continent not in d:
      d[continent] = [i, 0, 0]
      i += 1
   cont_code = d[continent][0]
   if region not in d:
      max_reg_code =  max( [y for x, y, z in d.values() if x==cont_code] )
      d[region] = [cont_code, max_reg_code+1 , 0]
   reg_code = d[region][1]
   if country not in d:
      max_country_code = max( [z for x, y, z in d.values() if x == cont_code and y== reg_code] )
      d[country] = [cont_code, reg_code, max_country_code+1]

def get_code(x):
   print d[x]

get_code 将打印列表,但您可以轻松地将它们以您想要的格式打印。

【讨论】:

【参考方案5】:

你可以使用itertree包(我是作者):

from itertree import *
        
#1. create the tree:
root2=iTree('root')
root2.append(iTree('Africa'))
root2[0].append(iTree('North Africa'))
root2[0].append(iTree('West Africa'))
root2[0][0].append(iTree('Algeria'))
root2[0][0].append(iTree('Morocco'))
item=iTree('Ghana') # keep the item for easier access
root2[0][1].append(item)
root2[0][1].append(iTree('Sierra Leone'))
        
# get the index path information of an item:
print('"Ghana" item index path:',item.idx_path)
        
# you can also search for items:
result = root2.find(['**', 'Morroco'])
print('"Morroco" item index path:', result.idx_path)

执行脚本将交付:

"Ghana" item index path: [0, 1, 0]
"Morroco" item index path [0, 0, 1]

除此之外,您可以为每个项目添加额外的数据并进行过滤搜索。

【讨论】:

以上是关于Python:树结构和数字代码?的主要内容,如果未能解决你的问题,请参考以下文章

天天数据结构和算法PHP中trie数据结构的使用场景和代码实例

python环境下使用mysql数据及数据结构和二叉树算法(图)

数据结构

数据结构与算法(Python)——常见数据结构Part5(二叉搜索树BST和AVL)

数据结构与算法(Python)——常见数据结构Part5(二叉搜索树BST和AVL)

数据结构之主席树