Kotlin 进阶函数式编程技巧

Kotlin 进阶函数式编程技巧

Kotlin 简介

软件开发环境不断变化,要求开发人员不仅适应,更要进化。Kotlin 以其简洁的语法和强大的功能迅速成为许多人进化过程中的信赖伙伴。虽然 Kotlin 的初始吸引力可能是它的简洁语法和与 Java 的互操作性,但 Kotlin 的真正优势在于其更深层次的函数式编程能力。一旦掌握这些技术,就有可能改变我们处理问题、设计解决方案甚至理解代码的方式。

本文深入探讨 Kotlin 中的高级函数式编程,提供见解和现实世界的示例,旨在提高您的编码技能。无论你是在提高自己的技能还是初步接触这个领域,这里都是一个旨在与现代开发人员的挑战和愿景共鸣的指南。

Kotlin 的函数式基础

Kotlin 函数式编程的核心在于不可变性的概念和将函数视为一等公民。

1. 不可变数据结构

基本语法

在 Kotlin 中,“val”关键字表示只读(不可变)变量。虽然变量本身是不可变的,但它所指向的数据不一定是不可变的。这就是为什么 Kotlin 还提供了不可变集合。

val readOnlyList = listOf("a", "b", "c")

真实示例

考虑一个典型的电子商务应用程序。当用户查看他们的个人资料时,他们会看到他们过去的订单列表。为了在显示这些订单时防止意外修改,最好确保订单列表保持不可变。

data class Order(val orderId: Int, val product: String, val price: Double)

// 假设我们从数据库或 API 中获取该列表
val userOrders: List<Order> = fetchOrdersFromDatabase()

// 如果我们想要打折,我们可以通过创建具有更新价格的新列表来避免修改原始列表。
val discountedOrders = userOrders.map { order ->
    if (order.price > 100.0) {
        order.copy(price = order.price * 0.9)  // 10% 折扣
    } else {
        order
    }
}

2. 一等公民函数

基本语法

Kotlin 支持将函数分配给变量、将它们作为参数传递或从其他函数中返回,这意味着它们可以作为一等公民。

fun greet(name: String) = "Hello, $name!"
val greetingFunction: (String) -> String = ::greet
println(greetingFunction("Bob"))  // 输出:Hello, Bob!

真实示例

在图形渲染软件中,可以将各种效果(如模糊、锐化或颜色反转)应用于图像。通过将函数视为一等公民,可以将这些效果表示为函数并以各种方式组合。

fun blur(image: Image): Image = ...
fun sharpen(image: Image): Image = ...
fun invertColors(image: Image): Image = ...

val effects = listOf(::blur, ::sharpen, ::invertColors)

// 顺序地在图像上应用所有效果
val processedImage = effects.fold(originalImage) { img, effect -> effect(img) }

高级集合函数

Kotlin 提供了丰富的集合操作函数。除了基础知识,理解这些函数的复杂性可以极大提高代码的清晰度和效率。

1. 使用 map 和 flatMap 进行转换

基本语法

“map” 函数使用提供的转换函数转换集合中的每个元素。“flatMap” 可以转换和扁平化集合。

val numbers = listOf(1, 2, 3)
val squared = numbers.map { it * it }  // [1, 4, 9]

真实示例

假设您有一个字符串列表,表示潜在 URL,并想要提取域名。不是每个字符串都是有效的 URL,因此这就是 “flatMap” 起作用的地方。

val potentialUrls = listOf("https://example.com/page", "invalid-url", "https://another-example.com/resource")

val domains = potentialUrls.flatMap { url ->
    runCatching { URL(url).host }.getOrNull()?.let { listOf(it) } ?: emptyList()
}
// Result: ["example.com", "another-example.com"]

2. 使用 filter 和 filterNot 进行过滤

基本语法

“filter” 返回满足给定谓词的元素列表。“filterNot” 则相反。

val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filterNot { it % 2 == 0 }  // [1, 3, 5]

真实示例

想象一下,基于多个动态条件(如价格范围、评分和可用性)而不仅仅是一个条件来筛选产品。

data class Product(val id: Int, val price: Double, val rating: Int, val isAvailable: Boolean)

