scala集合_数组_元组_映射_列表

数组
元组
映射
列表
1.11 集合(scala.collection)

集合是一种用来存储各种对象和数据的容器。Scala 集合分为可变的和不可变的集合。

1. 不可变集合可以安全的并发访问。
2. 可变集合可以在适当的地方被更新或扩展。这意味着你可以修改,添加,移除一个集合的元素。

不可变集合,相比之下,永远不会改变。不过,你仍然可以模拟添加,移除或更新操作。但是这些操作将在每一种情况下都返回一个新的集合,同时使原来的集合不发生改变。

scala集合两个主要的包:

# 不可变集合
    scala.collection.immutable (Scala默认采用不可变集合)
# 可变集合
    scala.collection.mutable

Scala的集合有三大类:序列Seq、集Set、映射Map,所有的集合都扩展自Iterable特质(暂理解为接口),意味着集合的基本特点是支持迭代遍历的。

分类描述
Seq序列。元素以线性方式存储,集合中可以存放重复对象,。参考API文档
Set集(数据集,区别于集合)。集中的对象不按特定的方式排序,并且没有重复对象。 参考 API文档
Map一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。  参考 API文档

对于可变与不可变集合,Seq、Set、Map又有不同的实现方式,下二图详细描述了其继承关系。官方文档

不可变集合结构:

可变集合结构图:

1.11.1 数组

数组元素内容要求类型一致。按照是否可扩容分为两种:

# Array
- 定长数组,数组不可扩容 scala.Array
# ArrayBuffer
- 变长数组,数组可扩容 scala.collection.mutable.ArrayBuffer
1.11.1.1 定长数组Array

Java中创建定长数组

int[] data = new int[3]; /*定义长度为3的数组*/
data[0] = 1; // 第一个元素
data[1] = 2; // 第二个元素
data[2] = 3; // 第三个元素
​
//也可以创建时初始化数组元素
int[] data=new int[]{1,2,3};

同样,Scala中也有两种创建一个定长数组的方式:

  • 使用new关键字创建一个定长数组,var arr=new Array[Int](3)

  • 直接使用Array创建并初始化一个数组,注意不再使用new关键字。var arr=Array(1,2,3)

课上练习:使用Array定义一个长度不变的数组

object ArrayDemo {
  def main(args: Array[String]){
    //初始化一个长度为8的定长数组
    val arr1 = new Array[Int](8)
    //会有初始化零值:Array[Int] = Array(0,0,0,0,0,0,...)
    //直接打印定长数组,内容为数组的hashcode值
    println(arr1)
    //将数组转换成数组缓冲,就可以看到原数组中的内容了
    //toBuffer会将数组转换长数组缓冲
    println(arr1.toBuffer)
​
    //注意:如果不使用new获取数组,相当于调用了数组的apply方法,直接为数组赋值
    //通过一组初始化值定义定长数组
    val arr2 = Array[Int](10,20,30)
    //输出数组元素值
    println(arr2.toBuffer)
    //使用()来访问元素
    println(arr2(2))
    //遍历数组
    for(i <- 0 until arr2.length)
      print(s"$i:${arr2(i)} ")  
    println()
    
    //赋初值的字符串数组
    val strs1 = Array("hello" ,"world")
    //访问并修改元素值
    strs1(0) = "byebye"
    for(i <- 0 until strs1.length)
      print(s"$i:${strs1(i)} ") 
      println()
  }
}

运行结果如下:

