软工计算1笔记 大一下第八周 20240415

1.Mutation Operations

在Python中,listdictionary 是可变类型(mutable types),这意味着它们的内容可以在不改变其对象身份的情况下被修改。这与不可变类型(immutable types),如字符串(str)和元组(tuple),形成对比,后者一旦创建,其内容就不能改变。

可变类型(Mutable Types):

  1. 列表(List)list 是一个有序的元素集合,可以包含不同类型的元素。列表的内容可以通过索引来修改,也可以通过添加或删除元素来改变列表的大小。

    示例:

    my_list = [1, 2, 3]
    my_list[1] = 5  # 修改索引为1的元素,现在列表变为 [1, 5, 3]
    my_list.append(4)  # 添加一个元素到列表末尾,现在列表变为 [1, 5, 3, 4]
    del my_list[0]  # 删除索引为0的元素,现在列表变为 [5, 3, 4]
    
  2. 字典(Dictionary)dictionary 是一个无序的键值对集合。字典的键必须是不可变类型,而值可以是任何类型。字典可以通过添加、删除或修改键值对来改变其内容。

    示例:

    my_dict = {'a': 1, 'b': 2}
    my_dict['a'] = 3  # 修改键'a'对应的值,现在字典变为 {'a': 3, 'b': 2}
    my_dict['c'] = 4  # 添加一个新的键值对,现在字典变为 {'a': 3, 'b': 2, 'c': 4}
    del my_dict['b']  # 删除键'b'及其对应的值,现在字典变为 {'a': 3, 'c': 4}
    

函数传参与可变类型:

当你将一个可变类型的对象(如列表或字典)作为参数传递给函数时,你实际上是传递了对象的引用。这意味着函数内部对对象所做的任何修改都会反映到原始对象上。这种行为类似于C++中的引用传参,但请注意,Python中的所有参数传递都是通过引用完成的,无论是可变类型还是不可变类型。

示例:

def modify_list(lst):
    lst.append(4)

def modify_dict(dct):
    dct['new_key'] = 'new_value'

my_list = [1, 2, 3]
modify_list(my_list)
print(my_list)  # 输出: [1, 2, 3, 4]

my_dict = {'a': 1, 'b': 2}
modify_dict(my_dict)
print(my_dict)  # 输出: {'a': 1, 'b': 2, 'new_key': 'new_value'}

在上面的例子中,modify_listmodify_dict 函数分别修改了传入的列表和字典。由于列表和字典是可变的,所以这些修改在函数外部也是可见的。

传递可变对象的注意事项:

虽然Python中的参数传递总是通过引用完成的,但你可以通过传递不可变类型的对象或使用参数的默认值来避免在函数内部对原始对象进行修改。例如:

def safe_modify_list(lst):
    new_lst = lst[:]  # 创建原始列表的浅拷贝
    new_lst.append(4)
    return new_lst

my_list = [1, 2, 3]
modified_list = safe_modify_list(my_list)
print(my_list)  # 输出: [1, 2, 3]
print(modified_list)  # 输出: [1, 2, 3, 4]

在这个例子中,safe_modify_list 函数通过创建原始列表的一个浅拷贝来避免直接修改原始列表。这样,即使函数内部对拷贝进行了修改,原始列表也不会受到影响。这种方法在需要保护原始数据不被改变时非常有用。

2.python中max函数和key参数的结合使用

在Python中,max 函数可以与 key 参数结合使用,以根据特定的函数来确定序列中的最大项。key 参数接受一个函数,这个函数会在序列的每个元素上调用,并根据返回值来确定最大项。这种方式允许我们根据复杂的标准来找出最大值。

在你给出的例子中:

max(range(10), key=lambda x: 7-(x-4)*(x-2))

这行代码做了以下几件事:

  1. range(10) 生成一个从 0 到 9 的整数序列。

  2. max 函数找出这个序列中的最大项。

  3. key 参数接受一个 lambda 匿名函数,这个函数定义了一个关于 x 的数学表达式 7-(x-4)*(x-2)。这个表达式实际上是一个二次函数,它可以被重写为 a*x^2 + bx + c 的形式,其中 a = -1b = 8c = -7

  4. lambda x: 7-(x-4)*(x-2) 这个函数被应用到 range(10) 中的每个元素上。对于每个元素 x,它计算表达式的值。

  5. max 函数比较所有通过 key 函数计算得到的值,并找出最大的那个。

