《Kotlin核心编程》下篇

设计模式

设计模式分类

创建型设计模式:

  • 概念:创建型设计模式主要用于对象的创建过程,比如对象的实例化、创建对象的方式和时机等,它关注的是如何将对象的创建和使用分离,使得代码在创建对象时更加灵活、可维护和可扩展。

  • 特点:封装对象创建过程;提高灵活性;便于代码复用。

  • 常见模式:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。

结构型设计模式:

  • 概念:结构型设计模式主要用于处理类或对象的组合结构,它关注的是如何将类或对象组合成更大的结构,以及如何在这些结构中实现功能的扩展和优化。

  • 特点:组合对象结构;增强功能;提高可维护性。

  • 常见模式:代理模式、适配器模式、桥接模式、装饰模式、外观模式、享元模式、组合模式。

行为型设计模式:

  • 概念:行为型设计模式主要用于处理对象之间的交互和职责分配,它关注的是对象之间如何进行通信、协作以及如何分配职责,以实现系统的功能和行为。

  • 特点:对象间交互;职责分配;提高可扩展性。

  • 常见模式:策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

工厂模式

工厂模式是一种创建对象的设计模式,它将对象的创建和使用分离。通过使用工厂类来创建对象,而不是在客户端代码中直接实例化,这样可以降低代码的耦合度,提高可维护性和可扩展性。例如,有一个Shape接口及其实现类CircleRectangle,可以创建一个ShapeFactory工厂类:

interface Shape {fun draw()
}class Circle : Shape {override fun draw() {println("Drawing a circle")}
}class Rectangle : Shape {override fun draw() {println("Drawing a rectangle")}
}class ShapeFactory {fun createShape(shapeType: String): Shape? {return when (shapeType) {"CIRCLE" -> Circle()"RECTANGLE" -> Rectangle()else -> null}}
}

使用示例:

fun main() {val factory = ShapeFactory()val circle = factory.createShape("CIRCLE")circle?.draw()
}

内联函数简化抽象工厂

内联函数在编译时会将函数体直接插入到调用处,减少函数调用开销。在抽象工厂模式中,使用内联函数可以简化代码结构。例如,定义一个内联的抽象工厂函数,用于创建不同类型的数据库连接:

interface DatabaseConnection {fun connect()
}class MySQLConnection : DatabaseConnection {override fun connect() {println("Connecting to MySQL database")}
}class PostgreSQLConnection : DatabaseConnection {override fun connect() {println("Connecting to PostgreSQL database")}
}inline fun createDatabaseConnection(type: String): DatabaseConnection = when (type) {"MYSQL" -> MySQLConnection()"POSTGRESQL" -> PostgreSQLConnection()else -> throw IllegalArgumentException("Unsupported database type")
}

使用示例:

fun main() {val connection = createDatabaseConnection("MYSQL")connection.connect()
}

构建者模式

构建者模式用于创建复杂对象,将对象的构建过程和表示分离,使得同样的构建过程可以创建不同的表示。比如创建一个Computer类,其配置较为复杂:

class Computer(val cpu: String,val ram: String,val storage: String
)class ComputerBuilder {private var cpu: String = ""private var ram: String = ""private var storage: String = ""fun setCpu(cpu: String): ComputerBuilder {this.cpu = cpureturn this}fun setRam(ram: String): ComputerBuilder {this.ram = ramreturn this}fun setStorage(storage: String): ComputerBuilder {this.storage = storagereturn this}fun build(): Computer {return Computer(cpu, ram, storage)}
}

使用示例:

fun main() {val computer = ComputerBuilder().setCpu("Intel i7").setRam("16GB").setStorage("1TB SSD").build()println("Computer configured: CPU - ${computer.cpu}, RAM - ${computer.ram}, Storage - ${computer.storage}")
}

观察者模式

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新 。在 Kotlin 中可以通过委托来实现观察者模式。假设有一个被观察的Subject类和多个Observer