[I@4520ebad
ArrayBuffer(0, 0, 0, 0, 0, 0, 0, 0)
ArrayBuffer(10, 20, 30)
30
0:10 1:20 2:30 
0:byebye 1:world

定长数组是不可变集合吗?

不是。定长数组是可变集合的一种,内容可变,但是其长度不可变。

# 扩展:为什么定长数组是可变集合?
    Array本身不属于scala集合成员,从前面类继承图中也可发现这一点,在可变集合图中IndexedSeq有一条虚线指向了Array,说明并不是直接继承关系。
    一般将Array归为集合是因为Scala默认将Array隐式转换为WrappedArray,而WrappedArray实现了IndexedSeq特质。
    从这一点上来说,String与WrappedString也有异曲同工之妙,可以发现在不可变集合中,String与IndexedSeq也是虚线连接,也就是说在Scala中,String可以当集合处理。参考下文中的描述,请自行通过源码验证。
1.11.1.2 变长数组ArrayBuffer

使用 ArrayBuffer定义长度按需变化的数组。

import scala.collection.mutable.ArrayBuffer
​
object VarArrayDemo {
  def main(args: Array[String]){
    //定义一个空的可变长Int型数组
    val nums =  ArrayBuffer[Int]()
​
    //在尾端添加元素
    nums += 1
​
    //在尾端添加多个元素
    nums += (2,3,4,5)
​
    //使用++=在尾端添加任何集合
    nums ++= Array(6,7,8)
​
   //这些操作符,有相应的 -= ,--=可以做数组的删减,用法同+=,++=
      
   //使用append追加一个或者多个元素
    nums.append(1)
    nums.append(2,3)
      
   //在下标2之前插入元素
    nums.insert(2,20)
    nums.insert(2,30,30)    
​
    //移除最后2个元素
    nums.trimEnd(2)
    //移除最开始的一个或者多个元素
    nums.trimStart(1)
   
    //从下标2出移除一个或者多个元素
    nums.remove(2)
    nums.remove(2,2)     
      
   //使用增强for循环进行数组遍历   
    for(elem <- nums)
      print(elem+"  ")
      
    println()
    //基于下标访问使用增强for循环进行数组遍历
    for(i <- 0 until nums.length)
      print(nums(i)+"  ")
  }
}

执行结果:

2  30  4  5  6  7  8  1  
2  30  4  5  6  7  8  1
1.11.1.3 定长数组与变长数组的转换
arr1.toBuffer  //转为变长
arr2.toArray     //转为定长
1.11.2 元组(Tuple)

Scala Tuple表示固定元素的组合,元组可以装着多个不同类型的值,是不同类型的值的聚集。Tuple是Scala中非常重要的一种数据结构,后面会大量使用。其特点包括:

1.最多支持22个元素组合,分别对应类型Tuple1~Tuple22,相应也称为一元组(一般不用)、二元组、三元组...
2.元组可以容纳不同类型的元素
3.元组不可变

==特别地:==二元组可以表示Map中的一个元素,Map是K/V对偶的集合,对偶是元组的最简单形式。

1.11.2.1 创建访问元组

创建元组:使用小括号()将多个元素括起来,元素之间用逗号分隔,元素的类型和个数不超过22。

访问组元:使用_1_2_3等形式访问组元,注意下标从1开始。

第二种方式定义的元组也可以通过abc 去访问组元。

val t = ("qianfeng","scala",1) //定义元组
val t_1 = t._1   //取元组第一个值

另一种定义方式:

val tuple3 = new Tuple3(1, 3.14, "Fred")

元组的实际类型取决于它的元素的类型,比如 :

  • (99, "runoob") 实际类型是 Tuple2[Int, String]

  • ('u', 'r', "the", 1, 4, "me")实际类型 为 Tuple6[Char, Char, String, Int, Int, String]

目前 Scala 支持的元组最大长度为 22。对于更大长度你可以使用集合,或者样例类(case class)。

1.11.2.2 元组访问
//注意元组元素的访问有下划线,并且访问下标从1开始
val value1 = tuple3._3
println(value1)
// 按照索引访问元组的第一个元素,从0开始
val value2 = tuple3.productElement(0) 
println(value2)
1.11.2.3 元组遍历

因为元组本身不是集合成员,所以元组不能作为for生成器的表达式。但元组实现了productIterator()方法,该方法可以生成元组的迭代器Iterator(继承自TraversableOnce),迭代器提供了遍历集合的方法。这也是通常我们将Tuple视为集合的原因。

方式1:

for (elem <- tuple1.productIterator) {
  print(elem)
}
println()

方式2:

t.productIterator.foreach(i => println(i)) //foreach是高阶函数
t.productIterator.foreach(print(_))
1.11.3 映射(Map)

在Scala中,把哈希表这种数据结构叫做映射。Scala中的Map存储的内容是键值对(key-value),Map区分可变Map (scala.collection.mutable.Map) 和不可变Map(scala.collection.immutable.Map) 。不可变的Map(仅TreeMap)支持有序,而可变的Map是无序的。

1.11.3.1 构建映射

Map中的元素为二元组,可用两种方式表示。

("a",1)
"a"->1

课上练习

//构建一个不可变的Map,默认即为不可变Map
//其中的元素其实是Tuple2
val scores = Map("zhangsan"->90,"lisi"->80,"wangwu"->70)
​
//使用元组方式构建
val scores = Map(("zhangsan",90),("lisi",80),("wangwu",70))
​
//构建一个可变的map,注意包名
val scores = scala.collection.mutable.Map(("zhangsan",90),("lisi",80),("wangwu",70))
1.11.3.2 访问映射中的值

根据键获取map中对应的值,可以有以下三种方法,推荐使用getOrElse方法。

//如果key存在,则返回对应的值
//如果key不存在,则抛出异常[java.util.NoSuchElementException]
//在Java中,如果key不存在则返回null
val score1 = scores("lisi")
​
//使用contains方法检查是否存在key对应的值 
//使用containts先判断在取值,可以防止异常,并加入相应的处理逻辑
// 返回Boolean,true或者false
// 如果key存在,则返回true
// 如果key不存在,则返回false
map4.contains("B")
​
val score2 = if(scores.contains("lisi")) scores("lisi") else 0
​
//使用get方法取值,返回Option对象,Some或者None
//如果返回some,可以进一步通过get方法取回相应的值
//如果返回None,通过get方法取值,抛出异常 java.util.NoSuchElementException: None.get
var map4 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) )
println(map4.get("A")) //Some
println(map4.get("A").get) //得到Some在取出
​
//使用getOrElse()取值
//def getOrElse[V1 >: V](key: K, default: => V1)
//如果key存在,返回key对应的值。
//如果key不存在,返回默认值。
val score3 = scores.getOrElse("lisi",0)
1.11.3.3 修改可变Map信息