在这个特定的例子中,二次函数 7-(x-4)*(x-2)x=4 时取得最大值,因为在 x=4 时,表达式变为 7 - (0)*(0) = 7。由于 range(10) 包含数字 4,所以 max 函数返回 4,并且对应的 key 函数的值为 7。

如果你想找出一个范围内所有数的某个函数式子最大的那一个数,你可以使用类似的方法。下面是一个例子,我们找出 0 到 10 之间使得函数 f(x) = (x-3)^2 + 1 取得最小值的 x

# 定义函数式子
def f(x):
    return (x-3)**2 + 1

# 使用min和range找到使得f(x)最小的x
min_x = min(range(11), key=f)
print(min_x)  # 输出: 3

在这个例子中,我们定义了一个函数 f(x),它是一个关于 x 的二次函数。我们使用 min 函数和 range 函数来找到 f(x) 最小值对应的 xmin 函数的 key 参数接受一个函数,这个函数被应用到 range 生成的每个元素上。min 函数比较所有这些函数值,并找出最小的那个。在这个例子中,f(x)x=3 时取得最小值,因为 (3-3)^2 + 1 = 1。所以 min_x 的值是 3。

通过这种方式,你可以使用 maxmin 函数结合 key 参数来找出一个范围内使得某个函数取得最大值或最小值的元素。这种方法在数据分析、优化问题和其他需要基于特定标准进行比较的场景中非常有用。

3.同一性VS相等性

a is b # identity,是否bound到相同的元素(类似于C中的指针指向的地址相同)
a == b # Equality,值是否相同

a="we"
b="we"
print(a is b) 输出true,

a,b为100,上面那个输出true,为500则输出false

在Python中,字符串是不可变的对象,当两个变量指向相同的字符串值时,它们被认为是相等的。在这个例子中,变量 ab 都指向了字符串 "we"。当你使用 is 关键字比较这两个变量时,Python检查它们是否指向内存中的同一个对象。

由于 "we""we" 是相同的字符串字面量,Python可能会优化内存使用,使得多个变量指向相同的字符串对象。因此,a is b 返回 True,表示 ab 确实指向了内存中的同一个对象。

这种行为是Python实现细节的一部分,通常在Python 3中,小的字符串字面量会被缓存并重用,以节省内存。然而,这不是一个保证的行为,它依赖于Python的实现和版本。在某些情况下,尤其是对于较大的字符串或动态创建的字符串,Python可能会创建新的对象,这时 a is b 可能会返回 False。但是,对于小的、相同的字符串字面量,a is b 通常会返回 True

在Python 3中,整数(int)是不可变的,并且对于小范围的整数,Python实现了一种称为“小整数缓存”(small integer caching)的优化机制。这意味着,对于一定范围内的整数,Python可能会重用相同的对象,以提高效率和节省内存。

对于大多数实现,这个范围通常包括从 -5 到 256(包含两端),但这个范围可能因Python实现和版本而异。在这个范围内的整数会被缓存,所以即使创建了两个看似独立的变量,它们也可能指向内存中的同一个对象。

在这个例子中:

a = 100
b = 100
print(a is b)
由于 100 在小整数的范围内,Python 可能会重用相同的对象。因此,在大多数Python 3实现中,print(a is b) 的输出很可能是 True,表示 a 和 b 指向同一个整数对象。

然而,需要注意的是,依赖于这种小整数缓存行为是不安全的,因为它不是Python语言规范的一部分,而是一种实现细节。不同的Python实现或不同版本的Python可能会有不同的缓存策略。因此,即使在这个例子中 a is b 返回 True,也不应该基于这种优化编写代码,而应该假设每个整数都是独立的对象。

4.global vs nonlocal

在Python中,global 关键字用于在函数内部声明一个变量为全局变量。这意味着,当你在函数内部使用 global 关键字时,你告诉Python你将操作一个已经存在于全局作用域中的变量,而不是在函数内部创建一个新的局部变量。