val products = fetchProducts()  // 假设这会获取产品列表

val filteredProducts = products.filter { product ->
    product.price in 10.0..50.0 && product.rating >= 4 && product.isAvailable
}

Sure! Here is the content organized using Markdown:

使用 fold 和 reduce 进行累积操作

fold 和 reduce 的概述

foldreduce 都用于累积操作,但它们在使用场景和语法上有一些不同。

fold
用途:对集合的元素执行操作,需要一个初始的累加器值和一个组合操作。可以处理任何类型的集合。
基本语法

val numbers = listOf(1, 2, 3, 4)
val sumStartingFrom10 = numbers.fold(10) { acc, number -> acc + number }  // 结果: 20

例子:例如,将字符串连接起来

val words = listOf("apple", "banana", "cherry")
val concatenated = words.fold("Fruits:") { acc, word -> "$acc $word" }
// 结果: "Fruits: apple banana cherry"

reduce
用途:与 fold 类似,但不需要一个初始的累加器值。它使用集合的第一个元素作为初始的累加器。

基本语法

val numbers = listOf(1, 2, 3, 4)
val product = numbers.reduce { acc, number -> acc * number }  // 结果: 24

例子:结合自定义数据结构。假设我们有一个范围的列表,我们想要合并范围:

val ranges = listOf(1..5, 3..8, 6..10)
val combinedRange = ranges.reduce { acc, range -> acc.union(range) }
// 结果: 1..10

关键区别

  • 初始值:
    • fold 需要一个显式的初始累加器值。
    • reduce 使用集合的第一个元素作为初始值。
  • 适用性:
    • fold 可以处理任何大小的集合,包括空集合(因为有初始累加器值)。
    • reduce 在空集合上会抛出异常,因为没有初始值来开始操作。
  • 灵活性:
    • fold 更灵活,允许定义与集合元素类型不同的初始值。
    • reduce 有类型约束,要求累加器和集合的元素必须是相同的类型。

使用 groupBy 和 associateBy 进行分区

groupBy 和 associateBy 的概述

groupBy 根据键选择器函数的结果返回一个将元素分组的 Map。associateBy 根据提供的键选择器将每个元素作为键返回一个 Map。

基本语法

val words = listOf("apple", "banana", "cherry")
val byLength = words.groupBy { it.length }  // {5=[apple], 6=[banana, cherry]}

示例

假设我们有一个学生对象的列表,我们想要根据学生的 ID 对其进行分组。

data class Student(val id: String, val name: String, val course: String)

val students = fetchStudents()

// 假设 students 包含:
// Student("101", "Alice", "Math"), Student("101", "Eve", "History"), Student("102", "Bob", "Science")

val studentsById = students.associateBy { it.id }
// 结果的 Map 将是:
// {"101"=Student("101", "Eve", "History"), "102"=Student("102", "Bob", "Science")}

在上面的例子中,Eve 覆盖了 Alice,因为它们都有相同的 ID “101”。结果的 Map 只保留了具有该 ID 的最后一个学生的详细信息。

关键区别

  • groupBy 创建一个 Map,其中每个键指向原始集合中的项目列表。
  • associateBy 创建一个 Map,其中每个键指向原始集合中的单个项目。如果存在重复项,最后一个元素将覆盖其他元素。

在选择使用 groupBy 还是 associateBy 时,主要考虑是否需要保留具有相同键的所有元素(使用 groupBy),还是只保留最后一个元素(使用 associateBy)。

在 Kotlin 中的函数组合

想象一下你有一个玩具工厂的装配线,在这条线上的每个工位上,玩具都要经历特定的变化。玩具从一个工位移动到下一个工位,每个步骤都会进行修改。

在编程中,尤其是在 Kotlin 中,当你将两个函数链接在一起,使得第一个函数的结果成为下一个函数的输入时,就像玩具从一个工位流畅地移动到终点一样。

想象一下我们的玩具工厂有三个工位:

  • A工位:给玩具上色。
  • B工位:将轮子安装到已上色的玩具上。
  • C工位:在已装有轮子的玩具上贴上贴纸。

这些工位就像函数一样,每个函数按照顺序执行自己的任务。