遍历访问map

//修改键对应的值
//可变的map才能修改
//key存在,则修改对应的值,key不存在,则添加键值对
scores("lisi") = 100
println(scores)
scores.update("lisi",50)
println(scores)
​
//添加单个元素-方式1,如果key存在,则修改相应key的值。
scores("zhaoliu") = 88
println(scores)
​
//添加单个元素-方式2
scores +=("tom"->77)
println(scores)
​
//添加多个元素-方式1
scores = scores + ("tom"->77,"jerry"->88)
scores +=("tom"->77,"jerry"->88)
​
val scores2 = Map(("za",90),("lq",80),("wg",70))                      
//添加多个元素-方式2
scores ++= scores2
​
//移除键值对
scores-"lisi"
​
//移除多个键一
scores--List("zhangsan","tom")
​
//移除多个键二
scores-("lisi","lq")
1.11.3.4 遍历map
//遍历
//返回一个set集合
val res = scores.keySet
​
for(elem <- res)
   print(elem + "  ")
​
//返回Map中所有key的迭代器 set结合
val ite = scores.keys
​
//返回Map中所有值的迭代器
val values = scores.values
​
//返回键值对
for (item <- scores)
    print(item+" ")
​
​
//使用k、v表示二元组中的键和值
for ((k,v) <- scores)
   print(k+":"+v+"  ")
1.11.4 列表(List)

Scala 列表类似于数组,它们所有元素的类型都相同,但是它们也有所不同:列表是不可变的,值一旦被定义了就不能改变,其次列表是链表结构,而数组不是。