使用 global 关键字的场景通常包括以下几种:

  1. 当你需要在函数内部修改一个全局变量的值时。
  2. 当你需要在函数内部引用一个全局变量,并确保不会创建一个同名的局部变量时。

下面是一个使用 global 关键字的例子:

# 定义一个全局变量
global_variable = 10

def modify_global_variable():
    # 声明要修改的是全局变量
    global global_variable
    # 修改全局变量的值
    global_variable = 20
    print("Inside function:", global_variable)

# 打印全局变量的初始值
print("Before modification:", global_variable)

# 调用函数,修改全局变量
modify_global_variable()

# 打印修改后的全局变量的值
print("After modification:", global_variable)

输出将会是:

Before modification: 10
Inside function: 20
After modification: 20

在这个例子中,我们首先在全局作用域中定义了一个变量 global_variable,并赋值为 10。然后,我们定义了一个函数 modify_global_variable,它使用 global 关键字来声明 global_variable 是一个全局变量,并将其值修改为 20。在函数外部,我们打印全局变量的值,调用函数修改它,然后再次打印修改后的值。由于我们使用了 global 关键字,函数内部的修改反映到了全局作用域中的变量上。

如果你在函数内部尝试修改一个没有使用 global 关键字声明的全局变量,Python将会创建一个新的局部变量,而不是修改全局变量。例如:

global_variable = 10

def modify_variable():
    # 没有使用 global 关键字,将创建一个新的局部变量
    global_variable = 20
    print("Inside function:", global_variable)

print("Before modification:", global_variable)
modify_variable()
print("After modification:", global_variable)

这将输出:

Before modification: 10
Inside function: 20
After modification: 10

在这个例子中,由于没有使用 global 关键字,modify_variable 函数内部的 global_variable 实际上是一个局部变量,它遮蔽了全局变量。因此,全局变量的值没有被修改,函数外部的打印语句仍然显示原始值 10

在Python中,nonlocal 关键字用于在嵌套函数中声明一个变量,使得该变量在外围函数的作用域中,但不在内嵌函数的局部作用域中。这允许内嵌函数访问和修改外围函数的局部变量,但不是通过局部作用域。

nonlocal 关键字通常与 global 关键字一起使用,但它们有不同的用途:

  • global 关键字用于声明一个变量是全局变量,即它存在于最外层的作用域中。
  • nonlocal 关键字用于声明一个变量是外围函数的局部变量,但不是属于当前内嵌函数的局部变量。

下面是一个使用 nonlocal 关键字的例子:

def outer_function():
    # 定义一个外围函数的局部变量
    outer_variable = 10
    # 定义一个内嵌函数
    def inner_function():
        # 使用 nonlocal 声明变量,使其在外围函数的作用域中
        nonlocal outer_variable
        # 修改外围函数的局部变量
        outer_variable += 5
        print("Inside inner function:", outer_variable)

    # 调用内嵌函数
    inner_function()
    # 打印外围函数的局部变量,确认它也被修改了
    print("Outside inner function:", outer_variable)

# 调用外围函数
outer_function()

输出将会是:

Inside inner function: 15
Outside inner function: 15

在这个例子中,outer_function 是一个外围函数,它定义了一个局部变量 outer_variableinner_function 是一个内嵌函数,它使用 nonlocal 关键字来声明 outer_variable,并修改它的值。由于 outer_variable 是在 outer_function 的作用域中定义的,所以 inner_function 能够访问并修改它。当我们在 inner_function 外部打印 outer_variable 时,我们可以看到它的值也被修改了。

global 关键字比较:

def outer_function():
    # 定义一个外围函数的局部变量
    outer_variable = 10
    # 定义一个内嵌函数
    def inner_function():
        # 使用 global 声明变量,使其成为全局变量
        global outer_variable
        # 这将创建一个新的全局变量,而不是修改外围函数的局部变量
        outer_variable = 20
        print("Inside inner function:", outer_variable)

    # 调用内嵌函数
    inner_function()
    # 打印外围函数的局部变量,确认它没有被修改
    print("Outside inner function:", outer_variable)

