边写软件边学kotlin(二)——语法推进

(一)TypeToken

是一个类,因Google的Gson库为了解决Java原生中泛型擦除的痛点而生,通常搭配匿名内部类使用。

val listType =object: TypeToken<List<InterviewPost>>(){}.type Gson().fromJson<List<InterviewPost>>(jsonString, listType)
  1. object :…(){}:是kotlin语言中用以创建匿名内部类的一种写法
  2. 泛型擦除:在Java语言中,在将json字符串转成对象的过程中,只知“数据结构”,不知“数据类型”,例如想将json字符串转换成List< User >类型,转换的时候只知道要转换成list列表,但是不知道里面是User类型。这就是泛型擦除
  3. TypeToken解决方式:通过创建一个匿名内部类,可以通过反射拿到该类的父类信息,这个类的父类信息储存了我们需要的数据类型
  4. .type:获取java.lang.reflect.type对象,也就是我们在Java代码中执行反射操作的东西

(二)

val assetManager = getApplication<Application>().assets val inputStream = assetManager.open("interview_data.json")val reader =BufferedReader(InputStreamReader(inputStream))
  1. getApplication< Application >():获取当前的全局单例上下文对象context,说白了就是当前这个应用的实例
  2. getApplication< Application >().assets:获取这个应用下的针对assets包的AssetsManager资源管理器,可以访问该包下的目录,这个资源管理器可以读取原始字节流
  3. assetManager.open:获取该包下从文件到内存的字节输入流

(三)

viewModelScope.launch{val loadedPosts =withContext(Dispatchers.IO){try{val assetManager = getApplication<Application>().assets val inputStream = assetManager.open("interview_data.json")val reader =BufferedReader(InputStreamReader(inputStream))val jsonString = reader.use{ it.readText()}val listType =object: TypeToken<List<InterviewPost>>(){}.type Gson().fromJson<List<InterviewPost>>(jsonString, listType)}catch(e: Exception){ e.printStackTrace() emptyList<InterviewPost>()}} _posts.value = loadedPosts }
  1. .launch:发起一个线程
  2. withContext(Dispatchers.IO)withContext:切换到另一个线程,Dispatcher.IO:标明这是一个专门用来处理IO操作的线程
小tips:在kotlin中,可以出现定义在后,使用在前的情况正则表达式:是一种用来匹配和处理字符串的工具

(四)