在 Kotlin 中,让我们将这些工位表示为函数:

fun paint(toy: Toy): Toy { /*上色并返回玩具*/ }
fun attachWheels(toy: Toy): Toy { /*安装轮子并返回玩具*/ }
fun placeSticker(toy: Toy): Toy { /*贴上贴纸并返回玩具*/ }

我们不想手动地将玩具从一个工位移动到下一个工位,我们希望有一个自动化的过程,使得玩具可以顺利地从开始到结束。这就是函数组合发挥作用的地方。

为了使其在 Kotlin 中生效,我们将定义一个 compose 函数:

infix fun <A, B, C> ((B) -> C).compose(g: (A) -> B): (A) -> C {
    return { x -> this(g(x)) }
}

这个 compose 函数是我们链接两个工位(函数)的工具。它确保一个工位的输出成为下一个工位的输入。

现在,使用 compose 函数,我们可以定义我们的自动化玩具装配线:

val completeToyProcess = ::placeSticker compose ::attachWheels compose ::paint

当你将原始玩具放入 completeToyProcess 中时,它会自动被上色、安装轮子,然后贴上贴纸。

实际示例

val rawToy = Toy()
val finishedToy = completeToyProcess(rawToy)

在这个例子中,原始玩具经过整个过程,变成了完成的玩具——上色、安装轮子和贴上贴纸,全部在一个流畅的操作中完成。

为什么这很有用?

清晰明了:就像我们的玩具工厂类比一样,您可以一次性看到整个装配线过程。您可以快速了解玩具经历的变化顺序。
灵活性:如果您需要不同的结果,您可以轻松地更改顺序或添加/删除工位(或函数)。
效率:您无需在每次修改后存储玩具;它只需通过装配线不断移动。
需要注意的事项

顺序很重要:就像不能在玩具上涂贴标签之前连续涂色一样,链接函数的顺序至关重要。
保持简单:如果您的装配线(或函数链)太长,就会变得难以理解或管理。这就像我们的玩具工厂中有太多工位一样。因此,平衡是关键!

科里化 - 增量决策的力量

想象一下您正在一家多功能咖啡店。他们不提供现成的饮料,而是给您一系列的选择。首先,您选择咖啡豆的类型,然后决定使用牛奶(或替代品),最后选择任何额外的口味或配料。

现在,假设您是一位常客,总是选择阿拉比卡咖啡豆,但会根据心情变化其他选择。咖啡店不会让您每次都从头开始选择,而是记住您的豆子偏好。这种方法节省时间,减少决策疲劳,并让您可以专注于当下最重要的事情。

这就像科里化在编程中所实现的功能。

分解问题

简化复杂的决策:就像选择一杯咖啡需要几个步骤一样,一些函数有很多参数。科里化将这些多参数函数简化为一系列更简单的函数链。该链中的每个函数都接受一个参数并返回下一个要使用另一个参数调用的函数。
记住偏好:通过科里化函数,您可以“记住”特定的决策(或函数参数)。在我们的咖啡示例中,您对阿拉比卡咖啡豆的喜好被记住了,让您可以进行其他选择。
专注于重要事项:有时,您并没有所有的信息。科里化允许您在信息可用时进行决策。就像当您来到柜台时,即使几天前选择了咖啡豆类型,您也可以稍后再决定使用牛奶和口味。

代码示例

假设有一个订购咖啡的函数。
当您使用科里化时,可以在函数调用过程中使用标记来指定特定的参数。这样做可以提供更灵活和可读性更高的代码。

在函数签名中使用标记:

fun orderCoffee(bean: String): (milk: String) -> (flavor: String) -> Coffee { ... }

在这个示例中,我们在函数签名中为 milkflavor 参数添加了标记。这使得在函数调用时可以明确地指定每个参数的值。

使用标记进行函数调用:

val arabicaOrder = orderCoffee("Arabica")
val myCoffee = arabicaOrder(milk = "Almond Milk")(flavor = "Vanilla")

通过在函数调用中使用标记,我们可以清楚地表达每个参数的含义,并且不需要按照特定顺序传递参数。这提高了代码的可读性,并且对于具有多个可选参数的函数尤其有用。