# 调用外围函数
outer_function()

输出将会是:

Inside inner function: 20
Outside inner function: 10

在这个例子中,inner_function 使用 global 关键字来声明 outer_variable 为全局变量。然而,由于 outer_variable 已经作为局部变量在 outer_function 中定义,global 关键字在这里实际上没有效果。相反,它创建了一个新的局部变量 outer_variable 并将其值设置为 20。这意味着 inner_function 内部的修改不影响 outer_function 的局部变量。

总结来说,nonlocal 关键字用于修改嵌套函数外部的局部变量,而 global 关键字用于操作全局变量。在使用 global 时,需要特别注意,因为它可能会导致意外的行为,尤其是当全局变量和局部变量同名时。在可能的情况下,使用 nonlocal 通常是更好的选择,因为它的意图更明确,也更不容易出错。

函数式编程 vs 命令式编程(可变式的思想)

函数式编程(Functional Programming, FP)是一种编程范式,它将计算视为数学函数的求值,并避免使用程序状态以及易变对象。函数式编程的核心思想包括:

  1. 不可变性(Immutability):在函数式编程中,数据是不变的。一旦创建了一个数据结构,就不能再改变它。所有的操作都会产生新的数据结构。

  2. 纯函数(Pure Functions):纯函数是函数式编程的基石。它们没有副作用,只依赖于输入参数计算输出结果,相同的输入总是产生相同的输出。

  3. 高阶函数(Higher-Order Functions):高阶函数是那些接受函数作为参数或返回函数的函数。这允许函数组合、递归和抽象,是函数式编程的重要特性。

  4. 递归(Recursion):由于函数式编程避免使用循环和可变状态,递归成为实现迭代的主要方法。

  5. 函数组合(Function Composition):函数组合是将多个函数组合成一个新函数的过程。新函数将一个函数的输出作为另一个函数的输入。

下面是一个简单的Python例子,说明函数式编程的思想:

# 纯函数示例:计算两个数的和
def add(a, b):
    return a + b

# 函数组合示例:创建一个新函数,它先乘两个数,然后加一个数
def add_and_multiply(a, b, c):
    return add(a * b, c)

# 使用纯函数和函数组合
result = add_and_multiply(2, 3, 4)  # 结果是 10 + 12 = 22

在这个例子中,add 函数是一个纯函数,它接受两个参数并返回它们的和。add_and_multiply 函数是一个高阶函数,它组合了 add 函数和乘法操作。我们没有使用任何可变状态或副作用,所有的计算都是确定性的。

与函数式编程相对的是命令式编程(Imperative Programming),它侧重于描述如何通过操作数据和状态来执行计算。命令式编程通常使用循环、条件语句和可变数据结构。

下面是命令式编程的一个例子:

# 命令式编程示例:计算列表中所有数的和
def imperative_sum(numbers):
    total = 0  # 可变状态
    for number in numbers:
        total += number  # 改变状态
    return total

# 使用命令式编程
result = imperative_sum([1, 2, 3, 4])  # 结果是 10

在这个例子中,我们使用了一个可变变量 total 来累积列表中所有数的和。这个函数有副作用,因为它改变了 total 的值。

总结来说,函数式编程强调使用不可变数据、纯函数、高阶函数和递归来构建程序,而命令式编程侧重于使用可变状态和控制结构来描述计算过程。函数式编程通常能够提供更清晰、更易于测试和维护的代码,特别是在处理并发和并行计算时。然而,命令式编程在某些情况下可能更直观、更易于理解,尤其是在处理需要频繁改变状态的问题时。在实际编程实践中,两种范式往往结合使用,以发挥各自的优势。

5.构造子,选择子与python程序设计

在编程语言中,构造子(Constructor)和选择子(Selector)是面向对象编程中的两个概念。在Python中,这些概念可能不像在其他语言(如C++或Java)中那样明显,但我们仍然可以通过Python的特性来理解它们。

构造子(Constructor)