1.列表中的元素类型必须相同。
2.列表是有序的。
3.列表是不可变的,内容及长度都不可变。
1.11.4.1 List的创建和访问
object ImmutListDemo {
    def main(args: Array[String]) {
      //创建一个不可变的集合
      val lst1 = List(1,2,3)
      //将0插入到lst1的前面生成一个新的List ::和+:右结合
      // ::为右结合操作符
      // 将元素追加到集合开头
      val lst2 = 0 :: lst1
      val lst3 = lst1.::(0)  //等价上诉写法
      val lst4 = 0 +: lst1
      val lst5 = lst1.+:(0)  //等价上诉写法
      //将一个元素添加到lst1的后面产生一个新的集合,:+左结合
      val lst6 = lst1 :+ 3
      val lst0 = List(4,5,6)
      //将2个list合并成一个新的List  ++ 左结合
      val lst7 = lst1 ++ lst0
      //将lst1插入到lst0前面生成一个新的集合  ++: 左结合
      val lst8 = lst1 ++: lst0
      //将lst0插入到lst1前面生成一个新的集合 ::: 右结合
      val lst9 = lst1.:::(lst0)
      println(lst9)
      println(lst1(2))
    }
}
1.11.4.2 List的遍历
/**
 * List的各种遍历方式
 */
val lst = List(1,2,3,4,5);
print("foreach遍历:")
lst.foreach { x => print(x+",")}  //foreach遍历,这个是传统遍历,新手不熟无奈之下可以用它
println("")
​
var temp = lst.map { x => x+1 }   //遍历,与foreach的区别是返回值为List【B】
println("map遍历:"+temp.mkString(","))
1.11.4.3 List的基本操作

对列表的所有操作可以表达为一下三种

head 返回列表的第一个元素。 tail 返回除第一个元素之外所有元素组成的列表。

isEmpty 返回列表是否为空。

注意:其中tail head作用在空列表上,会报异常。

val list0 = List(1,2,3)
val list1 = List(4,5,6)
// head:返回列表第一个元素
list0.head // Int = 1
// tail:返回除了第一个元素之外的其他元素,以列表返回
list0.tail // List[Int] = List(2, 3)
// isEmpty:判断列表是否为空,为空时返回true
list0.isEmpty // Boolean = false
// concat:连接列表,返回一个新的集合
List.concat(list0,list1) // List[Int] = List(1, 2, 3, 4,5, 6)
// fill:使用数个相同的元素创建一个列表
List.fill(10)(2) // 重复次数:10,重复元素:2
// reverse:将列表顺序反转,返回一个新的集合
list0.reverse
​
//求列表长度
list0.length
1.11.4.4 可变列表ListBuffer

ListBuffer与List的区别在于:修改ListBuffer改变的是本身。

import scala.collection.mutable.ListBuffer
​
object MutListDemo extends App{
  //构建一个可变列表,初始有3个元素1,2,3
  val lst0 = ListBuffer[Int](1,2,3)
  //创建一个空的可变列表
  val lst1 = new ListBuffer[Int]
  //向lst1中追加元素,注意:没有生成新的集合
  lst1 += 4
  lst1.append(5)
​
  //将lst1中的元素最近到lst0中, 注意:没有生成新的集合
  lst0 ++= lst1
​
  //将lst0和lst1合并成一个新的ListBuffer 注意:生成了一个集合
  val lst2= lst0 ++ lst1
​
  //将元素追加到lst0的后面生成一个新的集合
  val lst3 = lst0 :+ 5
}
1.11.5 Set

Set中的元素不可重复,无序(TreeSet除外)。

1.11.5.1 创建不可变集合Set
import scala.collection.immutable.HashSet
val set0=Set(1,2,3,4,5)
println(set0.getClass)  //scala.collection.immutable.HashSet
​
val set1 = new HashSet[Int]()   //默认的Set
// 可以使用加号追加元素,会产生一个新的集合,原有set不变
val set2 = set1 + 1
// Set集合中不会出现重复的元素
val set3 = set2 ++ Set(1,2,3)
val set4 = Set(1,2,3,4,5,6)
// Set集合中的元素是无序的
print(set4)
​
​
// SortedSet中是有序的
val set5=scala.collection.immutable.TreeSet(1,1,10,5,6,7,8,11,13)
1.11.5.2 创建可变集合Set
import scala.collection.mutable

object MutSetDemo extends App{
  
   val mutableSet = Set(1,2,3)
   println(mutableSet.getClass.getName) // scala.collection.mutable.HashSet
 