for(i in0 until 5){// 1. 尝试获取 AI 返回的维度名,如果获取不到或为空,则使用默认侧写val defaultList =listOf("核心技能","综合表现","成长潜力","行业认知","实践能力")val dName = dimArray?.optString(i)?.takeIf{ it.isNotBlank()&&!it.contains("维度")}?: defaultList[i] newDims.add(dName)// 2. 尝试获取分值,并进行归一化和钳位var v = valArray?.optDouble(i,0.5)?.toFloat()?:0.5fif(v >1.0f) v /=100fval finalVal = v.coerceIn(0.1f,1.0f)// 最小值 0.1 保证雷达图有形状 newVals.add(finalVal) totalSum += finalVal }
  1. optString:获取json中的字符串类型的数据,与getString()方法不同的是,即使获取到的字符串为空,也不会抛出异常。
  2. optDouble:获取json中的double类型的数据,与getDouble()方法不同的是,即使获取到的数据为0.0f,也不会抛出异常
  3. . takeif():如果对象满足括号里面的条件,则返回对象本身。
  4. coerceIn:将值限制在一个区间内,例如coerceIn(0.1f, 1.0f)就是将值限制在0.1~1.0之间

(五)

aiScore =(totalSum /5f*100).roundToInt().toString()

roundToInt:将浮点数四舍五入转换成整数

在安卓开发中,有一个类叫Bitmap,他代表图片,图片一般在内存中以字节数组,流,文件的形式存储,而BitmapFactory负责从内存中读取这些数据转换为Bitmap对象方便操作,也可以把图片转换成字节数组,流,文件。如下图所示
val options = BitmapFactory.Options().apply{ inJustDecodeBounds =true}
  1. .options():表示对BitmapFactory对象的一些参数的设置
  2. .apply:对这些参数对象进行设置并返回它们

(六)

context.contentResolver.openInputStream(uri)?.use{ input -> BitmapFactory.decodeStream(input,null, options)}
  1. contentResolver:用来访问和操作内容提供者中的数据
  2. BitmapFactory.decodeStream(input, null, options):打开从图片文件到内存的输入流,获取图片输入进来的流,对流进行解码,null表示不设置解码区域(即解码整个区域),options表示使用对BitmapFactory使用刚才的设置参数
?:后面既可以是一个具体的返回结果,也可以是一段执行逻辑
val outputStream =ByteArrayOutputStream()
 -创建一个字节输出流的对象,这个对象是可以动态扩展字节数组的,因此不需要预先设置数组的大小 
bitmap.compress(Bitmap.CompressFormat.JPEG,70, outputStream)
 将bitmap对象压缩成原来70%的质量,并以JPEG的形式存储在outputStream这个字节数组文件中 
val bytes = outputStream.toByteArray()
 将字节输出流中的字节数据转成字节数组 
Base64.encodeToString(bytes, Base64.NO_WRAP)
 将字节数组转换成Base64格式的字符串,并且(No_WRAP)去除任何换行符 
为什么要去除换行符:因为Base64编码的过程中可能每76个字符就会加一个换行符,这些换行符可能会影响处理结果Base64编码:将二进制数据转换成ASCII码,字节数组就是二进制数据,所以才会用字节数组进行Base64编码

(七)

class A{init{}}

init{}:类似Java中的构造代码块,在类创建实例的时候也会一并执行

(八)

val searchQuery: StateFlow<String>= _searchQuery.asStateFlow()
  1. val:表示searchQuery是一个不可变的变量,也就是说这个变量的引用不变了
  2. StateFlow< String >:是一个发布String类型数据的流,这个流的状态的变化可以被外界观察,使用asStateFlow(),就可以将StateFlow变成不可变的StateFlow,使其只能被订阅而不能被改变
val uiList: StateFlow<List<CompanyListItem>>=combine( _searchQuery, _selectedIndustry ){ query, industry ->Triple(query, industry, allCompanyData)}.map{(query, industry,data)->// 在后台线程进行数据处理if(query.isEmpty()&& industry ==null){emptyList()}else{generateUiList(query, industry,data)}}.flowOn(Dispatchers.Default)// 确保计算在后台线程.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue =emptyList())
  1. combine:用以合并流,代码中就是将_searchQuery和_selectedIndustry两个流进行合并,有任何一个流的值发生改变都会触发合并操作
  2. Triple:可以固定存储三个不限数据类型的数据的容器,例如:
val triple =Triple("Hello",42,true)

就是将字符串,数字,布尔值统一放在一个容器中

(九)

.flowOn(Dispatchers.Default)
  1. flowOn:字面意义上理解就是“流在哪里”,意思就是线程在哪里执行
  2. Default:默认后台线程,就是说把这个线程放在了后台进行执行

(十)

.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue =emptyList())
  1. stateIn():将flow变成一个可被观察状态的容器
  2. scope:设置生命周期,它的生命周期和viewModelScope一样长
  3. started:设置需要重新计算的时间阈值。例如切出屏幕,5秒内再回来就不会重新计算,屏幕如初,5秒之后再回来,就会重新计算(Flow计算链)

(十一)

companies.filter{ it.name.contains(query, ignoreCase =true)}
  1. filter:筛选出符合括号内条件的元素(自动实现了循环逻辑
  2. ignoreCase:忽略大小写

(十二)

val grouped = filtered.groupBy{ it.pinyinFirstLetter } colors = TextFieldDefaults.colors( focusedContainerColor = Color.Transparent, unfocusedContainerColor = Color.Transparent, disabledContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent ), singleLine =true
  1. groupBy:按括号内的条件进行组分,其返回值是一个Map,“键”是单个字符:A,B,C,D。“值”是原本filtered内部的值
  2. it.pinyinFirstLetter:它的拼音首字母
  3. focusedContainerColor :获得聚焦时容器的颜色。unfocusedContainerColor :没活得聚焦时容器的颜色。disabledContainerColor :被禁用时的容器颜色。focusedIndicatorColor:获得聚焦时下划线的颜色
  4. singleLine = true:将输入框设置成仅支持一行,也就是不能换行

(十三)

funCompanySearchScreen( onBack:(()-> Unit)?=null, showSearchBar: Boolean =true, viewModel: CompanySearchViewModel =viewModel())val searchQuery by viewModel.searchQuery.collectAsState()
  1. kotlin语言中的小特性:括号内可以赋入初始值,而Java语言不行
  2. val searchQuery by viewModel.searchQuery.collectAsState()其实就等于
val state = viewModel.searchQuery.collectAsState()val searchQuery = state.value()by语法的作用就是获取值 
  1. collectAsState:将Flow数据流变成Compose可监听的state

(十四)

remember{}

将括号内的对象进行记忆,使得界面重组,重新调用函数的时候不会再把创建新对象的代码再跑一遍。而是直接拿第一次创建好的对象去用。有个好处就是之前的数据不会丢

(十五)

funProfileSectionCard(title: String, content:@Composable ColumnScope.()-> Unit){Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.White), shape =RoundedCornerShape(12.dp)){Column( modifier = Modifier.padding(16.dp)){Text(text = title, fontWeight = FontWeight.Bold, fontSize =15.sp)Spacer(modifier = Modifier.height(12.dp))content()}}}
  1. @Composable:表明这个函数是用来写UI的
  2. ColumnScope:Column的内部环境
  3. ()->Unit:表明这是一个函数
    content: @Composable ColumnScope.() -> Unit:组合在一起就是content是一个函数,可以在Column的内部环境运行,并且可以往column里面写UI