在Python中,构造子通常指的是__init__方法。这个方法在创建类的新实例时被自动调用,用于初始化对象的状态。下面是一个简单的例子:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 创建Person类的实例
person1 = Person("Alice", 30)

# 打印初始化后的属性
print(person1.name)  # 输出: Alice
print(person1.age)   # 输出: 30

在这个例子中,Person类有一个构造子__init__,它接受三个参数:selfnameage。当我们创建Person类的实例person1时,我们传递了"Alice"和30作为参数。构造子使用这些值来初始化对象的nameage属性。

选择子(Selector)

选择子通常指的是访问对象属性或方法的机制。在Python中,这通常是通过属性访问语法(如object.property)或方法调用语法(如object.method())来实现的。选择子不一定是一个特定的函数或方法,而是指向对象内部数据的引用。

下面是一个使用选择子的例子:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def greet(self):
        return f"Hello, my name is {self.name} and I am {self.age} years old."

person2 = Person("Bob", 25)

# 使用选择子访问属性
print(person2.name)  # 输出: Bob

# 使用选择子调用方法
greeting = person2.greet()  # 输出: Hello, my name is Bob and I am 25 years old.
print(greeting)

在这个例子中,我们使用person2.name这样的选择子来访问Person实例person2name属性。我们也通过person2.greet()这样的选择子来调用Person实例的方法greet

在Python中,选择子的使用非常自然和直接,因为我们通常直接通过属性名或方法名来访问对象的内部数据。而在其他一些语言中,选择子可能是通过特定的访问器(Accessor)和修改器(Mutator)方法来实现的,这些方法专门用于获取(读取)和设置(写入)对象的属性值。

6.用强强列表储存树结构

在Python中,我们可以使用列表(list)来表示树结构,其中列表的每个元素代表一个节点,而节点的子节点则以嵌套列表的形式存储。这种方法称为“强强列表”(strongly typed list),因为它将数据和结构紧密地结合在一起。下面是一个使用强强列表表示树结构的例子和解释:

# 定义一个简单的树结构
tree = [      # 根节点
    'root',   # 根节点的值
    [
        'child1',  # 子节点1的值
        [],        # 子节点1没有子节点,因此这里是空列表
        ['subchild1', 'subchild2']  # 子节点1的子节点
    ],
    [
        'child2',  # 子节点2的值
        [],        # 子节点2没有子节点,因此这里是空列表
        ['subchild3']  # 子节点2的子节点
    ]
]

# 遍历树结构
def traverse_tree(tree, node_index=0):
    current_node = tree[node_index]
    print(current_node)  # 打印当前节点的值

    # 如果当前节点有子节点,遍历子节点
    if node_index + 1 < len(tree):
        for subnode_index in tree[node_index + 1]:
            traverse_tree(tree, subnode_index + 1 + len(tree) - len(tree[node_index + 1]))

在这个例子中,我们定义了一个简单的树结构,其中根节点有两个子节点,每个子节点可以有它们自己的子节点。树结构存储在一个名为 tree 的列表中,列表的第0个元素是根节点,后面每个元素是一个子树,也是一个列表。

traverse_tree 函数是一个递归函数,用于遍历树结构。它接受两个参数:树结构 tree 和当前节点的索引 node_index。函数首先打印当前节点的值,然后检查当前节点是否有子节点。如果有,它会遍历每个子节点,并对每个子节点递归调用 traverse_tree 函数。

递归调用中的 subnode_index + 1 + len(tree) - len(tree[node_index + 1]) 是一个重要的偏移计算,它确保了我们可以正确地访问每个子节点。这是因为每个节点后面的列表表示它的子节点,所以我们需要跳过这个子节点列表,然后继续遍历。

使用强强列表来存储树结构是一种简单直观的方法,但它也有一些局限性,比如它不便于处理具有不同分支结构的树。在实际应用中,我们通常会使用专门的树数据结构或者类来表示和管理树结构,以便更高效地进行操作和查询。

7.浅谈python与树

在计算机科学中,树(Tree)是一种非常重要的数据结构,它模拟了自然界中树的层级结构,由节点(Nodes)和连接节点的边(Edges)组成。在树结构中,有一个特殊的节点称为根节点(Root Node),每个节点可以有零个或多个子节点(Child Nodes),而除了根节点外,每个节点都有一个父节点(Parent Node)。树结构中的节点不形成环路,即从根节点到任何一个节点的路径都是唯一的。