  //创建一个可变的HashSet
  val set1 = new mutable.HashSet[Int]()
  //向HashSet中添加元素
  set1 += 2
  //add等价于+=
  set1.add(4)
  //删除一个元素,如果删除的对象不存在,则不生效,也不会报错

  set1 -= 5
  set1.remove(2)
  println(set1)
}

Guff_hys_python数据结构,大数据开发学习,python实训项目-CSDN博客 

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

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

相关文章

云计算在数字营销中的作用是什么?

营销策略和云计算是一个为企业提供多种优势的系统。它使他们能够取得更大的成功&#xff0c;同时提高产量。这样做的原因是&#xff0c;可以从任何位置远程使用云集成工具和应用程序。基本上&#xff0c;该系统增强了存储设备和传播。同时&#xff0c;它减轻了公司 IT 网络的压…

《Effective C++》学习笔记

条款01&#xff1a;把 C 看成一个语言联邦 C由几个重要的次语言构成 C语言&#xff1a;区块&#xff0c;语句&#xff0c;预处理器&#xff0c;数组&#xff0c;指针等等。 类&#xff1a;class&#xff0c;封装&#xff0c;继承&#xff0c;多态......&#xff08;动态绑定等…

MyBatis-Plus - 论自定义 BaseMapper 方法『逻辑删』失效解决方案

问题描述 在上一篇我们讲过 MyBatis-Plus - 论 1 个实体类被 N 个DAO 类绑定&#xff0c;导致 MP 特性&#xff08;逻辑删&#xff09;失效的解决方案-CSDN博客 所以在这个基础上&#xff0c;我们可以很好定位到源码的分析位置。 但是今天这个问题就更奇怪了&#xff0c;已经…

apt-get update失败

一、先验证是否有网络 rootlocalhost:~# ping www.baidu.com ping: www.baidu.com: Temporary failure in name resolution rootlocalhost:~# 说明没有网&#xff0c;参考&#xff1a;https://blog.csdn.net/qq_43445867/article/details/132384031 sudo vim /etc/resolv.con…

巧用ChatGPT系列丛书(由北京大学出版社出版)

前言 随着人工智能技术的迅速发展&#xff0c;越来越多的工具和应用程序被应用于职场中&#xff0c;以提高我们的工作效率。其中&#xff0c;ChatGPT作为一种先进的自然语言处理技术&#xff0c;正在逐渐引起人们的关注。 ✨巧用ChatGPT系列书籍&#xff1a; 《巧用chatGPT快…

抖去推--短视频剪辑、矩阵无人直播saas营销工具一站式开发

抖去推是一款短视频剪辑和矩阵无人直播SAAS营销工具一站式开发平台。它提供了以下功能和特点&#xff1a; 1. 短视频剪辑&#xff1a;抖去推提供了一系列的剪辑工具&#xff0c;包括自动剪辑、特效制作、配音配乐等&#xff0c;可以帮助用户轻松制作出高质量的短视频。 2. 矩阵…

使用curl在Linux系统上进行HTTP请求代码示例

在Linux系统上&#xff0c;curl是一个非常实用的命令行工具&#xff0c;用于进行HTTP请求。下面是一些使用curl进行HTTP请求的示例代码。 获取网页内容 bash复制代码 curl https://www.example.com 这个命令会向https://www.example.com发送一个GET请求&#xff0c;并将返回的…

接口自动化多层嵌套json数据处理代码实例

最近在做接口自动化测试&#xff0c;响应的内容大多数是多层嵌套的json数据&#xff0c;在对响应数据进行校验的时候&#xff0c;可以通过&#xff08;key1.key2.key3&#xff09;形式获取嵌套字典值的方法获取响应值&#xff0c;再和预期值比较 1 2 3 4 5 6 7 8 9 10 11 12 13…

前期只用到审批流程,好用的OA软件有哪几个?

“前期只用到审批流程&#xff0c;需要五六个人层层审批&#xff0c;最好审批流程是免费的&#xff0c;后期会扩展到其它功能&#xff0c;有适合我的软件吗&#xff1f;” 先来总结一下题主的需求&#xff1a; OA系统中审批流程最好是免费的流程需要层层审批后期能够扩展到其…

企业展厅 | OLED薄纸屏:屏幕与安装环境融为一体