interface Observer {fun update(message: String)
}class Subject {private val observers: MutableList<Observer> = mutableListOf()fun registerObserver(observer: Observer) {observers.add(observer)}fun removeObserver(observer: Observer) {observers.remove(observer)}fun notifyObservers(message: String) {observers.forEach { it.update(message) }}
}class ConcreteObserver : Observer {override fun update(message: String) {println("Observer received message: $message")}
}

使用示例:

fun main() {val subject = Subject()val observer = ConcreteObserver()subject.registerObserver(observer)subject.notifyObservers("Something has changed!")
}

高阶函数简化设计模式

高阶函数可以以函数作为参数或返回值,利用这一特性可以简化一些设计模式的实现。例如策略模式,它定义了一系列算法,将每个算法都封装起来,并且使它们可以相互替换。通过高阶函数实现一个简单的计算策略模式:

typealias MathOperation = (Int, Int) -> Intfun calculate(a: Int, b: Int, operation: MathOperation): Int {return operation(a, b)
}fun main() {val add: MathOperation = { x, y -> x + y }val subtract: MathOperation = { x, y -> x - y }val result1 = calculate(5, 3, add)val result2 = calculate(5, 3, subtract)println("Addition result: $result1")println("Subtraction result: $result2")
}

重载 iterator 方法、责任链模式、ADT 实现状态模式

  • 重载 iterator 方法:在 Kotlin 中,通过重载iterator方法可以使自定义类支持迭代操作。例如,创建一个简单的自定义集合类:

class MyList<T>(private val elements: Array<T>) : Iterable<T> {override fun iterator(): Iterator<T> = object : Iterator<T> {private var index = 0override fun hasNext(): Boolean = index < elements.sizeoverride fun next(): T = elements[index++]}
}fun main() {val myList = MyList(arrayOf(1, 2, 3))for (element in myList) {println(element)}
}
  • 责任链模式:责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。例如,处理请假审批流程:

abstract class Approver {protected var successor: Approver? = nullfun setSuccessor(successor: Approver) {this.successor = successor}abstract fun processRequest(request: Int)
}class TeamLead : Approver() {override fun processRequest(request: Int) {if (request <= 2) {println("Team lead approved the leave for $request days")} else {successor?.processRequest(request)}}
}class Manager : Approver() {override fun processRequest(request: Int) {if (request <= 5) {println("Manager approved the leave for $request days")} else {successor?.processRequest(request)}}
}class Director : Approver() {override fun processRequest(request: Int) {if (request <= 10) {println("Director approved the leave for $request days")} else {println("Request needs further discussion")}}
}//使用示例
fun main() {val teamLead = TeamLead()val manager = Manager()val director = Director()teamLead.setSuccessor(manager)manager.setSuccessor(director)teamLead.processRequest(3)
}
  • ADT 实现状态模式:代数数据类型(ADT)可以很好地实现状态模式。状态模式允许对象在内部状态改变时改变它的行为。例如,实现一个简单的电梯状态管理:

sealed class ElevatorState
class Idle : ElevatorState()
class MovingUp : ElevatorState()
class MovingDown : ElevatorState()class Elevator {private var state: ElevatorState = Idle()fun moveUp() {when (state) {is Idle -> {state = MovingUp()println("Elevator is moving up")}is MovingDown -> {state = MovingUp()println("Elevator stopped and is moving up")}else -> println("Elevator is already moving up")}}fun moveDown() {when (state) {is Idle -> {state = MovingDown()println("Elevator is moving down")}is MovingUp -> {state = MovingDown()println("Elevator stopped and is moving down")}else -> println("Elevator is already moving down")}}
}//使用示例
fun main() {val elevator = Elevator()elevator.moveUp()elevator.moveDown()}
}

装饰模式

装饰模式动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更为灵活。例如,有一个Coffee接口及其实现类SimpleCoffee,可以通过装饰类为咖啡添加不同配料:

interface Coffee {fun getCost(): Doublefun getDescription(): String
}class SimpleCoffee : Coffee {override fun getCost(): Double = 1.0override fun getDescription(): String = "Simple coffee"
}class MilkDecorator(private val coffee: Coffee) : Coffee {override fun getCost(): Double = coffee.getCost() + 0.5override fun getDescription(): String = "${coffee.getDescription()}, with milk"
}class SugarDecorator(private val coffee: Coffee) : Coffee {override fun getCost(): Double = coffee.getCost() + 0.2override fun getDescription(): String = "${coffee.getDescription()}, with sugar"
}//使用示例
fun main() {val simpleCoffee = SimpleCoffee()val coffeeWithMilk = MilkDecorator(simpleCoffee)val coffeeWithMilkAndSugar = SugarDecorator(coffeeWithMilk)println("${coffeeWithMilkAndSugar.getDescription()}: ${coffeeWithMilkAndSugar.getCost()}")
}

函数式编程

函数式语言

  • 函数是一等公民:在 Kotlin 中,函数可以像普通数据类型一样进行操作,例如作为参数传递给其他函数、作为函数的返回值,或者存储在变量中。

//1.第一个示例:
//定义一个函数并赋值给变量
val add: (Int, Int) -> Int = { a, b -> a + b }
// 使用变量调用函数
val result = add(3, 5)
println(result)//2.第二个示例:函数作为参数传递给其他函数
// 定义一个接受函数作为参数的高阶函数
fun operateOnNumbers(a: Int, b: Int, operation: (Int, Int) -> Int): Int {return operation(a, b)
}
// 定义一个简单的乘法函数
val multiply: (Int, Int) -> Int = { a, b -> a * b }
// 将乘法函数作为参数传递给高阶函数
val multiplyResult = operateOnNumbers(4, 6, multiply)
println(multiplyResult)//3.第三个示例:函数作为返回值
// 定义一个根据条件返回不同函数的高阶函数
fun getOperation(shouldAdd: Boolean): (Int, Int) -> Int {return if (shouldAdd) {{ a, b -> a + b }} else {{ a, b -> a - b }}
}
// 根据条件获取函数并调用
val operation = getOperation(true)
val operationResult = operation(10, 5)
println(operationResult)
  • 声明式编程风格:在 Kotlin 中,使用集合操作函数体现声明式编程风格,只需要声明对数据的操作,而不需要关心具体的实现细节。

val numbers = listOf(1, 2, 3, 4, 5)
// 筛选出偶数并计算平方
val squaredEvenNumbers = numbers.filter { it % 2 == 0 }.map { it * it }
println(squaredEvenNumbers)

引用透明性和副作用

  • 引用透明性:满足引用透明性的函数,给定相同的输入,总是返回相同的输出,并且不会产生任何可观察的副作用。

// 一个具有引用透明性的纯函数
fun square(x: Int): Int = x * x
// 无论何时调用,结果都相同
val result1 = square(5)
val result2 = square(5)
println(result1)
println(result2)
  • 副作用:副作用是指函数在执行过程中,除了返回值之外,对外部状态产生的影响,如修改全局变量、进行 I/O 操作等。

// 定义一个全局变量
var globalVar = 0
// 一个具有副作用的函数,修改全局变量
fun incrementGlobal() {globalVar++
}
// 调用具有副作用的函数
incrementGlobal()
println(globalVar)

纯函数和局部可变性

  • 纯函数:纯函数是满足引用透明性的函数,不依赖外部状态,只根据输入参数返回确定的输出。

// 纯函数示例,计算两个数的商
fun divide(a: Int, b: Int): Double {require(b!= 0) { "除数不能为零" }return a.toDouble() / b
}
// 调用纯函数
val divisionResult = divide(10, 2)
println(divisionResult)
  • 局部可变性:虽然函数式编程强调不可变性,但在某些情况下,在局部范围内使用可变变量可以提高效率或实现特定算法。

// 在函数内部使用可变变量实现累加
fun sumList(list: List<Int>): Int {var result = 0list.forEach { result += it }return result
}
val numbersList = listOf(1, 2, 3, 4, 5)
val sum = sumList(numbersList)
println(sum)

异步和并发