对于具有多个标记参数的函数,您可以根据自己的需求选择要使用的参数,并省略其他参数。例如:

val arabicaWithFlavor = arabicaOrder(flavor = "Caramel")
val myCoffee = arabicaWithFlavor(milk = "Whole Milk")

在这个示例中,我们只指定了 milkflavor 参数,并忽略了 bean 参数。这样,我们可以通过只提供所需的标记参数来创建定制的函数,而无需重复指定其他参数。

使用标记进行函数调用可以提高代码的可读性和灵活性,并使函数调用更具表达力和可维护性。标记参数允许您以更直观的方式指定参数,并且不需要依赖于参数的顺序。

单子——编程的安全网

想象一下组装一个DIY家具套件。说明书中的每个步骤都依赖于前一个步骤。然而,并不是所有的步骤都那么简单,有时你可能会发现缺少一个部件或者意识到你在之前的步骤中犯了个错误。

如果说明书带有内置的安全网岂不是太好了?例如,如果你准备在错误的地方固定螺丝钉,说明书会立即提醒你。或者,如果有一块零件缺失,它会提供一个权宜之计或告诉你如何在没有它的情况下继续进行。

这种“安全网”概念在DIY世界中就是单子给编程带来的。

理解单子

  • 依赖步骤——就像家具组装涉及一系列依赖步骤一样,编程中的操作通常是一个链条,其中每个链接都依赖前面的成功。
  • 安全机制——单子充当了一个安全机制,确保如果一个步骤失败或没有产生有效值,后续的步骤能够意识到并做出相应反应。
  • 封装挑战——单子将值与产生这些值的上下文捆绑在一起,无论是通过成功、错误还是某些副作用产生的。

实际应用

Kotlin的Optional是一种单子形式。想象一下从数据库查询用户资料的情况 -

fun findUserProfile(id: Int): Optional<UserProfile> {
    // 一些获取资料的逻辑
}

假设我们想获取用户的电子邮件 -

val emailOpt = findUserProfile(123).flatMap { profile -> profile.email }

如果findUserProfile找不到资料,它可能会返回一个空的Optional。flatMap操作不会崩溃或抛出错误;它只会产生另一个空的Optional。

这就像我们的DIY说明书的安全网。如果一个步骤不能完成,它不会停止整个过程,而是给你一个安全继续进行的方式。

单子引起注意

  • 优雅的失败:单子允许函数以优雅的方式失败。它们确保进程继续前行,即使是为了传达一个错误。
  • 直观的流程:有了单子,代码的流程变得更直观,更能反映现实生活中的决策过程。
  • 增强的可组合性:由于它们可链式使用的特性,单子导致更模块化和适应性更强的代码。

惰性求值和序列——提供高效的操作

曾经去过自助餐厅,决定只拿你确定会吃的菜,而不是一次性把盘子填满,可能浪费食物吗?这种策略让你在需要时消耗所需,确保最大限度地享受,最少的浪费。

编程中的惰性求值采用了类似的策略。它不是预先计算所有东西,而是在需要时计算所需的内容。在Kotlin中,序列是实现这一目标的主要方式。让我们深入了解!

理解惰性求值

  • 惰性求值是一种计算策略,其中表达式仅在实际需要其结果时进行评估。这可以提高内存使用效率和执行速度,尤其是在处理大型集合时。

Kotlin Sequences

在 Kotlin 中,sequences(Sequence)表示一种惰性计算的集合。与列表不同,sequences 不保存数据;相反,它们描述在请求时生成数据元素的计算过程。

实际应用——Sequences vs. Lists

考虑一个数字列表,我们想要在平方后找到第一个可被 5 整除的数。

使用列表:

val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val result = numbers.map { it * it } // 平方所有数字
                .filter { it % 5 == 0 } // 过滤所有可被 5 整除的平方数
                .first() // 获取第一个项目
println(result)  // 25

在这种方法中,我们对所有数字进行平方和过滤,只使用一个值。那太低效了!

使用 sequence:

val numbersSeq = numbers.asSequence()

val resultSeq = numbersSeq.map { it * it }
                          .filter { it % 5 == 0 }
                          .first()