企业展厅 | OLED薄纸屏&#xff1a;屏幕与安装环境融为一体 产品&#xff1a;55寸OLED柔性屏 项目时间&#xff1a;2023年11月 项目地点&#xff1a;浙江嘉兴 应用场景&#xff1a;通过OLED柔性屏具有色彩鲜艳、可弯曲等特性&#xff0c;满足对安装环境的灵活展示和需求&…

CTF V8 pwn入门(一)

仍然是因为某些原因&#xff0c;需要学学浏览器pwn 环境 depot_tools建议直接去gitlab里下&#xff0c;github上这个我用魔法都没下下来 下完之后执行 echo export PATH$PATH:"/root/depot_tools" >> ~/.bashrc路径换成自己的就ok了 然后是ninja git clo…

企业IT安全:内部威胁检测和缓解

什么是内部威胁 内部威胁是指由组织内部的某个人造成的威胁&#xff0c;他们可能会造成损害或窃取数据以谋取自己的经济利益&#xff0c;造成这种威胁的主要原因是心怀不满的员工。 任何内部人员&#xff0c;无论是员工、前雇员、承包商、第三方供应商还是业务合作伙伴&#…

商城免费搭建之java商城 java电子商务Spring Cloud+Spring Boot+mybatis+MQ+VR全景+b2b2c 鸿鹄云商

鸿鹄云商 SAAS云产品概述 【SAAS云平台】打造全行业全渠道全场景的SaaS产品&#xff0c;为店铺经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多…

智能优化算法应用:基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鸡群算法4.实验参数设定5.算法结果6.参考文献7.MA…

瑞盟OP07运算放大器可Pin to Pin兼容OP07

瑞盟 OP07 是一款低失调电压的运算放大器&#xff0c;它采用晶圆级的修调来消除失调&#xff0c;同时还可以通过外部电路进一步减小失调电压。可Pin to Pin兼容OP07。同时具有极低的偏置电流&#xff08;只有 4nA&#xff09;以及很高的开环增益&#xff08;最小 200V/mV&#…

最新可运营版博客流量主小程序

源码简介 全新修复版的博客流量主小程序【运营版】&#xff0c;现在支持个人用户搭建自己的小程序&#xff0c;发布文章&#xff0c;还能通过查看文章和点击下载来观看广告。这个版本经过多次更新&#xff0c;解决了一些问题&#xff0c;让用户体验更加顺畅。 我们听到了你们…

生活中必不可缺的免费的解压文件APP

解压精灵 解压&#xff01;我真的要极力推荐&#xff01;&#xff01; 手机软件商店里下载的解压软件只能免费解压几次&#xff1f;用几次就要开通vip才可以解压&#xff0c;给宝子们推荐一款免费的解压软件&#xff0c;速度是普通解压软件的数倍&#xff0c;并且免费无限使用&…

数学常识 公理·定义·定理·引理·推论的区别

数学常识 公理定义定理引理推论的区别 事先规定出来的公理&#xff08;Axiom&#xff09;定义&#xff08;Definition&#xff09; 推论出来的定理&#xff08;Theorem&#xff09;引理&#xff08;Lemma&#xff09;推论&#xff08;Corollary&#xff09; 逻辑图地址 事先规定…

HCIA-H12-811题目解析(9)

1、【单选题】下面选项中&#xff0c;能使一台IP地址为10.0.0.1的主机访问Interne的必要技术是&#xff1f; 2、【单选题】 FTP协议控制平面使用的端口号为&#xff1f; 3、【单选题】 使用FTP进行文件传输时&#xff0c;会建立多少个TCP连接&#xff1f; 4、【单选题】完成…

打工人副业变现秘籍,某多/某手变现底层引擎-Stable Diffusion 真人照片转动漫风格

相信我们很多人在看过动漫/动画后,都想看一看二次元世界中的自己长什么样子,那今天就以客户照片为例,说说我们如何用 Stable Diffusion,让 AI 帮我们将真实照片转成一个绝美二次元少女,Let’s do it~ 客户原图照片如下,希望转成二次元甜美少女。 1. 打开 Web UI …