Python中没有内置的树数据结构,但是你可以使用类和对象来实现树。下面是树的一个简单实现:

class TreeNode:
    def __init__(self, value):
        self.value = value
        self.children = []

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

    def __repr__(self):
        return f"TreeNode({self.value})"

# 创建树的根节点
root = TreeNode('root')

# 创建子节点并添加到根节点
child1 = TreeNode('child1')
child2 = TreeNode('child2')
root.add_child(child1)
root.add_child(child2)

# 创建子节点的子节点
subchild1 = TreeNode('subchild1')
child1.add_child(subchild1)

# 打印树结构
def print_tree(node, level=0):
    print('  ' * level + str(node))
    for child in node.children:
        print_tree(child, level + 1)

print_tree(root)

在这个例子中,我们定义了一个TreeNode类来表示树中的节点。每个节点都有一个值(value)和一个子节点列表(children)。我们还定义了一个add_child方法来向节点添加子节点。print_tree函数用于递归地打印树结构,它会根据树的层级来缩进节点的输出,使得树的结构更加清晰。

树的类型有很多,比如二叉树(Binary Tree)、平衡树(Balanced Tree)、B树(B-Tree)、B+树(B+-Tree)、红黑树(Red-Black Tree)等。每种树都有其特定的特性和用途。例如,二叉搜索树(Binary Search Tree)是一种特殊的二叉树,它能够保持排序的特性,使得查找、插入和删除操作更加高效。

在实际应用中,树结构被广泛用于表示层次关系、组织数据、解析表达式、优化搜索算法等场景。例如,文件系统使用树来表示文件和目录的结构,Web浏览器使用树来解析HTML文档,数据库索引使用B树或B+树来加快数据检索速度。

总之,树是一种非常强大和灵活的数据结构,通过理解和掌握树的原理和实现,你可以解决许多复杂的编程问题。

让我们更详细地探讨 tree 函数和 root 函数的工作原理和它们在这段代码中的作用。

def tree(label, branches=[]):
for branch in branches:
assert is_tree(branch)
return [label] + list(branches)

def label(tree):
return tree[0]

def branches(tree):
return tree[1:]

def is_tree(tree):
if type(tree) != list or len(tree) < 1:
return False
for branch in branches(tree):
if not is_tree(branch):
return False
return True

def is_leaf(tree):
return not branches(tree)

def fib_tree(n):
if n <= 1:
return tree(n)
else:
left, right = fib_tree(n-2), fib_tree(n-1)
return tree(label(left)+label(right),[left, right])

tree 函数