println(resultSeq)  // 25

使用 sequences,每个数字都会被平方,检查是否可被 5 整除,然后当找到第一个这样的数字时,该过程停止。因此,在这种情况下,sequence 只会平方和过滤,直到它找到数字 5。那是高效的!

通过 Sequences 的惰性评估带来的好处:

  • 效率——只计算必要的部分。
  • 灵活性——可以表示无限数据结构。
  • 内存节省——在处理大型数据集时尤为重要。

在 Kotlin 中采用 sequences 和惰性评估,就像采用“边走边消费”的方法一样。它使开发人员能够编写高效和可扩展的代码,特别是在涉及大量数据操作的场景下。

尾递归——利用 Kotlin 编写高效的递归

这样想——你站在高楼的底层,向上凝视着永无止境的楼梯。如果你一个接一个地爬每个台阶,你可能很快就会累垮,或者感到不知所措。但是,如果你可以一次跳过多个楼层,并使用爬一个台阶所需的相同的能量呢?这就是 Kotlin 中尾递归的魔力!

解析递归

递归是一种编程技术,其中函数调用自身以将复杂问题分解为简单的子问题。但是,标准递归可以很快占用大量内存,特别是对于大型输入。每个函数调用都会添加到调用堆栈中,而对于深度递归,这可能会导致堆栈溢出错误。

引入尾递归

尾递归是递归的一种特殊形式,其中递归调用是函数中执行的最后一件事。Kotlin 的编译器优化尾递归函数以使用恒定的堆栈空间,防止堆栈溢出错误。

简单示例——阶乘

没有尾递归:

fun factorial(n: Int): Int {
    if (n == 1) return 1
    return n * factorial(n - 1)
}

使用尾递归:

fun factorial(n: Int, accumulator: Int = 1): Int {
    if (n == 1) return accumulator
    return factorial(n - 1, n * accumulator)
}

在尾递归版本中,递归调用的结果(与当前操作相结合)作为累加器传递。它确保在递归调用后没有额外的操作待处理,使其成为有效的尾调用。

为什么使用尾递归?

  • 效率——它使用恒定的堆栈空间,防止堆栈溢出。
  • 清晰度——对于某些问题,递归解决方案可能更直观。
  • Kotlin 的支持——只需添加 tailrec 修饰符,Kotlin 即可处理优化!

重要说明

必须确保递归真正处于尾部位置。如果递归调用后有任何操作待处理,该函数将不是尾递归,并且 Kotlin 的编译器将无法优化它。

尾递归背后发生的事情是什么?

在传统递归中,每个函数调用都会被堆叠,等待下一个函数完成其自身计算之前。随着函数调用的堆叠,内存使用量将增加,特别是对于大型输入数字。

在我们的尾递归版本中,发生了以下情况:

  • 每个递归调用都被优化以重用当前函数的堆栈帧,因为在递归调用后没有剩余的计算(例如阶乘中的乘法)。
  • 累加器充当运行总数,保存中间结果。这意味着,到达基本情况(n == 1)时,我们已经在累加器中得到了答案,无需“往回走”。
  • Kotlin 编译器看到 tailrec 修饰符,并识别出函数是尾递归的。然后,它在幕后优化字节码,以确保函数使用恒定的堆栈内存,无论输入大小如何。

实质上,我们的阶乘函数,当调用 factorial(5) 时,从计算:

5 * 4 * 3 * 2 * 1

转换为:

(((5 * 1) * 4) * 3) * 2

这种转换确保在到达基本情况时即可得到答案,同时使用恒定的堆栈空间。

另一个说明

尽管尾递归优化是 Kotlin 中的一个强大功能,但值得注意的是,这个概念并不是该语言所专有的。许多其他编程语言,包括函数式语言(如 Haskell)和更通用的语言(如 Scala),都支持尾递归。但是,它们实现和优化的方式可能不同。在过渡各种语言或与来自不同背景的开发人员讨论该主题时,请始终考虑这一点。

结论