基本概念

  • 异步:程序在执行过程中,某些操作无需等待当前任务完成,就能继续执行后续代码,不会阻塞主线程,提升用户体验。比如在 Android 应用中,进行网络请求时采用异步方式,主线程不会因等待数据而卡顿,用户可继续操作界面。在 Kotlin 中,异步主要通过协程来实现。协程是一种轻量级的线程模型,它基于挂起函数(suspend function)来暂停和恢复执行。挂起函数可以暂停当前协程的执行,并将控制权交回给调用者,而不会阻塞底层线程。这样,其他协程就可以在同一线程上运行,从而实现了异步执行。

  • 并发:指在同一时间段内,多个任务看似同时执行。在单核 CPU 系统里,通过任务快速切换模拟同时执行;多核 CPU 系统则能真正并行执行任务,充分利用系统资源,提高程序运行效率。

核心技术

  • 协程

    • 挂起函数:以 suspend 关键字修饰,可暂停执行并将控制权交回调用者,不阻塞线程。常用于异步操作,如网络请求、文件读取等。例如:suspend fun readFile() : String,函数内部执行文件读取时可暂停,等待数据读取完毕再恢复。

    • 协程构建器:

      • launch:启动新协程并立即执行,无返回值。主要用于执行独立的异步任务,如更新 UI、记录日志等。例如:launch { doSomeBackgroundWork() }

      • async:启动协程并返回 Deferred 对象,通过 await 方法获取协程执行结果。适用于需要异步计算并获取结果的场景,如多个数据的并行计算。例如:val deferredResult = async { calculateValue() }; val result = deferredResult.await()

    • 协程上下文与调度器:协程上下文包含协程运行的环境信息,调度器是其中关键部分,决定协程执行的线程或线程池。

      • Dispatchers.Main:用于 Android 主线程,更新 UI 等操作必须在此调度器执行。

      • Dispatchers.IO:适合 I/O 密集型任务,如文件读写、网络请求,它使用一个线程池来处理这些任务。

      • Dispatchers.Default:适用于 CPU 密集型任务,利用共享线程池执行,充分利用多核 CPU 性能。

  • 线程

    • 创建方式:可通过继承 Thread 类或实现 Runnable 接口创建线程。不过,Kotlin 中协程提供了更简洁、高效的异步处理方式,线程使用相对较少。例如继承 Thread 类:

class MyThread : Thread() {override fun run() {// 线程执行逻辑}
}
val myThread = MyThread()
myThread.start()
  • 线程安全:多线程访问共享资源时,可能引发数据不一致问题。解决方法有:

    • 同步块:使用 synchronized 关键字包裹共享资源访问代码,保证同一时刻只有一个线程能访问。

    • 锁机制:ReentrantLock 等锁类提供更灵活的同步控制。使用时先获取锁,操作完成后释放锁。

val lock = ReentrantLock()
lock.lock()
try {// 访问共享资源的代码
} finally {lock.unlock()
}

实际应用

  • 网络请求:结合协程与网络框架(如 OkHttp),实现异步网络请求。在 Dispatchers.IO 调度器执行请求,获取数据后切换到 Dispatchers.Main 更新 UI。例如:

    • 当按钮被点击时,使用 CoroutineScope(Dispatchers.Main).launch 在主线程启动一个协程。

    • 在协程中调用 fetchData 函数进行网络请求,fetchData 函数使用 withContext(Dispatchers.IO) 将网络请求操作切换到 Dispatchers.IO 调度器执行,避免阻塞主线程。