tree 函数是这段代码中定义树结构的核心。它接受两个参数:

  1. root:这是树的根节点。根节点可以是任何类型的数据,不一定是列表。在树结构的最顶层,根节点可能只是一个单独的值,而不是列表。

  2. branches``:这是一个可选参数,默认值为一个空列表 []`。它表示从根节点出发的所有分支。每个分支本身也是一个树结构,这意味着每个分支可以有自己的根节点和子分支,形成一个嵌套的树形结构。

tree 函数的主要作用是将根节点和分支列表组合成一个完整的树结构。为了确保每个分支都是有效的树,函数遍历 branches 列表,并使用 assert 语句检查每个分支是否通过 is_tree 函数的验证。如果任何一个分支不是有效的树结构,程序将抛出异常。

函数返回一个新的列表,其中第一个元素是根节点,后面跟着的是分支列表。这个列表代表了整个树结构。

root 函数

root 函数用于提取树结构的根节点。它接受一个参数:

  1. tree:这是一个树结构,通常是由 tree 函数创建的列表。

root 函数简单地返回列表的第一个元素,即根节点。这个函数不处理任何错误或特殊情况,它假设传入的树结构是有效的,并且至少包含一个元素。

示例解释

让我们通过一个具体的例子来解释这两个函数是如何工作的:

# 创建一个树结构
my_tree = tree(3, [tree(1, [tree(2, [tree(1), tree(1)]), tree(1)])])

# 获取根节点
root_node = root(my_tree)  # 结果是 3

在这个例子中,我们首先使用 tree 函数创建了一个树结构 my_tree。这个树的根节点是 3,它有一个分支,这个分支本身也是一个树结构,其根节点是 1,并且有两个子分支:一个是 2,它又有两个子分支(两个 1),另一个是单独的 1

然后,我们使用 root 函数来获取这个树结构的根节点。由于 my_tree 是一个列表,其形式为 [3, [1, [2, [1, 1]], 1]]root 函数返回列表的第一个元素 3,这就是树的根节点。

通过这种方式,treeroot 函数共同支持了树结构的创建和访问。tree 函数用于构建复杂的树形结构,而 root 函数用于从这个结构中提取根节点。其他函数,如 branchesis_tree,也在这个模型中扮演重要角色,分别用于访问分支和验证树结构的有效性。

8.python中的字典与item方法

Python中的字典(Dictionary)是一种可变的容器模型,且可存储任意类型对象。字典的每个元素都是一个键值对(key-value pair),键和值通过冒号分隔,键值对之间用逗号分隔。字典的键必须是不可变的类型,如字符串、数字或元组,且必须是唯一的;而值可以是任何数据类型,包括另一个字典。

字典提供了非常高效的数据查找和动态添加/删除键值对的能力。以下是一些关于Python字典的基本操作和示例:

创建字典

# 空字典
my_dict = {}

# 使用键值对初始化字典
my_dict = {'name': 'Alice', 'age': 25, 'location': 'Wonderland'}

访问字典中的值

# 通过键访问值
name = my_dict['name']
print(name)  # 输出: Alice

# 使用get方法访问,如果键不存在,可以返回默认值
age = my_dict.get('age', 0)
print(age)  # 输出: 25

添加或修改字典

# 添加新的键值对
my_dict['email'] = 'alice@example.com'

# 修改已有的键对应的值
my_dict['age'] = 26

删除字典中的元素

# 删除键值对
del my_dict['location']

# 使用pop方法删除并返回值
email = my_dict.pop('email', None)
print(email)  # 输出: alice@example.com

遍历字典

# 遍历字典中的键
for key in my_dict:
    print(key)

# 遍历字典中的值
for value in my_dict.values():
    print(value)

# 遍历字典中的键值对
for key, value in my_dict.items():
    print(key, value)

字典的方法

# keys() 返回所有键的列表
keys_list = my_dict.keys()
print(keys_list)  # 输出: ['name', 'age']

# values() 返回所有值的列表
values_list = my_dict.values()
print(values_list)  # 输出: ['Alice', 26]

# items() 返回所有键值对的列表
items_list = my_dict.items()
print(items_list)  # 输出: [('name', 'Alice'), ('age', 26)]

字典的内置函数

# len() 函数返回字典中键值对的数量
print(len(my_dict))  # 输出: 2

# in 操作符检查键是否存在于字典中
if 'name' in my_dict:
    print("Name is in the dictionary.")

字典是Python中非常强大的数据结构,它在许多场景下都非常有用,如缓存数据、配置设置、数据汇总等。通过掌握字典的基本操作,你可以更有效地处理和组织数据。

在Python中,字典(dictionary)是一个内置的数据类型,它存储了键值对(key-value pairs)。当你需要遍历字典中的所有键值对时,可以使用items()方法。这个方法会返回一个包含字典中所有键值对的视图对象。每个键值对都是一个元组,其中第一个元素是键(key),第二个元素是值(value)。

以下是一些使用items()方法的例子:

遍历字典中的所有键值对

my_dict = {'name': 'Alice', 'age': 25, 'email': 'alice@example.com'}

# 使用for循环遍历字典中的所有键值对
for key, value in my_dict.items():
    print(f"Key: {key}, Value: {value}")

这个循环会输出:

Key: name, Value: Alice
Key: age, Value: 25
Key: email, Value: alice@example.com

使用items()方法进行数据统计

假设你有一个包含员工信息的字典,你想计算员工的平均工资。

employees = {
    'Alice': 5000,
    'Bob': 6000,
    'Charlie': 5500
}

# 计算平均工资
total_salary = sum(salary for _, salary in employees.items())
average_salary = total_salary / len(employees)

print(f"Average Salary: {average_salary}")

这段代码会计算出员工的平均工资并打印出来。

使用items()方法更新字典

你还可以使用items()方法来创建一个新的字典,或者更新现有字典中的键值对。

# 创建一个新字典,其中所有值翻倍
new_dict = {key: value * 2 for key, value in my_dict.items()}

print(new_dict)  # 输出: {'name': 'AliceAlice', 'age': '2525', 'email': 'alice@example.comalice@example.com'}

在这个例子中,我们使用列表推导式创建了一个新的字典new_dict,它的键与my_dict相同,但每个值都是my_dict中对应值的两倍。

过滤字典中的键值对

使用items()方法,你可以根据条件过滤字典中的键值对。

# 过滤出年龄大于30的员工信息
filtered_employees = {key: value for key, value in employees.items() if value > 30}

print(filtered_employees)  # 输出: {'Bob': 6000}

在这个例子中,我们创建了一个只包含年龄大于30的员工信息的新字典filtered_employees

items()方法是处理字典时非常有用的工具,它提供了一种灵活的方式来访问和操作字典中的键值对。通过掌握items()方法,你可以编写出更加高效和清晰的代码来处理字典数据。

9.汉诺塔问题的递归解法

著名的汉诺塔(Towers of Hanoi)问题。汉诺塔是一个经典的递归问题,要解决这个问题,我们需要将n个圆盘从一个杆子(起始杆)移动到另一个杆子(目标杆),同时遵守以下规则:

  1. 每次只能移动一个圆盘。
  2. 任何时候,在小圆盘上面不能放大圆盘。
  3. 只能将圆盘从一个杆子的顶部移动到另一个杆子的顶部。

为了解决这个问题,我们可以使用递归的方法。我们需要一个辅助函数来处理将n-1个圆盘从起始杆移动到中间杆,然后再将这些圆盘从中间杆移动到目标杆。下面是move_stack函数的完整实现:

def move_stack(n, start, end, auxiliary=None):
    """
    Print the moves required to move n disks on the start pole to the end
    pole without violating the rules of Towers of Hanoi.

    n -- number of disks
    start -- a pole position, either 1, 2, or 3
    end -- a pole position, either 1, 2, or 3
    auxiliary -- a pole position, either 1, 2, or 3 (not used in this function)

    If auxiliary is not given, it means we are at the base case where we only need to move
    the last disk from the start to the end pole.
    """
    assert 1 <= start <= 3 and 1 <= end <= 3 and start != end, "Bad start/end"

    # Base case: If there's only one disk, move it directly from start to end.
    if n == 1:
        print_move(start, end)
        return

    # Move n-1 disks from start to auxiliary (so they are out of the way).
    move_stack(n - 1, start, auxiliary)

    # Move the nth disk from start to end.
    print_move(start, end)

    # Move the n-1 disks that we left on auxiliary to end.
    move_stack(n - 1, auxiliary, end)

# Example usage:
move_stack(3, 1, 3)

在这个实现中,我们首先检查是否只有一个圆盘需要移动。如果是,我们直接打印移动指令。如果不是,我们将问题分解为三个步骤:

  1. 递归地将上面的n-1个圆盘从起始杆移动到辅助杆。
  2. 打印将最后一个圆盘从起始杆移动到目标杆的指令。
  3. 递归地将n-1个圆盘从辅助杆移动到目标杆。

注意,辅助杆在这里并没有作为参数传递给move_stack函数。这是因为在每次递归调用中,我们都会根据当前的杆子选择不同的辅助杆。例如,如果我们想从杆1移动到杆3,我们会使用杆2作为辅助杆。这个逻辑在实际调用move_stack时会被处理。

这个函数的实现遵循了汉诺塔问题的递归解法,并且能够正确地打印出移动圆盘的步骤。

posted @ 2024-04-15 11:01  陆舟LandBoat  阅读(37)  评论(0)    收藏  举报