在我们探讨 Kotlin 中的高级函数式编程时,我们已经看到了 Kotlin 提供的深度和多功能性。从集合函数的复杂性、函数组合的优雅性到尾递归的效率,Kotlin 为开发人员提供了强大的工具。虽然这些概念在 Kotlin 中得到了强调,但它们是更广泛的函数式编程世界中的支柱。通过掌握它们,您不仅可以优化 Kotlin 技能,而且还可以使用永恒的编程原则。在您继续前进时,请让这些工具和技巧指导您的 Kotlin 之旅,以生成更有效、更干净、更易于维护的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/118094.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每天一点python——day61

#第61天 #字符串的驻留机制字符串&#xff1a;python中基本数据类型&#xff0c;是一个不可变的序列【目前我们学了两个&#xff1a;元组、字符串】 可以使用单引号&#xff0c;双引号&#xff0c;三引号来定义#定义字符串 apython#用单引号&#xff0c;双引号&#xff0c;三引…

Redis原理到常用语法基础图文讲解

在初期&#xff0c;已经讲述了Redis安装问题。现在正式进入Redis的入门阶段 系统架构的演进 传统单机架构 一台机器运行应用程序、数据库服务器 现在大部分公司的产品都是这种单机架构。因为现在计算机硬件发展速度很快&#xff0c;哪怕只有一台主机&#xff0c;性能也很高…

智慧城市排水系统,管网水位监测仪怎么监测

地下排水管网应用于城市的多个环境之中&#xff0c;比如排放雨水&#xff0c;污水或者是地表水等&#xff0c;总之是在维护城市的安全运行&#xff0c;并且保护城市地下生命线处于正常状态。但是一旦排水系统面对各种极端天气&#xff0c;便有可能会突发安全事故&#xff0c;导…

如何再kali中下载iwebsec靶场

这个靶场有三种搭建方法&#xff1a; 第一种是在线靶场&#xff1a;http://www.iwebsec.com:81/ 第二种是虚拟机版本的&#xff0c;直接下载到本地搭建 官网地址下载&#xff1a;http://www.iwebsec.com/ 而第三种就是利用docker搭建这个靶场&#xff0c;我这里是用kali进行…

部署kubevirt教程

前提条件 已安装&#xff1a;kubernetes集群、kubectl、docker apt install -y qemu-kvm libvirt virt-install bridge-utils 【所有节点全部安装】 virt-host-validate qemu部署kubevirt 下载kubevirt-cr.yaml和kubevirt-operator.yaml 先执行&#xff1a; Kubectl apply …

网络安全(黑客)-0基础小白自学

1.网络安全是什么 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 2.网络安全市场 一、是市场需求量高&#xff1b; 二、则是发展相对成熟…

Go Gin中间件

Gin是一个用Go语言编写的Web框架&#xff0c;它提供了一种简单的方式来创建HTTP路由和处理HTTP请求。中间件是Gin框架中的一个重要概念&#xff0c;它可以用来处理HTTP请求和响应&#xff0c;或者在处理请求之前和之后执行一些操作。 以下是关于Gin中间件开发的一些基本信息&am…

数据库系统原理与实践 笔记 #7

文章目录 数据库系统原理与实践 笔记 #7数据库设计和E-R模型(续)转换为关系模式具有简单属性的实体集的表示复合属性多值属性联系集的表示模式的冗余—合并 实体-联系设计问题设计问题联系属性的布局 扩展的E-R特性特化概化属性继承特化/概化的设计约束聚集E-R图表示方法总结E-…

聚会娱乐喝酒游戏小程序源码系统 可开流量主 带完整的搭建教程

今天罗峰来给大家分享一款聚会娱乐喝酒游戏小程序源码系统 。在聚会娱乐活动中&#xff0c;喝酒游戏是一种非常受欢迎的活动方式。但是&#xff0c;往往由于缺乏有效的组织和规则&#xff0c;导致游戏的进行不够顺畅&#xff0c;甚至出现混乱的情况。因此&#xff0c;开发一款能…

Java-Hbase介绍

1.1. 概念 base 是分布式、面向列的开源数据库&#xff08;其实准确的说是面向列族&#xff09;。HDFS 为 Hbase 提供可靠的 底层数据存储服务&#xff0c;MapReduce 为 Hbase 提供高性能的计算能力&#xff0c;Zookeeper 为 Hbase 提供 稳定服务和 Failover 机制&#xff0c…