    • 当网络请求完成后,将结果赋值给 resultTextViewtext 属性,由于协程在主线程启动,所以可以直接更新 UI。

class MainActivity : AppCompatActivity() {private lateinit var fetchDataButton: Buttonprivate lateinit var resultTextView: TextViewoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)fetchDataButton = findViewById(R.id.fetchDataButton)resultTextView = findViewById(R.id.resultTextView)fetchDataButton.setOnClickListener {// 在主线程启动协程CoroutineScope(Dispatchers.Main).launch {try {// 调用 fetchData 函数进行网络请求val result = fetchData()// 在主线程更新 UIresultTextView.text = result} catch (e: Exception) {resultTextView.text = "Error: ${e.message}"}}}}private suspend fun fetchData(): String {return withContext(Dispatchers.IO) {val client = OkHttpClient()val request = Request.Builder().url("https://jsonplaceholder.typicode.com/todos/1").build()try {val response = client.newCall(request).execute()response.body?.string() ?: ""} catch (e: IOException) {"Network error: ${e.message}"}}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis|前言

文章目录 什么是 Redis&#xff1f;Redis 主流功能与应用 什么是 Redis&#xff1f; Redis&#xff0c;Remote Dictionary Server&#xff08;远程字典服务器&#xff09;。Redis 是完全开源的&#xff0c;使用 ANSIC 语言编写&#xff0c;遵守 BSD 协议&#xff0c;是一个高性…

【算法设计与分析】实验8:分支限界—TSP问题

目录 一、实验目的 二、实验环境 三、实验内容 四、核心代码 五、记录与处理 六、思考与总结 七、完整报告和成果文件提取链接 一、实验目的 掌握分支界限求解问题的思想&#xff1b;针对不同的问题&#xff0c;能够利用分支界限法进行问题拆分和求解以及时间复杂度分析…

2025年大年初一篇,C#调用GPU并行计算推荐

C#调用GPU库的主要目的是利用GPU的并行计算能力&#xff0c;加速计算密集型任务&#xff0c;提高程序性能&#xff0c;支持大规模数据处理&#xff0c;优化资源利用&#xff0c;满足特定应用场景的需求&#xff0c;并提升用户体验。在需要处理大量并行数据或进行复杂计算的场景…

2025:影刀RPA使用新实践--CSDN博客下载

文章目录 一键CSDN博客下载器程序说明指导说明使用步骤 获取方法 一键CSDN博客下载器 程序说明 配置信息&#xff1a;CSDN账号&#xff08;手机号/邮箱/用户名&#xff09;、密码、博客文件类型支持markdown格式、html格式&#xff08;默认值markdown格式&#xff09;、博客保…

游戏引擎 Unity - Unity 启动(下载 Unity Editor、生成 Unity Personal Edition 许可证)

Unity Unity 首次发布于 2005 年&#xff0c;属于 Unity Technologies Unity 使用的开发技术有&#xff1a;C# Unity 的适用平台&#xff1a;PC、主机、移动设备、VR / AR、Web 等 Unity 的适用领域&#xff1a;开发中等画质中小型项目 Unity 适合初学者或需要快速上手的开…

【Postman接口测试】Postman的安装和使用

在软件测试领域&#xff0c;接口测试是保障软件质量的关键环节之一&#xff0c;而Postman作为一款功能强大且广受欢迎的接口测试工具&#xff0c;能够帮助测试人员高效地进行接口测试工作。本文将详细介绍Postman的安装和使用方法&#xff0c;让你快速上手这款工具。 一、Pos…

边缘检测算法(candy)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 Canny 边缘检测的步骤 1. 灰度转换 如果输入的是彩色图像&#xff0c;则需要先转换为 灰度图像&#xff0c;因为边缘检测通常在单通道图像上进行。 2. 高斯滤波&#xff08;Gaussian Blur&#xff09; 由于边缘…

WinDBG查找C++句柄泄露

C代码&#xff08;频繁点击About按钮导致Mutex句柄泄露&#xff09; HANDLE _mutexHandle;LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {switch (message){case WM_COMMAND:{int wmId LOWORD(wParam);// 分析菜单选择:switch (wmId){c…

基于微信小程序的酒店管理系统设计与实现(源码+数据库+文档)

酒店管理小程序目录 目录 基于微信小程序的酒店管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、管理员模块的实现 (1) 用户信息管理 (2) 酒店管理员管理 (3) 房间信息管理 2、小程序序会员模块的实现 &#xff08;1&#xff09;系统首页 &#xff…

大白话讲清楚embedding原理

Embedding&#xff08;嵌入&#xff09;是一种将高维数据&#xff08;如单词、句子、图像等&#xff09;映射到低维连续向量的技术&#xff0c;其核心目的是通过向量表示捕捉数据之间的语义或特征关系。以下从原理、方法和应用三个方面详细解释Embedding的工作原理。 一、Embe…

mysql中in和exists的区别?

大家好&#xff0c;我是锋哥。今天分享关于【mysql中in和exists的区别&#xff1f;】面试题。希望对大家有帮助&#xff1b; mysql中in和exists的区别&#xff1f; 在 MySQL 中&#xff0c;IN 和 EXISTS 都是用于子查询的操作符&#xff0c;但它们在执行原理和适用场景上有所不…

MySQL高可用

一、mysql路由 1.利用路由器的连接路由特性&#xff0c;用户可以编写应用程序来连接到路由器&#xff0c;并令路由器使用响应的路由策略来处理连接来使其连接到正确的mysql数据库服务器 2.mysql route的部署方式 需要在所有数据库主机之外再打开一台主机mysql-router 配置mysql…

DDD - 微服务架构模型_领域驱动设计(DDD)分层架构 vs 整洁架构(洋葱架构) vs 六边形架构(端口-适配器架构)

文章目录 引言1. 概述2. 领域驱动设计&#xff08;DDD&#xff09;分层架构模型2.1 DDD的核心概念2.2 DDD架构分层解析 3. 整洁架构&#xff1a;洋葱架构与依赖倒置3.1 整洁架构的核心思想3.2 整洁架构的层次结构 4. 六边形架构&#xff1a;解耦核心业务与外部系统4.1 六边形架…

LS和MMSE信道估计

1️⃣ LS&#xff08;最小二乘&#xff09;信道估计 OFDM系统的信道估计常在频域进行&#xff0c;因为OFDM本身就是基于频域的。频域模型可以表示为&#xff1a; Y ( f ) X ( f ) H ( f ) Z ( f ) Y(f)X(f) H(f)Z(f) Y(f)X(f)H(f)Z(f) 其中&#xff0c; Y ( f ) Y(f) Y(f)表…

C++ strcpy和strcat讲解

目录 一. strcpy 代码演示&#xff1a; 二.strcat 代码演示&#xff1a; 一. strcpy 使⽤字符数组可以存放字符串&#xff0c;但是字符数组能否直接赋值呢&#xff1f; ⽐如&#xff1a; char arr1[] "abcdef"; char arr2[20] {0}; arr2 arr1;//这样这节赋值可…

力扣257. 二叉树的所有路径(遍历思想解决)

Problem: 257. 二叉树的所有路径 文章目录 题目描述思路复杂度Code 题目描述 思路 遍历思想(利用二叉树的先序遍历) 利用先序遍历的思想&#xff0c;我门用一个List变量path记录当前先序遍历的节点&#xff0c;当遍历到根节点时&#xff0c;将其添加到另一个List变量res中&…

JVM运行时数据区域-附面试题

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域 有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的区域随着虚拟机进程的启动而一直存在&#xff0c;有些区域则是 依赖用户线程的启动和结束而建立和销毁。 1. 程序计…

向上调整算法(详解)c++

算法流程&#xff1a; 与⽗结点的权值作⽐较&#xff0c;如果⽐它⼤&#xff0c;就与⽗亲交换&#xff1b; 交换完之后&#xff0c;重复 1 操作&#xff0c;直到⽐⽗亲⼩&#xff0c;或者换到根节点的位置 这里为什么插入85完后合法&#xff1f; 我们插入一个85&#xff0c;…

数据库备份、主从、集群等配置

数据库备份、主从、集群等配置 1 MySQL1.1 docker安装MySQL1.2 主从复制1.2.1 主节点配置1.2.2 从节点配置1.2.3 创建用于主从同步的用户1.2.4 开启主从同步1.2.4 主从同步验证 1.3 主从切换1.3.1 主节点设置只读&#xff08;在192.168.1.151上操作&#xff09;1.3.2 检查主从数…

【题解】AtCoder Beginner Contest ABC391 D Gravity

题目大意 原题面链接 在一个 1 0 9 W 10^9\times W 109W 的平面里有 N N N 个方块。我们用 ( x , y ) (x,y) (x,y) 表示第 x x x 列从下往上数的 y y y 个位置。第 i i i 个方块的位置是 ( x i , y i ) (x_i,y_i) (xi​,yi​)。现在执行无数次操作&#xff0c;每一次…