(十六)

object QuestionRepository {fungetQuestionsByCategory(category: String): List<Question>{return(1..150).map{Question(id = it, title ="[$category] 第 $it 题", answer ="", category = category, passRate ="${(60..95).random()}%")}}}

object:与class的区别就是,class可以创建多个实例,而object所创建出来的对象全局唯一

(十七)

privateval industryNamesMap =mapOf('互'to'H','金'to'J','医'to'Y','食'to'S','政'to'Z','教'to'J','制'to'Z','房'to'F','新'to'X','物'to'W','汽'to'Q','半'to'B','游'to'Y','传'to'C','零'to'L','咨'to'Z','法'to'F','建'to'J','化'to'H','航'to'H','旅'to'L','农'to'N','环'to'H','硬'to'Y','体'to'T')

作用:创建一个哈希表,并把一个一个元素对应起来

(十八)

val firstChar = name.firstOrNull()?:return'#'if(firstChar in'A'..'Z')return firstChar if(firstChar in'a'..'z')return firstChar.uppercaseChar()
  1. name.firstOrNull():找到name这个字符串的第一个字符并返回,如果name字符串为空则返回”#“
  2. uppercaseChar:将小写字母转成大写字母

(十九)

returnwhen(firstChar){'阿','爱','安'->'A''百','比','哔','保','北','碧'->'B''长','腾','传'->'C'// 腾->T, 但这里简单处理'滴','德','叠'->'D''饿'->'E''富','复','粉'->'F''广','工','贵','格','高','国'->'G''华','海','恒','好'->'H''阿'->'J'// 修正'京','吉','金','巨','晶'->'J''快','科'->'K''理','隆','龙','联','立'->'L''美','蚂','迈','蒙','明','米'->'M''宁','农','南'->'N''拼','平','片'->'P''奇','企','去','青'->'Q''人','日'->'R''三','搜','顺','申','上'->'S''腾','泰','天','通'->'T''阿'->'W''万','微','蔚','网','韦','五','娃'->'W''小','携','新','西'->'X''云','优','猿','药','英','圆','韵','伊','阳'->'Y''字','中','知','招','紫','兆','智','作'->'Z'else->if(industryNamesMap.contains(firstChar)) industryNamesMap[firstChar]!!else'Z'// 兜底}
  1. when():具有返回值,根据括号中的变量的不同情况返回不同值,类似于Java中的switch结构。举一个例子,在这段代码中,如果检测出firstChar是’阿’ 或者’安’或者 ‘爱’,就返回一个‘A’
  2. industryNamesMap[firstChar]:是kotlin中对哈希表中value值的调取方式
  3. !!:强制非空,如果为空则报错

(二十)

return industries.associateWith{ industry ->// 关键修复:先对原始数据去重,防止 LazyColumn 因 key 重复而崩溃val existingNames = realData[industry]?.distinct()?:emptyList()// 使用 MutableSet 来存储,既能保持去重,又能快速查找val fullSet =LinkedHashSet(existingNames)val baseList = existingNames.ifEmpty{listOf("$industry 行业知名企业")}val suffixes =listOf("分公司","研发中心","技术部","营销中心","办事处","控股子公司","集团")var suffixIndex =0var nameIndex =0while(fullSet.size <100){// 循环使用已有真实大厂名字 + 后缀val baseName = baseList[nameIndex % baseList.size]val suffix = suffixes[suffixIndex % suffixes.size]val newName =if(baseName.contains("No."))"$baseName${fullSet.size +1}"else"$baseName$suffix" fullSet.add(newName) nameIndex++if(nameIndex % baseList.size ==0){ suffixIndex++}}// 转换为 Company 对象并按拼音首字母排序 fullSet.map{ name ->Company(name,getPinyinFirstLetter(name))}.sortedBy{ it.pinyinFirstLetter }}
  1. .distinct:将realData[industry]去重
  2. LinkedHashSet():能对列表去重并保留顺序
  3. .map:把前面的列表元素映射成另一种新元素,并返回新列表
  4. .sortedBy { it.pinyinFirstLetter }:把新列表按照拼音顺序排列

Read more

百瑞互联(barrot)蓝牙,手柄,键盘,鼠标,蓝牙适配器,智能家居,蓝牙6.0。(BR8652,BR8654)

描述 BR8654A02 是北京百瑞互联(BARROT)推出的一款高度集成蓝牙 6.0 SOC 芯片,专为无线数据传输和智能互联设备打造。芯片整合了低功耗处理器、RF 收发器、多协议接口及电源管理单元,具备低功耗、适配范围广、连接稳定的特点,能满足各类蓝牙相关产品的无线通信需求,广泛适用于蓝牙 HID 设备、智能家居、遥控器、玩具、Mesh 网络及数据通信产品。 环境与封装参数 * 蓝牙 6.0 合规,连接更高效:兼容蓝牙 6.0 规范,支持 LE 1M/2M/Coded PHY 及 BR/EDR,可同时维护多链路连接(最多 4 个 BLE

OpenClaw配置飞书机器人完整指南

OpenClaw配置飞书机器人完整指南 使用openclaw channels add配置飞书机器人需完成插件安装→飞书应用创建→通道配置→事件订阅→发布应用五个核心步骤,以下是可直接执行的详细流程。 文章目录 * OpenClaw配置飞书机器人完整指南 * 一、前置准备 * 二、通道配置(openclaw channels add) * 方法1:交互式向导配置(推荐) * 方法2:非交互式命令配置(适合脚本) * 方法3:手动编辑配置文件 * 三、事件订阅与发布(关键步骤) * 四、测试与验证 * 五、常见问题排查 一、前置准备 1. 飞书开放平台创建应用(获取凭证) 1. 访问飞书开放平台:https://open.feishu.cn/app 2. 创建企业自建应用,填写名称(如"

机器人笔记——轨迹规划

机器人笔记——轨迹规划

前言 之前的文章讲过到了关节是持续运动的,雅可比矩阵正是描述关节运动与机器人末端运动映射关系的有力工具。然而有了如何映射的工具仅仅是分析机器人运动的开始,要知道空间两点间的运动轨迹是多样的,因此就产生了轨迹规划的概念。这里讲的轨迹规划可以理解为寻求最优路径的过程,下文对其展开介绍。  前序内容 * 机构自由度的计算 * 齐次变换与齐次变换矩阵的计算 * 机器人正运动学——学习笔记 * 机器人正运动学实例——PUMA560机械臂(附Matlab机器人工具箱建模代码) * 机器人逆运动学——以六自由度机器人为例(详解、易懂,附全部Matlab代码) * 双平行四边形码垛机械臂的运动学正逆解——简化方法(附完整Matlab代码、解析过程) * 机器人笔记——关于atan2与atan的区别 * 雅可比矩阵——机器人笔记(简化、易懂) 1. 什么是机器人轨迹规划? 想要解答这个问题,我们先来看什么是轨迹。 轨迹:就是机器人手臂(末端点或操作点)的位置、速度、加速度对于时间的历程; 我们在意的其实是,机器人末端轨迹对于工件的状态或相对关系,就像下面右侧图一样

论文阅读:Training language models to follow instructions with human feedback

Ouyang L, Wu J, Jiang X, et al. Training language models to follow instructions with human feedback[J]. Advances in neural information processing systems, 2022, 35: 27730-27744. 引言 引言首先指出了当前大型语言模型(LMs)存在的一个核心问题:模型规模变大并不意味着它们能更好地遵循用户的意图 。具体而言,大型模型经常生成不真实、有毒或对用户毫无帮助的输出,这是因为语言模型的训练目标(预测网页上的下一个 token)与用户希望的目标(“有用且安全地遵循指令”)是错位的。作者的目标是让模型在“有用性”(Helpful)、“诚实性”(Honest)和“无害性”(Harmless)这三个方面与用户意图对齐。