云计算实战项目之---学之思在线考试系统

简介&#xff1a; 学之思开源考试系统是一款 java vue 的前后端分离的考试系统。主要优点是开发、部署简单快捷、界面设计友好、代码结构清晰。支持web端和微信小程序&#xff0c;能覆盖到pc机和手机等设备。 支持多种部署方式&#xff1a;集成部署、前后端分离部署、docker部…

一文深入了解 CPU 的型号、代际架构与微架构

在 10 月 16 号的时候&#xff0c;Intel 正式发布了第 14 代的酷睿处理器。但还有很多同学看不懂这种发布会上发布的各种 CPU 参数。借着这个时机&#xff0c;给大家深入地讲讲 CPU 的型号规则、代际架构与微架构方面的知识。 CPU 在整个计算机硬件中、技术体系中都算是最最重…

Wpf 使用 Prism 实战开发Day04

一.菜单导航实现 1.首先创建出所有的页面(View)及对应的页面逻辑处理类(ViewModel) IndexView(首页)-----------------IndexViewModelToDoView(待办事项)------------ToDoViewModelMemoView(忘备录)--------------MemoViewModelSettingsView(设置)--------------SettingsViewMo…

防逆流系统中防逆流电表的正确安装位置-安科瑞黄安南

随着光伏行业的发展&#xff0c;部分地区村级变压器及工业用电变压器容量与光伏项目的装机容量处于饱和。电网公司要求对后建的光伏并网系统为不可逆流发电系统&#xff0c;指光伏并网系统所发生的电由本地负载消耗&#xff0c;多余的电不允许通过低压配电变压器向上级电网逆向…

【漏洞复现】Apache_HTTP_2.4.50_路径穿越漏洞(CVE-2021-42013)

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证方式一 curl方式二 bp抓捕 1.5、修复建议 说明内容漏洞编号CVE-2021-42013漏洞名称…

【GEE】6、在 Google 地球引擎中构建各种遥感指数

1简介 在本模块中&#xff0c;我们将讨论以下概念&#xff1a; 如何在 GEE 中重命名图像的波段。如何使用已有的遥感指数。如何使用波段数学生成自己的遥感指数。 一个田地已经灌溉的年数的卫星图像。灌溉水最可能的来源是奥加拉拉含水层。图片来自科罗拉多州霍利奥克附近。资料…

设计模式之迭代器模式

什么是迭代器模式 迭代器模式&#xff08;Iterator pattern&#xff09;是一种对象行为型设计模式&#xff0c;它提供了一种方法来顺序访问聚合对象中的元素&#xff0c;而又不暴露该对象的内部表示&#xff0c;同时也可以将迭代逻辑与聚合对象的实现分离&#xff0c;增强了代码…

信驰达RF-DG-52PAS CC2652P Zigbee 3.0 USB Dongle烧录指南

一、使用前准备 RF-DG-52PAS是信驰达科技基于美国 TI CC2652P和CP2102为核心设计的Zigbee 3.0 USB Dongle,可烧录 Z-Stack 3.x.0协调器固件&#xff0c;可以直接连接到计算机或树莓派&#xff0c;通过ZHA或 Zigbee2MQTT连接到 Home Assistant或其他开源物联网平台。还可以烧录…

uniapp小程序刮刮乐抽奖

使用canvas画布画出刮刮乐要被刮的图片&#xff0c;使用移动清除画布。 当前代码封装为刮刮乐的组件&#xff1b; vue代码&#xff1a; <template><view class"page" v-if"merchantInfo.cdn_static"><image class"bg" :src&q…

ElasticSearch与Lucene是什么关系?Lucene又是什么?

一. ElasticSearch 与 Lucene 的关系 Elasticsearch&#xff08;ES&#xff09;和Apache Lucene之间有密切的关系&#xff0c;可以总结如下&#xff1a; Elasticsearch构建于Lucene之上&#xff1a;Elasticsearch实际上是一个分布式的、实时的搜索和分析引擎&#xff0c;它构建…