ChicBian的博客


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

  • 站点地图

  • 公益404

Andorid面试总结

发表于 2018-08-01

Andorid 面试总结

给出我面试 Andorid 中被问的问题,以及解决方案。

  1. Activity 的生命周期

    onCreate() — onStart() — onResume() — onPause() — onStop() — onDestory()

  2. ListView 的优化方案

    ps: 其实我建议采用 RecycleView 来代替 ListView ,后面说会 两者之间的区别。

    1. converView 复用

      利用好 convertView 来重用 View,切忌每次 getView() 都新建。ListView 的核心原理就是重用 View,如果重用 view 不改变宽高,重用View可以减少重新分配缓存造成的内存频繁分配/回收。

    2. ViewHolder 优化

      使用ViewHolder的原因是findViewById方法耗时较大,如果控件个数过多,会严重影响性能,而使用ViewHolder主要是为了可以省去这个时间。通过setTag,getTag直接获取View。

      代码类似如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      class ViewHolder{
      ImageView img;
      TextView name;
      }
      public View getView(int position, View convertView, ViewGroup parent) {
      ViewHolder holder = null;
      if(convertView==null){
      convertView = inflater.inflate(R.layout.list_item, parent, false);
      holder.img = (ImageView) convertView.findViewById(R.id.img);
      holder.name = (TextView) convertView.findViewById(R.id.name);
      holder = new ViewHolder();
      convertView.setTag(holder);
      }else{
      holder = (ViewHolder) convertView.getTag();
      }
      //设置holder
      holder.img.setImageResource(R.drawable.ic_launcher);
      holder.name.setText(list.get(position).partname);
      return convertView;
      }
    3. 图片加载优化

      如果ListView需要加载显示网络图片,我们尽量不要在ListView滑动的时候加载图片,那样会使ListView变得卡顿,所以我们需要在监听器里面监听ListView的状态,如果ListView滑动(SCROLL_STATE_TOUCH_SCROLL)或者被猛滑(SCROLL_STATE_FLING)的时候,停止加载图片,如果没有滑动(SCROLL_STATE_IDLE),则开始加载图片。

      其实 RecycleView 已经解决了这个问题。

      • RecyclerView 和 ListView 的区别:

      ​ RecyclerView可以完成ListView,GridView的效果,还可以完成瀑布流的效果。同时还可以设置列表的 滚动方向(垂直或者水平);

      RecyclerView中view的复用不需要开发者自己写代码,系统已经帮封装完成了。

      RecyclerView可以进行局部刷新。

      RecyclerView提供了API来实现item的动画效果。

      在性能上:

      如果需要频繁的刷新数据,需要添加动画,则RecyclerView有较大的优势。

      如果只是作为列表展示,则两者区别并不是很大。

  3. Webview 和 js 的交互

    Android 调用 js 方法:

    1. webView.loadUrl(url),url 可以是一个地址,也可以是一个类似于 javascript:retrunpage() 这样一个定义在 js 内部的方法,都可以达到调用 js 的效果,简单便捷。但是此方法效率比较低,获取返回值比较困难。

    2. webView.evaluateJavascript()

      1
      2
      3
      4
      5
      6
      webView.evaluateJavascript(url, new ValueCallback<String>() {
      @Override
      public void onReceiveValue(String value) {
      //获取返回值
      }
      });

      该方法效率高,而且不会刷新页面,loadUrl 会刷新页面,效率低。但是该方法只能在 4.4 以上的版本使用,所以 2 个方法应该结合使用。

      具体可以看该博文

    Js 调用 Andorid 代码

    1. 通过 webview.addJavascriptInterface(new JSHook(), “androidApp”);

      1
      2
      3
      4
      5
      6
      7
      8
      webview.addJavascriptInterface(new JSHook(), "androidApp");
      public class JSHook {
      @JavascriptInterface
      public void setResult(String result) {
      //得到参数
      }
      }

      这个方法缺陷比较大,漏洞产生原因是:当JS拿到Android这个对象后,就可以调用这个Android对象中所有的方法,包括系统类(java.lang.Runtime 类),从而进行任意代码执行。

      解决方式:

      (1)Google 在Android 4.2 版本中规定对被调用的函数以 @JavascriptInterface进行注解从而避免漏洞攻击。

      (2)在Android 4.2版本之前采用拦截prompt()进行漏洞修复

    2. 通过 webviewClient的 shouldOverrideUrlLoading 回调拦截 url

      1
      2
      3
      4
      5
      6
      7
      webview.setWebViewClient(new WebViewClient() {
      @Override
      public boolean shouldOverrideUrlLoading(WebView view, String url) {
      view.loadUrl(url);
      return true;
      }
      });

      js 代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="utf-8">
      <title>Carson_Ho</title>
      <script>
      function callAndroid(){
      /*约定的url协议为:js://webview?arg1=111&arg2=222*/
      document.location = "js://webview?arg1=111&arg2=222";
      }
      </script>
      </head>
      <!-- 点击按钮则调用callAndroid()方法 -->
      <body>
      <button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button>
      </body>
      </html>

      Android 代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      // 步骤1:加载JS代码
      // 格式规定为:file:///android_asset/文件名.html
      mWebView.loadUrl("file:///android_asset/javascript.html");
      // 复写WebViewClient类的shouldOverrideUrlLoading方法
      mWebView.setWebViewClient(new WebViewClient() {
      @Override
      public boolean shouldOverrideUrlLoading(WebView view, String url) {
      // 步骤2:根据协议的参数,判断是否是所需要的url
      // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
      //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)
      Uri uri = Uri.parse(url);
      // 如果url的协议 = 预先约定的 js 协议
      // 就解析往下解析参数
      if ( uri.getScheme().equals("js")) {
      // 如果 authority = 预先约定协议里的 webview,即代表都符合约定的协议
      // 所以拦截url,下面JS开始调用Android需要的方法
      if (uri.getAuthority().equals("webview")) {
      // 步骤3:
      // 执行JS所需要调用的逻辑
      System.out.println("js调用了Android的方法");
      // 可以在协议上带有参数并传递到Android上
      HashMap<String, String> params = new HashMap<>();
      Set<String> collection = uri.getQueryParameterNames();
      }
      return true;
      }
      return super.shouldOverrideUrlLoading(view, url);
      }
      }
      );
      }

      特点:

      1. 该方法不存在 第一种方法中的 漏洞问题
      2. 但是 js 想要获取 Android 返回值,只能通过 Andorid 再调用 js 代码。
      
    3. Android通过 WebChromeClient 的onJsAlert()、onJsConfirm()、onJsPrompt()方法回调分别拦截JS对话框 。

      整个 Android 和 js 的交互大致就是这几种,详情可以看该博文。

  4. Andorid 多线程的线程锁

    Andorid 线程锁大致分为 2 种,同步机制关键字 synchronized 和 显示锁 Lock。

    1. 同步机制关键字 synchronized

      对于 java 来说,最常用的同步机制就是 synchronized 关键字,他是一种基于语言的粗略锁,能够作用于对象、函数、class。每个对象都只有一个锁,谁能够拿到这个锁谁就有访问权限。当 synchronized 作用于函数时,实际上锁的也是对象,锁定的对象就是该函数所在类的对象。而 synchronized 作用于 class 时则是锁的这个 Class 类,并非具体对象。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public class SynchronizedClass {
      public synchronized void syncMethod(){
      //代码
      }
      public void syncThis(){
      synchronized (this){
      //代码
      }
      }
      public void syncClassMethod(){
      synchronized (SynchronizedClass.class){
      //代码
      }
      }
      public synchronized static void syncStaticMethod(){
      //代码
      }
      }
    2. 显示锁 Lock

      Lock 接口的实现类提供了比使用 synchronized 关键字更加灵活和广泛的锁定对象操作,而且是以面向对象的方式进行对象加锁。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class ReentrantLockDemo {
      Lock lock = new ReentrantLock();
      public void doSth(){
      lock.lock();
      try {
      //执行某些操作
      }finally {
      lock.unlock();
      }
      }
      }

      需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在 try 内,释放锁放在finally 内!!

    3. 读写锁 ReadWriteLock

      当我们读取与写入数据时,有这么一个需求:读与写互斥、写与写互斥、读与读不互斥。这里就必须用到读写锁.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      package SychronizedTest;
      import java.util.concurrent.locks.ReadWriteLock;
      import java.util.concurrent.locks.ReentrantReadWriteLock;
      /**
      * 读写锁测试
      *
      * @author zy_style
      */
      public class ReadWriteLockTest {
      /**
      * @author zhouyang 2016-12-1
      * @time 下午3:01:27
      * @param args
      */
      public static void main(String[] args) {
      final Data data = new Data();
      // 开启3个子线程,分别写入数据
      for (int i = 0; i < 3; i++) {
      new Thread() {
      public void run() {
      for (int j = 0; j < 5; j++) {
      try {
      data.set(j); // 写入数据
      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      };
      }.start();
      }
      // 分别读取数据
      for (int i = 0; i < 3; i++) {
      new Thread() {
      public void run() {
      for (int j = 0; j < 5; j++) {
      try {
      data.get(); // 读取数据
      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      };
      }.start();
      }
      }
      }
      /**
      * 需要操作的数据
      *
      * @author zy_style
      */
      class Data {
      private int data;
      private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
      public void set(int data) throws Exception {
      readWriteLock.writeLock().lock(); // 获取写锁
      try {
      System.out.println(Thread.currentThread().getName() + "准备写入数据...");
      Thread.sleep(50); // 模拟耗时操作
      this.data = data;
      System.out.println(Thread.currentThread().getName() + "写入数据成功!");
      } finally {
      readWriteLock.writeLock().unlock(); // 释放写锁
      }
      }
      public void get() throws Exception {
      readWriteLock.readLock().lock(); // 获取读锁
      try {
      System.out.println(Thread.currentThread().getName() + "准备读取数据...");
      Thread.sleep(50); // 模拟耗时操作
      System.out.println(Thread.currentThread().getName() + "读取数据:"
      + this.data);
      } finally {
      readWriteLock.readLock().unlock(); // 释放读锁
      }
      }
      }
  5. 自定义 View

    View 的绘制流程:OnMeasure() — OnLayout() — OnDraw()

    • OnMeasure():测量试图大小。从顶层父 View 到子 View 递归调用 measure 方法,measure 方法又回调 OnMeasure。

    • OnLayout():确定 View 位置,进行页面布局。从顶层父 View 向子 View 的递归调用 view.layout 方法的过程,即父 View 根据上一步 measure 子 View 所得到的布局大小和布局参数,将子 View 放在合适的位置上。

    • OnDrwa():绘制试图。ViewRoot 创建一个 Canvas 对象,然后调用 OnDraw()。六个步骤:

      ①.绘制视图的背景;②.保存画布的图层(Layer);③.通过 onDraw() 绘制 View 的内容;④.通过 dispatchDraw() 绘制 View 子视图,如果没有就不用;⑤.还原图层(Layer);⑥.绘制滚动条

      View 的绘制流程具体可以看这篇博文

  6. View 的事件分布机制

    1. Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。
    2. ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。
    3. 触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。
    4. 当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。
    5. 当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。
    6. 当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。
    7. onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

    view 事件分发机制具体可以看该博文

  7. Thread 和 AsyncTask

    其实 Thread 和 AsyncTaks 并不是一个等级的东西。

    AsyncTask是作为异步任务,执行除了UI界面更新的任务之外的其他耗时操作的。UI界面的更新是在主线程,也就是UI线程中执行的,而在这个异步任务中,开启了一个工作线程来执行耗时操作。而这个工作线程和UI线程的执行顺序是不同步的,也就是说只有执行完工作线程中的下载之后,才会调用UI线程中的onPostExecute()执行后续UI操作,这样就实现了异步下载。如果UI线程销毁之后工作线程再发送下载结束的信息,由于工作线程再使用过程中是与AsyncTask绑定的,所以他也会随着当前AsyncTask的销毁而销毁,不会执行后续的下载操作,自然也不会执行发送下载结束的信息。

    而用 Thread 开启简单的子线程执行下载,子线程和 UI 线程只能保持简单的同步关系,这个时候需要通过 Handler 进行异步操作。

    举例说明:

    Thread 开启简单的子线程下载,下载完成后进行 UI 操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /**
    * 下载线程
    */
    private Thread downThread = new Thread(new Runnable() {
    @Override
    public void run() {
    //下载操作
    Message message = Message.obtain();
    message.what =1;
    message.obj = "data";
    handler.sendMessage(message);
    }
    });
    private Handler handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
    if(1==msg.what){
    //UI 操作
    }
    return false;
    }
    });
    //最后在需要下载的地方 调用
    downThread.start();

    AsyncTask:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    class DownLoadAsyncTask extends AsyncTask<Void, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
    super.onPreExecute();
    }
    @Override
    protected void onPostExecute(Boolean aBoolean) {
    super.onPostExecute(aBoolean);
    if (aBoolean) {
    Toast.makeText(mContext, "下载成功", Toast.LENGTH_SHORT).show();
    } else {
    Toast.makeText(mContext, "下载失败", Toast.LENGTH_SHORT).show();
    }
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
    super.onProgressUpdate(values);
    }
    @Override
    protected void onCancelled(Boolean aBoolean) {
    super.onCancelled(aBoolean);
    }
    @Override
    protected void onCancelled() {
    super.onCancelled();
    }
    @Override
    protected Boolean doInBackground(Void... voids) {
    return null;
    }
    }
    //调用方法
    DownLoadAsyncTask downLoadAsyncTask = new DownLoadAsyncTask();
    downLoadAsyncTask.execute();

    使用 AsyncTask 的注意事项:

    ① 异步任务的实例必须在UI线程中创建,即AsyncTask对象必须在UI线程中创建。

    ② execute(Params… params)方法必须在UI线程中调用。

    ③ 不要手动调用onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法。

    ④ 不能在doInBackground(Params… params)中更改UI组件的信息。

    ⑤ 一个任务实例只能执行一次,如果执行第二次将会抛出异常。

  8. 手写一个创建 sql 的过程,并附带一个些基本增删改查操作。

    Android 本身并不提供关于数据库的内容,所以一般是使用轻量级的 sqlite 数据库。

    创建数据库时候,需要继承于 SQLiteOpenHelper 这个类进行数据库的创建。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class MySqlHelper extends SQLiteOpenHelper {
    private final static String dbName = "test.sql";
    public MySqlHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
    super(context, name, factory, version);
    }
    public MySqlHelper(Context context) {
    //context :上下文 , name:数据库文件的名称 factory:用来创建cursor对象,默认为null
    //version:数据库的版本号,从1开始,如果发生改变,onUpgrade方法将会调用,4.0之后只能升不能将
    //这里注意,如果想要指定路径 sql 文件,比如 assets 或者其他目录,需要输入 sql 文件的完整目录名。同时,需要检测文件是否存在,如果不存在,则需要手动创建
    //默认创建目录是在 /data/data/database/... 目录下 ,这个权限在 android 6.0 后,非 root 手机无法直接访问目录
    super(context, dbName, null, 1);
    }
    @Override
    public void onOpen(SQLiteDatabase db) {
    super.onOpen(db);
    //通过SQLiteDatabase执行一个创建表的sql语句
    db.execSQL("create table userinfo (_id integer primary key autoincrement,name varchar(20))");
    }
    //oncreate方法是数据库第一次创建的时候会被调用; 特别适合做表结构的初始化,需要执行sql语句;SQLiteDatabase db可以用来执行sql语句
    @Override
    public void onCreate(SQLiteDatabase db) {
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    //添加一个phone字段
    db.execSQL("alter table userinfo add phone varchar(11)");
    }
    }

    而在主 java 程序中,直接调用该类就可以创建一个 sql。

    1
    2
    3
    //比如在 onCreate 方法中加入
    MySqlHelper mySqlHelper = new MySqlHelper(this);
    SQLiteDatabase database = mySqlHelper.getReadableDatabase();

    然后后续所有的数据库操作都是通过 database 这个类进行,例如增删改查等等。

    而这些基础操作,都需要基于 Cursor 类来进行处理,个人觉得很麻烦,所有后面都采用了 GreenDao 这个ORM。关于 Curor 可以看看该博客 。

  9. GreenDao 的原理

    这个 ORM 简单几句完全说不清,本人处于只可意会不可言传这种状态。具体参考下面的几个博文:

    https://blog.csdn.net/u010687392/article/details/48465315

    https://www.jianshu.com/p/0d3cbe6278fb

    我觉得里面说的非常详细了,其实 GreenDao 其实就是基于 Android 原生的 DataBase 进行一个封装。直接对象关系映射,这个是最简单方便的一个操作,并且简化了数据库的增删改查操作。

####暂时就更新这么多,后续看到有其他内容,会一一加上

android之sophix热修复

发表于 2018-04-27

热修复概述

近 2 年来,热修复技术飞速发展。毕竟每次一个小版本的更新都要通过发布 apk 来实现,这样用户使用感可以说也是非常差了。怎么让客户不去重新下载一个 apk 而实现一个小版本或者一次小 bug 的修复,因此,热修复应运而生。

特别是 android studio 2.0 版本后 推出了 Instant Run 这个功能,关于 Instant Run 可以看 郭大的博文。从此,各种热修复热更新技术相继出现,国内比较成熟的主流 APP 都拥有自己的热修复技术,像支付宝、QQ、微信、美团等。

之前的热修复一般只支持代码热修复,而不支持资源热修复。代码热修复只是针对一些逻辑性错误的代码修改,到了后面又相继出现资源热修复,以及 so 热修复。本文主要是说阿里的 sophix ,所以就拿阿里的各版本热修复来说明比较。具体看下图:

基本可以看清楚,阿里从 Andfix 到最新的 Sophix 正在逐步完善。

而本文就将介绍,如何在 Android 项目中集成 Sophix 以及 如何使用Sophix。

Sophix 集成和准备

  1. 集成准备

    工具:android studio

    环境:jdk 1.8

    找到项目的 build.gradle 直接添加 maven 仓库地址:

    1
    2
    3
    4
    5
    repositories {
    maven {
    url "http://maven.aliyun.com/nexus/content/repositories/releases"
    }
    }

    然后找到 app 的 gradle 添加版本依赖:

    1
    compile 'com.aliyun.ams:alicloud-android-hotfix:3.2.2'

    点击一波 sync now,完成配置。

  1. 权限说明

    Siphix 需要用到 4 个基本权限:

    1
    2
    3
    4
    5
    6
    <! -- 网络权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <! -- 外部存储读权限,调试工具加载本地补丁需要 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    第四个权限是属于 Dangerous Permissions,所以需要在 android 6.0 的版本以上进行动态权限获取。

  2. 混淆配置

    需要在 proguard-rules.pro 混淆文件中加入混淆代码

    1
    2
    3
    4
    5
    6
    7
    8
    #hotfix
    -keep class com.taobao.sophix.**{*;}
    -keep class com.ta.utdid2.device.**{*;}
    #防止inline
    -dontoptimize
    -keepclassmembers class com.zyue.zyueapp.base.BaseApplication {
    public <init>();
    }
  3. Sophix 的稳健初始化方式

    本文采用 Sophix 的最新版本初始化方式,该方法显得更加简单明了,而且跟原始的自己所加载的 Application 不会造成冲突。这样使得原先真正的 Application 也可以热修复,并且减少了补丁预加载的时间,同时还兼容了 Aandroid 8.0 以后的版本。

    具体就是要我们自己加入一个类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    package com.my.pkg;
    import android.app.Application;
    import android.content.Context;
    import android.support.annotation.Keep;
    import android.util.Log;
    import com.taobao.sophix.PatchStatus;
    import com.taobao.sophix.SophixApplication;
    import com.taobao.sophix.SophixEntry;
    import com.taobao.sophix.SophixManager;
    import com.taobao.sophix.listener.PatchLoadStatusListener;
    import com.my.pkg.MyRealApplication;
    /**
    * Sophix入口类,专门用于初始化Sophix,不应包含任何业务逻辑。
    * 此类必须继承自SophixApplication,onCreate方法不需要实现。
    * 此类不应与项目中的其他类有任何互相调用的逻辑,必须完全做到隔离。
    * AndroidManifest中设置application为此类,而SophixEntry中设为原先Application类。
    * 注意原先Application里不需要再重复初始化Sophix,并且需要避免混淆原先Application类。
    * 如有其它自定义改造,请咨询官方后妥善处理。
    */
    public class SophixStubApplication extends SophixApplication {
    private final String TAG = "SophixStubApplication";
    // 此处SophixEntry应指定真正的Application,并且保证RealApplicationStub类名不被混淆。
    @Keep
    @SophixEntry(MyRealApplication.class)
    static class RealApplicationStub {}
    @Override
    protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // 如果需要使用MultiDex,需要在此处调用。
    // MultiDex.install(this);
    initSophix();
    }
    private void initSophix() {
    String appVersion = "0.0.0";
    try {
    appVersion = this.getPackageManager()
    .getPackageInfo(this.getPackageName(), 0)
    .versionName;
    } catch (Exception e) {
    }
    final SophixManager instance = SophixManager.getInstance();
    instance.setContext(this)
    .setAppVersion(appVersion)
    .setSecretMetaData(null, null, null)
    .setEnableDebug(true)
    .setEnableFullLog()
    .setPatchLoadStatusStub(new PatchLoadStatusListener() {
    @Override
    public void onLoad(final int mode, final int code, final String info, final int handlePatchVersion) {
    if (code == PatchStatus.CODE_LOAD_SUCCESS) {
    Log.i(TAG, "sophix load patch success!");
    } else if (code == PatchStatus.CODE_LOAD_RELAUNCH) {
    // 如果需要在后台重启,建议此处用SharePreference保存状态。
    Log.i(TAG, "sophix preload patch success. restart app to make effect.");
    }
    }
    }).initialize();
    }
    }

    这边的初始化动作一定要放在 attachBaseContext 中进行,onCreate 不需要自行实现。并且如果项目中因为方法类过多,超过 64K ,需要使用到MultiDex,就必须把该方法放到初始化动作之前,也就是上类中注释掉的部分。

    在这个类中,关键一点要加入自己写的 Application.

    1
    2
    3
    @Keep
    @SophixEntry(MyRealApplication.class)
    static class RealApplicationStub {}

    这里的 MyRealApplication 就换成自己写的 Application。

    为了防止自己写的 Application 和 这个类混淆,在 proguard 文件中加入以下混淆代码:

    1
    2
    3
    4
    5
    -keepclassmembers class com.my.pkg.MyRealApplication {
    public <init>();
    }
    # 如果不使用android.support.annotation.Keep则需加上此行
    # -keep class com.my.pkg.SophixStubApplication$RealApplicationStub

    最后,将 AndoridManifest.xml 中的 application 的 name 换成这个类名。

    1
    2
    3
    4
    <application
    android:name="com.my.pkg.SophixStubApplication"
    ... ...>
    ... ...

    最为关键的一步:

    到此处,稳健接入初始化,还差一个步骤,就是进行补丁下拉,也就是 queryAndLoadNewPatch 这个方法的调用。我个人是直接放到了自己写的 Application onCreate中,不建议放到其他方法类下。

    或者自己写个线程进行间隔拉取补丁进行热修复。

    1
    2
    3
    4
    5
    @Override
    public void onCreate() {
    super.onCreate();
    SophixManager.getInstance().queryAndLoadNewPatch();
    }

    到这里,这样除了一个 setSecretMetaData 没有配置,其他都配置 OK 了。setSecretMetaData 里面的参数,需要在阿里控制台进行其他操作才能进行。

    若有不理解处,可以具体查看官方官方技术文档。

Sophix 具体使用

  1. 首先进入阿里云的移动研发平台。找到移动热修复功能点,或者搜索。

  2. 点击添加产品

  3. 填写产品信息,创建完成后,点击该产品进入。

  4. 点击添加应用

  5. app 名称和包名跟自己项目的保持一致,然后创建。

    这里可以看到项目中所需要的 AppSecret,还有2 个参数,就点击下载 aliyun-emas-services.json,打开找 到 IDSECRET ,APPSECRET ,RSASECRET 这 3 个参数,写入到一开始稳健接入 SophixStubApplication 类中。主要设置为 setSecretMetaData 方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    final SophixManager instance = SophixManager.getInstance();
    instance.setContext(this)
    .setAppVersion(appVersion)
    .setSecretMetaData("IDSECRET", "APPSECRET ","RSASECRET")//填写密钥和ID
    .setEnableDebug(true)
    .setEnableFullLog()
    .setPatchLoadStatusStub(new PatchLoadStatusListener() {
    @Override
    public void onLoad(final int mode, final int code, final String info, final
    int handlePatchVersion) {
    if (code == PatchStatus.CODE_LOAD_SUCCESS) {
    Log.i(TAG, "sophix load patch success!");
    } else if (code == PatchStatus.CODE_LOAD_RELAUNCH) {
    // 如果需要在后台重启,建议此处用SharePreference保存状态。
    Log.i(TAG, "sophix preload patch success. restart app to make effect.");
    }
    }
    }).initialize();

    到这一步完成,Sophix 的初始化才算真正完成。

  6. 然后点击移动热修复,点击功能。

    点击添加版本

    注意:这里的版本号必须跟项目中的 versionName 保持一致,否则项目拉取补丁会失败。

    到这个就可以上传自己项目的补丁了。

  7. 补丁制作

    是需要下载官方的一个工具,然后进行补丁打包。再将该补丁上传到该版本下。

    生成补丁看这里,生成补丁.

    又要注意了:这里的补丁名称不要乱修改,就得用这个工具生成的。

    到了此处,终于到了补丁发布了。折腾这么久,听着流泪,闻着伤心。

  8. 上传完补丁后,点击详情,进入该补丁界面。

    这个右侧有一个扫码验证补丁:点击下载 Sophix自带的一个自测 apk,安装在自己手机上,可以扫码来验证补丁是否有效,有效后,可以点击,新建发布。然后打开手机上的 app,就可以坐等热修复了。​​​​

总结

到这里,Sophix 热修复的功能就基本结束了。其中,小编自己踩的坑也都表明了注意,要是还有其他问题可以访问官方技术文档,或者进行问题搜索都可以得到一个满意的答复。

2017总结

发表于 2018-01-09

2017 年终总结

话说已经 2018 年 1 月 9 号,我才想起写总结,希望还是能搭上总结大军的车,老司机,带带我。

2017年发生了很多事情,从最近的李小璐出轨,再到前段时间中国最大庞氏骗局『钱包网』CEO 自首。各种大事件充斥在社会的每一个角落。网络流行语,也从”老司机,带带我”,“打工是不可能打工的”这些骚话到最近的”做头发“,作为常年一个混迹 A 站的我来说,洒洒水啦。技术角度来讲,去年就预热的 python 各种火热,我也蹭了一哈子,给自己的技术栈增加一点东西。

就不扯那么多废话了,回归正题。2017 对于我自己来说也发生了很多事情。

  • 为自己买了一部 i7 以及一台足够玩各类游戏和兼容工作的台式机,价格一般,但是我很满足了。

  • 买了个 kindle ,里面的一本『巨人的陨落』我才看了一半,羞愧。

  • 买了 5 本实体书,其中包括 2 本英文书:『了不起的盖茨比』和『老人与海』。本来打算先从基础的『老人与海』看完,再看比较复杂的『了不起的盖茨比』,不过暂时还没有实施。看完了三毛的『送你一匹马』和大冰去年的新书『我不』。杨绛先生的『我们仨』,看了开头的大部分,没有接下去看的勇气,正值青年的我,暂时可能还没法理解其中含义,不知道我到 30 岁去看是否有其他感受。也许之后不太会主动去读大冰的书了,我连续 3 年读了大冰的 3 新书,不可否认,我喜欢书中的故事,喜欢书中描述的世界,但是终究现实还是现实,逃不脱,解不开。

    那天看视频,其中一段插入了 三毛 打电话给荷西的录音。开口的瞬间,我震惊了,感觉三毛的声音好符合她的性格,怎么会有这样的声音。声音透露着温柔,温柔中又透露着洒脱,反正以我的文采并不能描述出来。从三毛的书中不难看出其实三毛的个人世界是有多纷乱和精彩,从小时候的疾病,到长大后的抑郁。我不清楚这样一段录音发生在什么样的情况下,也不清楚是发生在她人生中的哪一个阶段。但是我又懂了,怪不得,怪不得,那么多人去追求了三毛,哪怕是这样声音放到这个21世纪的世界也怕是极其具有诱惑力,更何况她那更有趣的灵魂。这里附上三毛珍贵录音

  • 自己学会了搭建博客,搭建翻墙 VPN ,也自学了 python 语言。在本职 android 上也学习了很多新技术,了解了更多的技术栈,以及对技术圈有了个大致了解。

  • 本来准备年中的时候跟前公司申请涨薪,但是沟通稍微有点失败,加上其他各种各种原因,离职。现在在新公司 2 个多月,比较满意自己现在的工作状态。希望以后再接再厉!

  • 娱乐上说,卸载了 LOL ,下载了「绝地求生」,俗称 ‘吃鸡’ ,很嗨。为啥卸载 LOL ,可以看我上一篇文章。而且我发现自从没玩 LOL ,整个人戾气少了很多,人更加平和了。

  • 锻炼,在夏天最热的 3 个月,每周坚持 2-3 次的外出 5 km 跑步,跑回家一身汗。就那段时间,我的晨脉特别健康状态。瞅瞅也好久没运动,所以买了个健身轮,天天在家做几组,拉练拉练。毕竟身体才是革命的本钱啊!

  • 在快年末的时候,家里去了一只小狗,我给叫「丢丢」,小狗没尾巴,是斗牛犬的混种,听家里人说很可爱。租房地方不大,不然我就可以带到自己这边养起了,小小的遗憾呐。

  • 唯一的大遗憾的就是没能攒下积蓄,这点就比较蛋疼了,希望明年可以有所突破。

展望 2018

不知道 2018 会如何,过了春节,就到了我的本命年了,希望在本命年,在工作上有所突破了,工资涨啊涨,其他就身体健康最为关键了。然后就是学习,学习,学习。

5年之痒,再见了LOL

发表于 2017-10-28

再见了,LOL

2012 年,某周末晚上,一家网吧,是的网吧,那时候还跟没网咖的概念。疯狂 CF ,一把 AN94 突突突 ,秀翻全场。临近 9 点,CF 战队开会,大家停止游戏统一进 YY 。别问为啥我这穷屌丝居然还战队开会,木办法,那时候的 CF 相对来说还是很公平的,网吧基本有一半甚至三分之二都在打 CF ,本人这种混子有个战队不是很正常吗?嗯?这时候一般我会打开一个叫做 QQ 斗地主的游戏,疯狂叫地主(以前斗地主只要能叫,绝对不怂)。

However,那天我正巧,牌运很差,送我的豆子都输光啦。好气,凭我的技术,一般这 4 次送豆都能玩好久好伐。

此时开会还没结束,我鬼使神差的打开了一款叫做英雄联盟的游戏,嗯,这游戏。我完全不知道是啥,也不知道这个游戏是干嘛的。直接登录注册,随手一个 ID :急哦XXX。啥特么教程,我瞅都不带瞅的,直接点了个大师你懂么?进入游戏,一把人机就搞了起来。然后被这游戏搞的头大,怎么操作,啥子 技能,我的天呐,真难啊,sb 游戏,然后退出了游戏。嗯,大概这就是我与 LOL 的第一次美好邂逅。呵呵,当时估计那把队友都是 mmp。虽然当时是人机,但是对于我这种新手还是很难的好吧。

第二次,战队开会,没错,第二次。我一看周围一个大兄弟也在玩,哎,这游戏有点意思啊,里面也登游戏进去。然后又是一把人机,全场一顿寒冰到处溜达,然后就赢了,哎,莫名其妙,还有点胜利的赶脚。不错哦,然后我又疯狂怼了几把人机。接着我就点开了一个奇怪的游戏模式,匹配模式。随之而来的所以一波又一波黑屏,难受,mmp,垃圾游戏,拉闸。

第三次,嗯,不是战队开会了,实在是那天打爆破打累着,就冲 LOL 这画质去放松下也是好的啊。其实是特么那个垃圾网吧网速很差,打游戏倒是很稳定,但是网速奇差无比,看个视频卡成 shi , 又不想这么早回去。所以准备点开一波 LOL 来个美滋滋的战斗。不巧,登上游戏,发现我姐也在玩,嘿嘿,玩游戏,啥子最重要,开黑,而不是单机。当初我姐也是愿意带我这个坑也是真爱啊。毕竟我特么坑成 shi。也感谢当初那些队友,没咋喷我,其实我当初也是懒得关注聊天记录区,喷都喷吧,小 case 啦。不巧,那天赵信,没错,信爷,又称菊花信。对着敌人菊花一阵 3 连捅直接要你狗命。不吹不黑,虽然是第一次玩菊花信,但是那天打出了风采,第一次超神,我滴天。个人感觉也是那时候彩笔还是很多的,不对,是新手还是很多,纯属是因为赵信当初那个版本太强了,见谁怼谁,超神,美滋滋。后来又仗着脸皮厚让我姐带着我飞起来,又是一顿菊花信,victory,胜利的喜悦,大概就是电竞的魅力吧。是的,当初就体验过电竞的魅力了,很嗨。

2017 年 10 月 28 日 10 点 33 分,我犹豫很久,想了又想,还是决定卸载掉她了。是的 RNG 输给了 SKT ,梦想破碎了,冠军不冠军又如何。RNG 承载了我们这一代 LOLer 的很多梦想。已经 S7 了,记得以前在一区打,要是周末,基本进去都是要排队,现在呢,基本上是秒进去,打个排位都要排好久。事实说明,现在玩 LOL 的人确实不多了。昨天 RNG vs SKT ,打的很精彩,说句实话, UZI 今年的状态就应该拿冠军,但是这是一个团队游戏,而现在的版本,ADC 前期根本发力不了。说白了,这游戏很吃团队。一直很支持 UZI,一手 VN 终究秀,把这个当年 17 岁的少年推上了世界第一 ADC 的座位。不是狗吹,但是是狗粉。比赛就不多说了。要是看过当年 S3 皇族打 SKT 1 那场决赛,就可以知道,当年的 5 个人,上单神超,中单五五开,打野 lucky,下路 UZI 和 Tabe。基本线上能力超级强,绝对的优势,但是就是当年的中国 LOL 没有教练,也没有运营思维,输在这里,所以大家说的 0-3 ,我上我也行也是有道理的。现在的中国上单只有 WE 的 957 还可以跟韩国上单一站,其他上单从来都是抗压或者被打出 shi 。

真正的开始玩 LOL ,是进大学,和宿舍的哥几个一起玩的。有天我乐看到我电脑上在下 LOL 。哎,你也玩这个,我们最近也在玩,一起哇。就酱,我们开始了每天 4 黑的日子,甚至后来,dota 大神,我熹也加入了我们,一度壮大到 5 黑。记得刚开始一起打人机的时候,我们 4 个连人机有时候都打不动,什么上中野下辅助,这些位置也是根本不了解的,唯有一腔热血,怼。渐渐地,大家技术都有了提升了,开始会了一些基本套路,比如 5 个人蹲草丛,四一分带拆塔赢, AP 中单 JS……我们对每个位置也有了足够的了解。我一般玩上单,补位其他,菊哥一般打野和辅助,我乐,专注于 ADC,我胖专注于中单,偶尔切换到其他路换换口味。

从此,6 个单身的汉子,一下课就是直奔宿舍,打开电脑,约战裁决之队。唯有大哥很坚挺,打开电脑,带上耳机,进入到自己的世界,任凭我们喧嚣和吵闹,很包容。我想我们 5 黑的道路上少不了大哥的包容,给我们创造了这样一个美好的环境。一到周末,开黑到 3 ,4 点那都不是事儿。所以,感谢大哥当年不杀之恩。在大一,每天可以打个十几把 LOL 都不带喘气的,洒洒水。

到了大二,我胖把我们带人了剑三的世界,然后我发现,我去这游戏不太适合我,== 。然后我每天还是坚持打 LOL ,连续好久都是我一人的独行 LOL,然后和发小一起决战其他区或者和我刘一起双排。偶尔宿舍还是一起约战到裁决之队。 来一波开黑,很燃。

曾经的游戏巅峰,大概是我们 3 个人硬拖到后期,3 打 5 赢了。当时对面和我们技术都差不多,到了后面硬是被当时我胖的旋转卡特给完美收割。有图有真相:

电竞胜利的喜悦是很美妙的,当年这把赢的更是美妙中的美妙。每一个大劣势局能赢都是队友们的互相鼓励和坚持,每一个大优势输的局肯定是浪输的。可以看到,当年的打野蛋刀充斥在野区,当年的饮血剑并不是唯一被动(依稀记得有一把被对面瑞文5把饮血剑支配的恐惧),当年的轻语还是那么帅的标记,当年的鞋子可以附魔,当年的兰顿也不是如今的兰顿……而如今的游戏也不是当年的那个 LOL 。

全华班的 RNG 输了,我感觉我的 LOL 也输了。因此我犹豫好久决定卸载掉她,将她变成一个美好的回忆,以后除非哥几个约战,将不再以召唤师的名义进入召唤师峡谷进行战斗。

本来还有很多关于 LOL 的故事要写,但是写到此处,也不知道写些什么。

看到 RNG 最后一波死人的的时候,直接关掉直播,凉了。输了,不想再点开比赛去看 RNG 的失落。LOL 终究是一个游戏竞技,跟传统体育竞技存在差别。作为电竞时代的宠儿 LOL 也是要开始走下坡路的。

今年作为最有希望的一年,全华班还是倒在了 SKT 王朝之下,虽败犹荣。但是终究还是输,这就是电竞,很残忍,也很现实。也有说明天 WE 也许可以夺冠,是的,夺冠归夺冠,但是 WE 他毕竟不是全华班,承载不了我这个一个老 LOLer 追逐。同样也支持 WE,但是意义大不相同。

因此,考虑很久,还是决定卸载掉 LOL,微博屏蔽掉所以关于 LOL 的推送消息,知乎关掉 英雄联盟话题,以后就和 LOL 相忘于江湖了。

作为一个老玩家实在是适应不了 LOL 现在多变的模式了,多变的英雄更新,机制更新。每天有着满足于菜米油盐的工作,并不能时时刻刻来关注她了。也许有很多玩家跟我有一样的感触,当 RNG 输掉的那一刻,这个游戏对于我来说索然无味了。

LOL 承载了我 5 年多的青春,在这样一个完全公平的竞技环境下,完全是靠个人发挥和对游戏的理解进行战斗。在以前跟宿舍兄弟们,和哥们几个一起开黑的,一起吵闹,一起喧嚣的日子本来就一去不复返了了,为何我还在执着这个游戏,执着到 S7 全华班夺冠无望,确实该放下了。

对我来说,LOL 已经不仅仅是一个游戏,我从小玩的游戏不多,从小霸王时代到 PC端时代,玩过的网络竞技游戏屈指可数。以前玩 LOL 的我是玩上单,从来不怂,刚正面,现在反而玩 ADC 多,怂发育,托后期,到中后期给队伍保证足够的伤害。依稀还记得在网吧拿五杀得了杯咖啡的时光,这原本就是这个游戏的魅力所在。

也许很多人和我一样,从此再不玩 LOL 。

人生不仅仅是菜米油盐,还有 LOL。

人生不仅仅包含有 LOL,更有菜米油盐。

再见了我的 LOl ,再见了我的召唤师峡谷!

本来还有很多话要说,但是无以言表,我不知道这对于一个中度 LOLer 到底有什么意义,但是我还是毅然决然地与她告别,也许大概以后再也不会玩一款游戏超过 5 年以上了,再也不会去研究一个游戏如此深刻,再也遇不到一帮志同道合的伙伴去玩这款游戏了。

动态布局 GridLayout(Kotlin)

发表于 2017-10-12

前言: 最近项目中需求到一个功能,就是根据后台传过来的数据,在 Android 上进行一个自定义分屏。分屏区域是不定的,分屏区域的大小更是不定的,唯一定的分屏区域的形状肯定是长方形。那这里就想到如同 Excel 合并单元格的模式来进行动态布局。而由于最近 Kotlin 语言大火,本次直接使用到 Kotlin 语言进行实际开发。

动态GridLayout的具体实现方式

一般实现 GridLayout 都是在 xml 文件中进行配置,然后在 GridLayout 对里面的子控件一个一个的写。如果用这个来实现一个简单的计算器布局,估计很累。同样在具体需求下面,这样的每个都进行布局重写根本不符合我们所需要的功能。因此博主就想到,能不能让 GridLayout 子布局动态大小,而且还要为每个子布局设置属性等等,想想就头大不是么,相当复杂了可以说。

这里就想到一个好办法,就是把 GridLayout 看做一个田字格画布,比如说 3x4 的画布,那这个画布上面就有 12 个格子。跟表格类似。就跟在 Excel 上合并单元格一样,只要能合并单元格的区域就可以单独作为一个子区域。那这个单独的子区域上面可以防止任何组件,如:TextView,Button,Fragment….只要你能想到的控件,基本都可以放进去,然后实现深一步的业务。

Kotlin 简介

今天的 Google 大会上,已经正式提出将 Kotlin 语言作为 Android 的开发语言。可以说又是一个 Google 的亲儿子,以后肯定会成为 Android 开发的主流语言。其对于传统语言 Java 的优势这里我就不多累赘了,具体可以看知乎上大神些的回答:Kotlin 作为 Android 开发语言相比传统 Java 有什么优势?

总得来说,用 Kotlin 来开发就会变的很爽。但是毕竟是一个新出语言,市场上,还没有公司正式用该语言进行具体的开发步骤,基本都是大家进行一些个人开发。毕竟很多国内关于 Kotlin 的社区并不是很完善。

本编将在实际操作的过程中顺便介绍 Kotlin 的简单知识,希望对你有所帮助。如遇到不理解的地方,欢迎 Google ,基本上一个 Stackoverflow 社区可以解决你的一些问题(要是访问慢的话试试科学上网吧)。

GridLayout 具体实现步骤

1.动态布局设置

首先我们在布局文件中放入一个 GridLayout 作为主布局,然后在代码中动态设置 GridLayout 的横向纵向数,以及每个格子的控件。

1
2
3
4
5
<GridLayout
android:id="@+id/grid_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

然后通过代码获取屏幕的长宽,方便后面动态布局设置长宽属性。

1
2
3
4
private var height = 0 //屏幕高度
private var width = 0 //屏幕宽度
private var mainX = 3 //x轴格子数
private var mainY = 4 //Y轴格子数
1
2
3
4
5
6
7
//获取手机屏幕高宽度
val wm: WindowManager = this.windowManager
height = wm.defaultDisplay.height
width = wm.defaultDisplay.width
grid_layout.columnCount = mainX //设置gridlayout 横向格子
grid_layout.rowCount = mainY //设置gridlayout 竖向格子

这里我为了操作方便,是直接把 X 和 Y 轴格子数写死了,就是横向 3 个,竖向 4 个,也就说一共是一个 3 x 4 = 12 的表格。如下图:

img

这里我们可以根据坐标,已经占用的横纵两向的格子数来合并这些格子。

在手机上,坐标跟普通坐标系是有点反着的,既手机的左上角是坐标原点处,往下是 Y 轴延伸方向,往左是 X 轴延伸方向。请忽略我这个灵魂画手。

手机屏幕坐标图

那么现在我们只需要传入 4 个参数,既坐标点 x,y 以及各占用的格子数 xn,xn,就可以在这个 12 表格的 GridLayout 上面将布局画出来。

每次都单独取一个布局元素

1
val par = GridLayout.LayoutParams() //每个子布局单独获取,并分配区域

分配好这个布局所占用的控件位置,以及占用表格大小

1
2
3
4
5
par.rowSpec = GridLayout.spec(y as Int, yn as Int)
par.columnSpec = GridLayout.spec(x as Int, xn as Int)
//根据每个格子占用高宽来计算这个子布局的高宽
par.height = height / mainY * yn
par.width = width / mainX * xn

如果有要在格子间加入线条的需求,可以设置各个子布局间的距离,然后统一设置整个布局背景,就是线条颜色,也可以在子布局中详细设置,这个就见仁见智了。具体需求具体操作。

因为有些格子是靠边的,所以并不需要靠边的一侧空出间隔,所以要进行一个简单的判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
//判断子布局的上下左右是否为边界,是,就不留间隔.
if (0 != y) {
par.topMargin = 1
}
if (0 != x) {
par.leftMargin = 1
}
if (mainX != (x + xn)) {
par.rightMargin = 1
}
if (mainY != (y + yn)) {
par.rightMargin = 1
}

接着把设置的布局元素设置为指定的控件,我这里因为功能需要,都统一成 FrameLayout,然后每个 FrameLayout 都设置一个单独的 Fragment ,方便在独立的 Fragment 中进行逻辑处理。

1
2
3
t.layoutParams = par
grid.addView(t, par)

这里我们把这个设置子布局的内容,单独设立一个方法,方便之后的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 设置Fragment组件 跨多少行 跨多少列
* 这里不一样的要设置成 Fragment,其他各种组件都可以自己搭配
*
* @param grid grid控件
* @param t fragment布局
* @param x 组件横向轴开始的索引
* @param y 组件纵向轴开始的索引
* @param xn 组件横向拉升位置 就是占几列
* @param yn 组件列向拉升位置
*/
fun setSpanRowCol(grid: GridLayout, t: FrameLayout, x: Int?, y: Int?, xn: Int?, yn: Int?) {
val par = GridLayout.LayoutParams() //每个子布局单独获取,并分配区域
par.rowSpec = GridLayout.spec(y as Int, yn as Int)
par.columnSpec = GridLayout.spec(x as Int, xn as Int)
par.height = height / mainY * yn
par.width = width / mainX * xn
//判断子布局的上下左右是否为边界,是,就不留间隔.
if (0 != y) {
par.topMargin = 1
}
if (0 != x) {
par.leftMargin = 1
}
if (mainX != (x + xn)) {
par.rightMargin = 1
}
if (mainY != (y + yn)) {
par.rightMargin = 1
}
t.layoutParams = par
grid.addView(t, par)
}

到这里基本就是这个动态设置的思路,需要注意的是,每次添加布局,子布局的大小以及位置不能有冲突,不然子布局会重叠,这个需要添加之前就计算好。

我这边自己手动添加了 2 个集合用来 for循环添加子布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
listone = ArrayList()
listone?.add(Data.Single.getInstance(0, 0, 3, 2))
listone?.add(Data.Single.getInstance(0, 2, 3, 2))
listtwo = ArrayList()
listtwo?.add(Data.Single.getInstance(0, 0, 3, 2))
listtwo?.add(Data.Single.getInstance(0, 2, 1, 2))
listtwo?.add(Data.Single.getInstance(1, 2, 2, 2))
for (i in listone!!.indices) {
val data: Data = listone!!.get(i)
val fragmentlayout: FrameLayout = FrameLayout(this)
fragmentLayouts?.add(fragmentlayout)
fragmentlayout.id = View.generateViewId()
setSpanRowCol(grid_layout, fragmentlayout, data.x, data.y, data.xn, data.yn)
addFragment(EasyFragment.Single.getInstance(i.toString()), fragmentlayout.id)
}

基本到这里整个动态 GridLayout 的添加就算完成了,下面主要介绍下本编中用到的 Kotlin 语言的相关知识。

2.Kotlin 简单语法介绍
  1. Kotlin 变量,常量

    变量 var,常量 val 表示,这点比 Java 中简洁多了,跟 JavaScript 类似。

  2. Kotlin 数据初始化

    Kotlin 中的数据类型跟 java 中类似,基础数据类型都包含有。

    而 Kotlin 对于数据或者对象的初始化可以直接通过 :来代替数据类型,也可以直接用 = 直接表明类型,该类型会直接饮用 = 后面的数据类型。

    1
    2
    private height : Int? = null
    private var height = 0 //屏幕高度
  3. Kotlin 空指针

    在Kotlin中空指针异常得到了很好的解决。

    • 在类型上的处理,即在类型后面加上?,即表示这个变量或参数以及返回值可以为null,否则不允许为变量参数赋值为null或者返回null
    • 对于一个可能是null的变量或者参数,在调用对象方法或者属性之前,需要加上?,否则编译无法通过。

    如下面的代码就是Kotlin实现空指针安全的一个例子,而且相对Java实现而言,简直是一行代码搞定的。

    这里不单独对空指针问题进行介绍了,毕竟这个属于学习 Kotlin 中的一个复杂点,关于空指针安全原理,可以参考这篇文章研究学习 Kotlin 的一些方法

  4. Kotlin 的类型转换

    在实际项目中会经常遇到数据类型转换问题,在 Kotlin 中,会经常遇到比如 Int? 转化为 Int ,就可以直接用 as 直接转化

    1
    y as Int

    这一串代码直接将 y 重新赋值成 Int 类型,相当于java中的

    1
    y = Integer.valueOf(y)

    简洁明了。当然 Kotlin 也支持多种转化方式,比如toInt,toString等等。

  5. Kotlin 方法类别

    代码中直接用 fun 就能表面是一个方法,甚至都不需要 function。

    而且直接设置该方法的返回类型,也只需要用 :来表面,比如

    1
    2
    3
    fun parse(url: String): String {
    retrun url
    }

    那这里就必须返回一个 String 类型。

  6. Kotlin 方法扩展

    很多时候,Framework提供给我们的API往往都时比较原子的,调用时需要我们进行组合处理,因为就会产生了一些Util类,一个简单的例子,我们想要更快捷的展示Toast信息,在Java中我们可以这样做。

    1
    2
    3
    4
    >public static void longToast(Context context, String message) {
    > Toast.makeText(context, message, Toast.LENGTH_LONG).show();
    >}
    >

    >

    但是Kotlin的实现却让人惊奇,我们只需要重写扩展方法就可以了,比如这个longToast方法扩展到所有的Context对象中,如果不去追根溯源,可能无法区分是Framework提供的还是自行扩展的。

    1
    2
    3
    4
    5
    >fun Context.longToast(message: String) {
    > Toast.makeText(this, message, Toast.LENGTH_LONG).show()
    >}
    >applicationContext.longToast("hello world")
    >

    >

    注意:Kotlin的方法扩展并不是真正修改了对应的类文件,而是在编译器和IDE方面做得处理。使我们看起来像是扩展了方法。

    本次项目中就使用到了该方法,通常在 BaseActivity 中,扩展一些方法比如正常的添加 fragment ,可以直接类似重写一般的扩展:

    1
    2
    3
    4
    5
    6
    7
    8
    //Fragment 添加
    inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
    beginTransaction().func().commit()
    }
    fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int) {
    supportFragmentManager.inTransaction { add(frameId, fragment) }
    }
    1
    2
    fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int) {
    supportFragmentManager.inTransaction { replace(frameId, fragment) }

​ 那我们在之后继承了 BaseActivity 的 Activity 中都可以直接调用 addFragment 和 replaceFragment 方法:

1
addFragment(EasyFragment.Single.getInstance(i.toString()), fragmentlayout.id)
  1. Kotlin 单例模式

    我们经常要在一些 class 中使用到单例模式,一般好的 java 中都是这么调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    private static MyPicasso myPicasso;
    private synchronized static void createInstance() {
    if (myPicasso == null)
    myPicasso = new MyPicasso();
    }
    public static MyPicasso getInstance() {
    if (myPicasso == null)
    createInstance();
    return myPicasso;
    }

    通过一个 synchronized 进行一个异步创建单例,但是这样代码很多了就,而且不方便。但是在 Kotlin 中可以直接一个 Object 解决问题。并且可以在这个 object中进行一些具体操作,很方便。我在一个 Fragment 实现单例,并且可以直接传入参数。相当简洁。

    1
    2
    3
    4
    5
    6
    7
    object Single {
    fun getInstance(str: String): EasyFragment {
    val instance: EasyFragment = EasyFragment()
    instance.mStr = str
    return instance
    }
    }
  2. Kotlin 数据序列化

    在 java 中一般都要继承 2 个接口 Serializable 或者 Parcelable ,虽然现在有了工具不需要手写 get 和 set,但是代码量在那边摆着呢,显得很麻烦。

    而在 Kotlin 中,直接一个 data class 就直接搞定,Kotlin 中自动包含了setter 和 getter。

    1
    2
    3
    4
    /**
    * 数据Model
    */
    data class Data(val x: Int, val y: Int, val xn: Int, val yn: Int) {}
总结

通过本次实际程序,基本可以对 Kotlin 的用法有个基本了解了,有不会的地方大可以直接 Google,国外对于该方面的资料还是比国内多很多。欢迎各位的学习讨论。

源码地址

废话不多说,直接上源码:源码地址

Django学习一

发表于 2017-09-14

前言: 最近 python 语言在编程界的受欢迎力度不减,长期稳居编程语言前 5 。而 python 在欧美国家,是拿来作为入门级编程语言。python 不仅可以用来编写传统 WEB 开发,GUI 应用开发,也可以用到现在主流的数据分析,以及人工智能方向,可谓是一个多功能型的语言。而本次学习,主要是以 Web 开发为主。

打好学习 pyhton 的基础,为以后的机器学习做准备。

Python 之 Django

python web 的开发框架现在越来越多,并且也都趋于成熟。现在主流的 python web 开发框架包含有 Django, Flask , Pyramid , Tornado 等。具体前 3 种框架的区别可以看这里。

本篇主要是以 Django 为学习。

  • Django 框架是什么?

    Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source.(来自 Django 官网)

  • 学习中用到的工具

    本人是在 Windows 10 版本上进行的开发,在这里推荐同学们使用 MAC 或者 Linux 进行开发,因为在后 2 个平台上面已经自动集成 python 的开发环境。这里主要介绍如何在 Windows 上进行有一个比较舒适的 python 开发姿势。

    • 工具:Viual Studio Code,优点我不累赘了,google 一下很多关于该工具的有点,使用起来也是美滋滋,相对于之前我使用过的 Sublime 3 ,个人推荐这款工具作为 python 开发(个人觉得 Sublime 的使用对于新手比较不那么友好)。

    • 环境:python 安装,这里也是 Google 一下,很多教程,基本大同小异,这里本人用的是 python 3.5.4 64 bit 版本。(廖雪峰的 Python 安装教程)

    • git 环境安装,因为后续的 Django 一些环境部署,包括 python 中一些类库的按照都需要用到 windows 指令,当然 vs code 工具中也自带有终端,不想按照可以略过。(廖雪峰的 Git 安装教程)

    • vs code 扩展插件按照:vs code的还有一个点好处就 是安装插件相当方便,并且配置相对于其他开发工具来说比较简单。点击左侧的扩展图标,进入到扩展程序。如下图:

      扩展界面

      进入之后直接在搜索栏输入要安装的程序名称,就可以直接安装,下面我记录下本人所用到的扩展程序。

      • 1 Auto Close Tag
      • 2 Auto Rename Tag
      • 3 Beautify
      • 4 Bootstrap 3 Snippets
      • 5 Class autocomplete for HTML
      • 6 Dash
      • 7 Debugger for Chrome
      • 8 Debugger for Firefox
      • 9 Django Template
      • 10 ESLint
      • 11 Git History
      • 12 HTML CSS Support
      • 13 HTML Snippets
      • 14 JavaScript SnipperPack
      • 15 JQuery Code Snippets
      • 16 JS-CSS-HTML Formatter
      • 17 Language Support for Java
      • 18 Mithril Emmet
      • 19 Path Intellisense
      • 20 Python
      • 21 Python for VSCode
      • 22 React Native Tools
      • 23 Ractjs code snippets
      • 24 Typings auto installer
      • 25 vscode-flake8
      • 26 vscode-icons

      这里就是我 vscode 中所有的扩展程序,这里我就不一一描述每个扩展程序的作用了,其中很多是关于代码规范,以及代码提示高亮准备的,为开发提供了便捷。因为整个 DJango 开发中涉及的内容很多,包含了数据库,python , html-css-js , MVC 开发模式等各种内容。后续有需要的话,会另写一篇关于 vscode 的文章,再详细介绍这些扩展程序的作用。

      注意:其中部分扩展程序是需要在 vscode 的设置用进行文件部署设置的。

      点击 文件 -> 首选项 -> 设置,到用户设置界面,在右侧可以进行覆盖设置,这些设置可以覆盖到原有的设置部分, 其中 “typescript.tsdk” 这一项可以略过,这是我在 React Ntive设置中进行的配置,其他都配置上。基本到这里,你的 vscode 就已经配置的差不多了。如下图:

      ​

      用户设置

​

Django 的简单创建以及使用

VS Code提供了两种范围的设置,分别是User及Workspace. 用户级别的设置可以理解为全局设置,其中的设置对任一vscode实例都生效。工作区级别的设置则仅针对当前项目生效,配置文件存放在项目的 .vscode 文件夹中。一般在项目开发中我们使用工作区级别的设置。以下步骤记录了如何建立Django项目的基本目录结构。

  1. 首先创建一个空的文件夹,作为一个工作空间使用,例如 :D:\python\django_project,进入该文件夹,并且右击空白选择 git bash 进入到终端(没安装 Git 的同学可以用自带的终端自行切换到该目录)。

  2. 在该终端输入 virtualenv env 创建 env 文件夹

  3. 在该目录下在创建一个 requirenments.txt 文件,文件中添加以下内容,pylint为python的静态语法检测器,pylint-django 是适用于 django 项目的语法检查其插件,autopep8 是代码格式化工具

    1
    2
    3
    4
    django<1.10
    pylint
    pylint-django
    autopep8
  4. 双击 \env\Scripts\activate.bat 激活虚拟环境

  5. 在 git bash 中执行 pip install -r requirenments.txt 安装相关模块

  6. 再执行 django-admin startproject mysitedemo ,在当前目录下创建一个 django 项目文件夹

  7. 进入 mysitedemo 目录,再执行 python manage.py startapp mysite 来新建一个 django app。

  8. 在 Vs Code 中选择打开文件夹方式打开该文件 django_project

  9. 在 Vs Code 中按下 Ctrl + Shift + P ,输入 Python ,然后选择 Python: Select Workspace Interpreter,在弹出框中选择 env 下的 python 。如图:

    选择图

  10. 在 Vs Code 中按下 Ctrl + Shift + P,输入 workspace,选择Preferences: Open Workspace Settings,配置如下的 JSON文件。

    配置

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "python.pythonPath": "d:/python/django_project/env/scripts/python.exe",
    "python.linting.pylintPath": "pylint",
    "python.linting.pylintArgs": [
    "--load-plugins", "pylint_django"
    ],
    "python.formatting.autopep8Path": "autopep8"
    }

    配置完成后,我们就可以继续项目开发了。

Django的简单使用

首先我们看下项目目录结构的具体情况

项目结构

熟悉下目录结构:

  • 最外层的 mysitedemo 也就是我们手动创建的文件夹,它在这里仅仅是项目的容器,它的命名对整个项目 都无关紧要,可以随便改。
  • mysitedemo/manage.py,这是一个命令工具,可以使你用多种方式对 Django 项目进行交互。包括之前我们用来的命令 startapp 来创建的 mysite.
  • mysitedemo/mysitedemo目录是项目中真正的 Python 包
  • mysitedemo/init.py,这是一个空文件,它的目的就是告诉 Python 这个目录应该被看做一个 Python 包。
  • mysitedemo/settings.py,该文件为 Django 项目的配置文件
  • mysietedemo/urls.py,该文件为 Django 的 URL 声明
  • mysitedemo/wsgi.py,用于你的项目的与WSGI兼容的Web服务器入口
  • mysitedemo/mysite,该文件夹为创建的新文件夹,用来做具体的项目

数据库的建立

Django 默认使用 SQLite。当然 Django 对其他数据库也是支持的。通常使用数据库需要在 mysitedemo/setting.py 中进行配置,使用默认数据库比较简单明了,如果使用其他远程数据库或者已经创建好的本地数据库文件,比如 MySQL,就需要在配置文件中进行详细的配置,添加User,password,hoset等额外的配置。

这里我们优先选用默认的 SQLite,方便使用和学习。

打开 setting.py 文件,找到 DATABASES ,在 ‘default’ 条目中可以看到以下的配置

1
2
3
4
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}

当使用 SQLite,你不需要事先创建任何东西,数据库文件将会在需要的时候自动创建。

这里我们暂时需要一个数据库表,直接运行以下命令:

1
python manage.py migrate

这时,项目目录中会直接出现一个数据库表 db.sqlite3

本次学习总结

本次主要介绍了 Django以及一些插件,和简单的DJango 项目部署,在下一节,将会采取实际项目来针对学习,根据具体需求来进行学习。

写的不好的地方,请大家积极反应。

牛客网算法之动态规则

发表于 2017-07-04

最近几天一直在牛客网上进行算法试题测试,在博客做下简单的记录。

原题地址

侵权删

题目:构造回文

题目简介:

给定一个字符串s,你可以从中删除一些字符,使得剩下的串是一个回文串。如何删除才能使得回文串最长呢?

输出需要删除的字符个数。

输入描述:
1
输入数据有多组,每组包含一个字符串s,且保证:1<=s.length<=1000.
输出描述:
1
对于每组数据,输出一个整数,代表最少需要删除的字符个数。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String s1 = sc.next();
String s2 = new StringBuilder(s1).reverse().toString();
int[][] dp = new int[s1.length() + 1][s2.length() + 1];
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[0].length; j++) {
dp[i][j] = s1.charAt(i - 1) == s2.charAt(j - 1) ? dp[i - 1][j - 1] + 1 : Math
.max(dp[i - 1][j], dp[i][j - 1]);
//System.out.println(i + ":" + dp[i][j]);
}
}
System.out.println(s1.length() - dp[s1.length()][s2.length()]);
}
}
}

代码说明:

具体采用动态规则进行计算,原题目网中有大神进行了阐述,如下:

提到回文串,自然要利用回文串的特点,想到将源字符串逆转后,“回文串”(不一定连续)相当于顺序没变求原字符串和其反串的最大公共子序列(不是子串,因为可以不连续)的长度(使用动态规划很容易求得),然后用原字符串的长度减去这个最大公共子串的长度就得到了最小编辑长度。(侵权删)

动态规则还有很多类似的算法,需要再接再厉!进行深一层次的学习。

牛客网算法一

发表于 2017-07-04

最近几天一直在牛客网上进行算法试题测试,在博客做下简单的记录。

原题地址

侵权删

题一:黑白卡片题

题目简介:

牛牛有n张卡片排成一个序列.每张卡片一面是黑色的,另一面是白色的。初始状态的时候有些卡片是黑色朝上,有些卡片是白色朝上。牛牛现在想要把一些卡片翻过来,得到一种交替排列的形式,即每对相邻卡片的颜色都是不一样的。牛牛想知道最少需要翻转多少张卡片可以变成交替排列的形式。

输入描述:
1
输入包括一个字符串S,字符串长度length(3 ≤ length ≤ 50),其中只包含'W'和'B'两种字符串,分别表示白色和黑色。整个字符串表示卡片序列的初始状态。
输出描述:
1
输出一个整数,表示牛牛最多需要翻转的次数

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Scanner;
public class wb {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
String line = input.next();
int b = 0, w = 0;//w为WBWBWB序列需要翻次数,b为BWBWBWB序列需要翻次数
for (int i = 0, len = line.length(); i < len; i++) {
char c = line.charAt(i);
//判断位置
//奇数位置
if ((i & 1) == 0) {
if (c == 'W') b++;
else w++;
} else {
if (c == 'B') b++;
else w++;
}
}
System.out.println(Math.min(b, w));
}
}
}

代码说明:

代码中的 i&1 用来判断 i 奇偶,基本就是遍历所有的元素。因为整个排序片段只有 2 种情况,所有只需要获取 2 种情况下所得结果再进行比较得到最小值,就是最终结果。

题目二:黑化的牛牛

题目简介:

牛牛变得黑化了,想要摧毁掉地球。但他忘记了开启地球毁灭器的密码。牛牛手里有一个字符串S,牛牛还记得从S中去掉一个字符就恰好是正确的密码,请你帮牛牛求出他最多需要尝试多少次密码。

如样例所示S = “ABA”,3个可能的密码是”BA”, “AA”, “AB”.

当S = “A”, 牛牛唯一可以尝试的密码是一个空的密码,所以输出1.

输入描述:
1
输入包括一个字符串S,字符串长度length(1 ≤ length ≤ 50),其中都是从'A'到'Z'的大写字母。
输出描述:
1
输出一个整数,表示牛牛最多需要尝试的密码次数。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.HashSet;
import java.util.Scanner;
public class Niuniuaaba {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
//方法一:
// String s = input.next();
// if (s.length() == 0) {
// return;
// } else if (s.length() == 1) {
// System.out.print(s.length() + "");
// } else {
// char[] chas = s.toCharArray();
// int re = 0;
// for (int i = 1; i < chas.length; i++) {
// if (chas[i - 1] == chas[i]) {
// re++;
// }
// }
// System.out.print(chas.length - re + "");
// }
//方法二
String s2 = input.next();
if (s2.length() == 0) {
return;
} else if (s2.length() == 1) {
System.out.print(s2.length() + "");
} else {
StringBuffer s3 = new StringBuffer(s2);
HashSet<String> hashSet = new HashSet<>();
for (int i = 0; i < s2.length(); i++) {
s3.deleteCharAt(i);
boolean add = hashSet.add(s3.toString());
s3.insert(i, s2.charAt(i));
}
System.out.print(hashSet.size() + "");
}
}
}
}

代码说明:

本代码未做字符串‘A-Z’的判断;

方法一:这里根据只要遇到 2 个相同元素,就只有可能会出现一个秘密组, re(代码重复秘密组)就 +1 ,而如果没有重复组,最多尝试的秘密组其实就是依次去掉字符串中的每个个元素所得,即 chas.lenth ,所以最终解为 chas.length - re.

方法二:这里根据了 java Hashset 的定义去运算. HashSet 中不能添加同一个元素,所以,遍历所以字符串,依次去掉一个字符串组成新字符串加入 hashSet 中,最终 hashSet 的长度就为答案.

题目三:膨胀的牛牛

题目简介:

牛牛以草料为食。牛牛有一天依次遇到n堆被施展了魔法的草料,牛牛只要遇到一堆跟他当前相同大小的草料,它就会把草料吃完,而使自己的大小膨胀一倍。一开始牛牛的大小的是A,然后给出牛牛依次遇到的n堆草料的大小。请计算牛牛最后的大小。

输入描述:
1
2
输入包括两行,第一行包含两个整数n和A(1 ≤ n ≤ 200, 1 ≤ A ≤ 1,000,000,000)
第二行包括n个整数,表示牛牛依次遇到的草料堆大小a_i(1 ≤ a_i ≤ 1,000,000,000)
输出描述:
1
输出一个整数,表示牛牛最后的大小。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner input = new Scanner(System.in);
int num = input.nextInt();//所有整数
int a = input.nextInt();//牛牛初始大小
int[] a_i=new int[num];
for(int i = 0;i < num; i++){
a_i[i] = input.nextInt();
}
for(int j=0 ;j<num;j++){
if(a==a_i[j]){
a*=2;
}
}
System.out.print(a);
}
}

题目四:序列交换

题目简介:

牛牛有一个长度为n的整数序列s,羊羊要在牛牛的序列中选择不同的两个位置,然后交换这两个位置上的元素。现在需要求出羊羊交换后可以得到的不同的序列个数。(注意被交换的两元素值可能相同)。

如序列{1, 47},输出1.羊羊必须交换仅有的两个元素,得到序列{47, 1}。羊羊必须交换,不能保留原有的序列。

{1, 2, 1},输出3.羊羊通过交换可以得到{2, 1, 1},{1, 1, 2},{1, 2, 1}这三个序列。

输入描述:
1
2
输入包括两行,第一行为一个整数n(2 ≤ n ≤ 50),即序列的长度。
第二行n个整数,表示序列的每个元素a_i(1 ≤ a_i ≤ 50),以空格分割。
输出描述:
1
输出一个整数,表示羊羊可以得到的不同的序列个数

代码说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = 0;//输入总数
int sum = 0;
int i, j;
boolean hasSame = false;//相同判断
num = sc.nextInt();
int[] test = new int[num];
for (i = 0; i < num; i++) {
test[i] = sc.nextInt();
}
if (num == 2) {
System.out.print(1 + "");
return;
}
for (i = 0; i < num; i++) {
for (j = i + 1; j < num; j++) {
if (test[i] != test[j]) {
sum++;
} else {
hasSame = true;
}
}
}
if (hasSame) {
System.out.print(sum + 1 + "");
} else {
System.out.print(sum + "");
}
}
}

代码说明:

根据交换条件,只要交换的 2 个数字不一样,交换出来的数组肯定为新的一列数组,因此sum在这种情况下每次 +1,而当有 2 个数字一样的时候,在最后结果需要再 +1,因为相同的数字在同等的情况下只允许出现同一个情况,所以不管有多少组重复数字,都只需要最终 +1.当然,这里也可以运用 HashSet 的方式来解决,跟问题二中描述类似.

问题五:丑陋的字符串

问题简介:

牛牛喜欢字符串,但是他讨厌丑陋的字符串。对于牛牛来说,一个字符串的丑陋值是字符串中相同连续字符对的个数。比如字符串“ABABAABBB”的丑陋值是3,因为有一对”AA”和两对重叠的”BB”。现在给出一个字符串,字符串中包含字符’A’、’B’和’?’。牛牛现在可以把字符串中的问号改为’A’或者’B’。牛牛现在想让字符串的丑陋值最小,希望你能帮帮他。

输入描述:
1
输入包括一个字符串s,字符串长度length(1 ≤ length ≤ 50),字符串只包含'A','B','?'三种字符。
输出描述:
1
输出一个整数,表示最小的丑陋值

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
char[] chars;
while (sc.hasNext()) {
String s = sc.next();
int count = 0;
if (s.length() == 1) {
System.out.print(0 + "");
return;
} else {
//去除最前面的所有?
int j = 0;
while (j != s.length() && s.charAt(j) == '?') {
j++;
}
if (j == s.length()) {
System.out.print(0 + "");
} else {
s = s.substring(j, s.length());
chars = s.toCharArray();
for (int i = 1; i < chars.length; i++) {
if ('?' == chars[i]) {
if (chars[i - 1] == 'A') {
chars[i] = 'B';
} else {
chars[i] = 'A';
}
} else {
if (chars[i - 1] == chars[i]) {
count++;
}
}
}
System.out.print(count + "");
}
}
}
}
}

代码说明:

因为只需要判断连续 2 位字符串是否相同,就可以判断到底有多少组丑陋值,按照顺时针方向,首先去除在开头所有的 ?,在依次判断接下来的字符串中是否包有 ?,有就把 ? 改成跟前一个字符不同的字符,再循环判断,最终可以得到所以的丑陋值 count.

题目六:庆祝 61

题目简介:

牛家庄幼儿园为庆祝61儿童节举办庆祝活动,庆祝活动中有一个节目是小朋友们围成一个圆圈跳舞。牛老师挑选出n个小朋友参与跳舞节目,已知每个小朋友的身高h_i。为了让舞蹈看起来和谐,牛老师需要让跳舞的圆圈队形中相邻小朋友的身高差的最大值最小,牛老师犯了难,希望你能帮帮他。

如样例所示:

当圆圈队伍按照100,98,103,105顺时针排列的时候最大身高差为5,其他排列不会得到更优的解

输入描述:
1
2
输入包括两行,第一行为一个正整数n(3 ≤ n ≤ 20)
第二行为n个整数h_i(80 ≤ h_i ≤ 140),表示每个小朋友的身高。
输出描述:
1
输出一个整数,表示满足条件下的相邻小朋友身高差的最大值。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] m = new int[n];
for (int i = 0; i < n; i++) {
m[i] = sc.nextInt();
}
Arrays.sort(m);
int max = Math.max(m[1] - m[0], m[n - 1] - m[n - 2]);
for (int j = 0; j < n - 2; j++) {
max = Math.max(m[j + 2] - m[j], max);
}
System.out.print(max);
}
}

代码说明:

首先该想到序号间隔越小差值也越小. 所以应该按顺序左右插入,这样形成一种金子塔型, 这样序号差值最大就只有2了,如有 1,2,3,4,5,6.7.应该排成7,5,3,1,2,4,6或1,3,5,7,6,4,2.(不是自己的思路,看到一个大神的思路,侵权删)

先排序,把所以的数字根据升降排序列好,根据金字塔型,最顶端数字为最大数或者最小数,左侧一列为与顶端数字位置相差为2的数字(m[j+2]),右侧则为相差1的数字(m[j+1]),而算出最大身高差,可以看出,只需要计算金字塔左侧和右侧每个相邻数字之差中的最大值就可以获得.

说明:上面所有代码段本人都亲自敲过,并且在牛客网都可以通过。

牛客网试题--系列一

发表于 2017-07-04

最近几天一直在牛客网上进行算法试题测试,在博客做下简单的记录。

原题地址

侵权删

题一:黑白卡片题

题目简介:

牛牛有n张卡片排成一个序列.每张卡片一面是黑色的,另一面是白色的。初始状态的时候有些卡片是黑色朝上,有些卡片是白色朝上。牛牛现在想要把一些卡片翻过来,得到一种交替排列的形式,即每对相邻卡片的颜色都是不一样的。牛牛想知道最少需要翻转多少张卡片可以变成交替排列的形式。

输入描述:
1
输入包括一个字符串S,字符串长度length(3 ≤ length ≤ 50),其中只包含'W'和'B'两种字符串,分别表示白色和黑色。整个字符串表示卡片序列的初始状态。
输出描述:
1
输出一个整数,表示牛牛最多需要翻转的次数

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Scanner;
public class wb {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
String line = input.next();
int b = 0, w = 0;//w为WBWBWB序列需要翻次数,b为BWBWBWB序列需要翻次数
for (int i = 0, len = line.length(); i < len; i++) {
char c = line.charAt(i);
//判断位置
//奇数位置
if ((i & 1) == 0) {
if (c == 'W') b++;
else w++;
} else {
if (c == 'B') b++;
else w++;
}
}
System.out.println(Math.min(b, w));
}
}
}

代码说明:

代码中的 i&1 用来判断 i 奇偶,基本就是遍历所有的元素。因为整个排序片段只有 2 种情况,所有只需要获取 2 种情况下所得结果再进行比较得到最小值,就是最终结果。

题目二:黑化的牛牛

题目简介:

牛牛变得黑化了,想要摧毁掉地球。但他忘记了开启地球毁灭器的密码。牛牛手里有一个字符串S,牛牛还记得从S中去掉一个字符就恰好是正确的密码,请你帮牛牛求出他最多需要尝试多少次密码。

如样例所示S = “ABA”,3个可能的密码是”BA”, “AA”, “AB”.

当S = “A”, 牛牛唯一可以尝试的密码是一个空的密码,所以输出1.

输入描述:
1
输入包括一个字符串S,字符串长度length(1 ≤ length ≤ 50),其中都是从'A'到'Z'的大写字母。
输出描述:
1
输出一个整数,表示牛牛最多需要尝试的密码次数。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.HashSet;
import java.util.Scanner;
public class Niuniuaaba {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
//方法一:
// String s = input.next();
// if (s.length() == 0) {
// return;
// } else if (s.length() == 1) {
// System.out.print(s.length() + "");
// } else {
// char[] chas = s.toCharArray();
// int re = 0;
// for (int i = 1; i < chas.length; i++) {
// if (chas[i - 1] == chas[i]) {
// re++;
// }
// }
// System.out.print(chas.length - re + "");
// }
//方法二
String s2 = input.next();
if (s2.length() == 0) {
return;
} else if (s2.length() == 1) {
System.out.print(s2.length() + "");
} else {
StringBuffer s3 = new StringBuffer(s2);
HashSet<String> hashSet = new HashSet<>();
for (int i = 0; i < s2.length(); i++) {
s3.deleteCharAt(i);
boolean add = hashSet.add(s3.toString());
s3.insert(i, s2.charAt(i));
}
System.out.print(hashSet.size() + "");
}
}
}
}

代码说明:

本代码未做字符串‘A-Z’的判断;

方法一:这里根据只要遇到 2 个相同元素,就只有可能会出现一个秘密组, re(代码重复秘密组)就 +1 ,而如果没有重复组,最多尝试的秘密组其实就是依次去掉字符串中的每个个元素所得,即 chas.lenth ,所以最终解为 chas.length - re.

方法二:这里根据了 java Hashset 的定义去运算. HashSet 中不能添加同一个元素,所以,遍历所以字符串,依次去掉一个字符串组成新字符串加入 hashSet 中,最终 hashSet 的长度就为答案.

题目三:膨胀的牛牛

题目简介:

牛牛以草料为食。牛牛有一天依次遇到n堆被施展了魔法的草料,牛牛只要遇到一堆跟他当前相同大小的草料,它就会把草料吃完,而使自己的大小膨胀一倍。一开始牛牛的大小的是A,然后给出牛牛依次遇到的n堆草料的大小。请计算牛牛最后的大小。

输入描述:
1
2
输入包括两行,第一行包含两个整数n和A(1 ≤ n ≤ 200, 1 ≤ A ≤ 1,000,000,000)
第二行包括n个整数,表示牛牛依次遇到的草料堆大小a_i(1 ≤ a_i ≤ 1,000,000,000)
输出描述:
1
输出一个整数,表示牛牛最后的大小。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner input = new Scanner(System.in);
int num = input.nextInt();//所有整数
int a = input.nextInt();//牛牛初始大小
int[] a_i=new int[num];
for(int i = 0;i < num; i++){
a_i[i] = input.nextInt();
}
for(int j=0 ;j<num;j++){
if(a==a_i[j]){
a*=2;
}
}
System.out.print(a);
}
}

题目四:序列交换

题目简介:

牛牛有一个长度为n的整数序列s,羊羊要在牛牛的序列中选择不同的两个位置,然后交换这两个位置上的元素。现在需要求出羊羊交换后可以得到的不同的序列个数。(注意被交换的两元素值可能相同)。

如序列{1, 47},输出1.羊羊必须交换仅有的两个元素,得到序列{47, 1}。羊羊必须交换,不能保留原有的序列。

{1, 2, 1},输出3.羊羊通过交换可以得到{2, 1, 1},{1, 1, 2},{1, 2, 1}这三个序列。

输入描述:
1
2
输入包括两行,第一行为一个整数n(2 ≤ n ≤ 50),即序列的长度。
第二行n个整数,表示序列的每个元素a_i(1 ≤ a_i ≤ 50),以空格分割。
输出描述:
1
输出一个整数,表示羊羊可以得到的不同的序列个数

代码说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num = 0;//输入总数
int sum = 0;
int i, j;
boolean hasSame = false;//相同判断
num = sc.nextInt();
int[] test = new int[num];
for (i = 0; i < num; i++) {
test[i] = sc.nextInt();
}
if (num == 2) {
System.out.print(1 + "");
return;
}
for (i = 0; i < num; i++) {
for (j = i + 1; j < num; j++) {
if (test[i] != test[j]) {
sum++;
} else {
hasSame = true;
}
}
}
if (hasSame) {
System.out.print(sum + 1 + "");
} else {
System.out.print(sum + "");
}
}
}

代码说明:

根据交换条件,只要交换的 2 个数字不一样,交换出来的数组肯定为新的一列数组,因此sum在这种情况下每次 +1,而当有 2 个数字一样的时候,在最后结果需要再 +1,因为相同的数字在同等的情况下只允许出现同一个情况,所以不管有多少组重复数字,都只需要最终 +1.当然,这里也可以运用 HashSet 的方式来解决,跟问题二中描述类似.

问题五:丑陋的字符串

问题简介:

牛牛喜欢字符串,但是他讨厌丑陋的字符串。对于牛牛来说,一个字符串的丑陋值是字符串中相同连续字符对的个数。比如字符串“ABABAABBB”的丑陋值是3,因为有一对”AA”和两对重叠的”BB”。现在给出一个字符串,字符串中包含字符’A’、’B’和’?’。牛牛现在可以把字符串中的问号改为’A’或者’B’。牛牛现在想让字符串的丑陋值最小,希望你能帮帮他。

输入描述:
1
输入包括一个字符串s,字符串长度length(1 ≤ length ≤ 50),字符串只包含'A','B','?'三种字符。
输出描述:
1
输出一个整数,表示最小的丑陋值

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
char[] chars;
while (sc.hasNext()) {
String s = sc.next();
int count = 0;
if (s.length() == 1) {
System.out.print(0 + "");
return;
} else {
//去除最前面的所有?
int j = 0;
while (j != s.length() && s.charAt(j) == '?') {
j++;
}
if (j == s.length()) {
System.out.print(0 + "");
} else {
s = s.substring(j, s.length());
chars = s.toCharArray();
for (int i = 1; i < chars.length; i++) {
if ('?' == chars[i]) {
if (chars[i - 1] == 'A') {
chars[i] = 'B';
} else {
chars[i] = 'A';
}
} else {
if (chars[i - 1] == chars[i]) {
count++;
}
}
}
System.out.print(count + "");
}
}
}
}
}

代码说明:

因为只需要判断连续 2 位字符串是否相同,就可以判断到底有多少组丑陋值,按照顺时针方向,首先去除在开头所有的 ?,在依次判断接下来的字符串中是否包有 ?,有就把 ? 改成跟前一个字符不同的字符,再循环判断,最终可以得到所以的丑陋值 count.

题目六:庆祝 61

题目简介:

牛家庄幼儿园为庆祝61儿童节举办庆祝活动,庆祝活动中有一个节目是小朋友们围成一个圆圈跳舞。牛老师挑选出n个小朋友参与跳舞节目,已知每个小朋友的身高h_i。为了让舞蹈看起来和谐,牛老师需要让跳舞的圆圈队形中相邻小朋友的身高差的最大值最小,牛老师犯了难,希望你能帮帮他。

如样例所示:

当圆圈队伍按照100,98,103,105顺时针排列的时候最大身高差为5,其他排列不会得到更优的解

输入描述:
1
2
输入包括两行,第一行为一个正整数n(3 ≤ n ≤ 20)
第二行为n个整数h_i(80 ≤ h_i ≤ 140),表示每个小朋友的身高。
输出描述:
1
输出一个整数,表示满足条件下的相邻小朋友身高差的最大值。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] m = new int[n];
for (int i = 0; i < n; i++) {
m[i] = sc.nextInt();
}
Arrays.sort(m);
int max = Math.max(m[1] - m[0], m[n - 1] - m[n - 2]);
for (int j = 0; j < n - 2; j++) {
max = Math.max(m[j + 2] - m[j], max);
}
System.out.print(max);
}
}

代码说明:

首先该想到序号间隔越小差值也越小. 所以应该按顺序左右插入,这样形成一种金子塔型, 这样序号差值最大就只有2了,如有 1,2,3,4,5,6.7.应该排成7,5,3,1,2,4,6或1,3,5,7,6,4,2.(不是自己的思路,看到一个大神的思路,侵权删)

先排序,把所以的数字根据升降排序列好,根据金字塔型,最顶端数字为最大数或者最小数,左侧一列为与顶端数字位置相差为2的数字(m[j+2]),右侧则为相差1的数字(m[j+1]),而算出最大身高差,可以看出,只需要计算金字塔左侧和右侧每个相邻数字之差中的最大值就可以获得.

说明:上面所有代码段本人都亲自敲过,并且在牛客网都可以通过。

python杨辉三角

发表于 2017-05-03

最近一直在自学 Python ,学习过一段时间后,为了方便以后自己的记忆。写一些学习过程中的东西,用来来记录之前的学习过程,本篇通过杨辉三角来记录下之前的学习经历。

平时主要在廖雪峰老师的教程网站上进行学习,这里要感谢廖雪峰老师的无私分享。

廖雪峰的官方网址

杨辉三角:

前提:每行端点与结尾的数为1.

  1. 每个数等于它上方两数之和。
  2. 每行数字左右对称,由1开始逐渐变大。
  3. 第n行的数字有n项。
  4. 第n行数字和为2n-1。

还有其他各种特性。

这里我们主要利用第一点的特性来实现该算法。

  • 先贴代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def triangles():
    L = [1]
    while True:
    yield L
    L = [1] + [L[i] + L[i+1] for i in range(0,len(L)-1) if len(L) >= 2]
    L.append(1)
    n = 0
    for t in triangles():
    print(t)
    n = n + 1
    if n == 6:
    break

代码块说明:

  • def : 这在 Python 中代表一个方法,triangles()就是一个方法,可以在后面的程序用直接调用,如:

    1
    for t in triangles():

    该段直接调用了该方法,跟其他高级语言中调用,是大同小异。

  • yield : 带有 yield 的函数在 Python 中被称之为 generator(生成器),生成器的内容就不在此累赘,不了解的同学可以 google 一下。根据我自学过程,统一采用我 google 到的答案进行描述。这里将用如何生成斐波那契數列来进行一系列说明。

    斐波那契(Fibonacci)数列是一个非常简单的递归数列,除了第一个第二个数外,任意一个数都可由两个数相加得到。用计算机程序输出菲波那切数列的前 N 个数是一个非常简单的问题,许多初学者都可以轻易写出如下函数:

    • 方法1:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def fab(max):
    n, a, b = 0, 0 ,1
    while n < max:
    print(b)
    a, b = b, a + b
    n = n + 1
    # 执行fab(5):
    fab(5)

    得到:

    1
    2
    3
    4
    5
    1
    1
    2
    3
    5

    结果没问题,但是这里会有一个问题产生,直接在 fab 函数中用 print 打印数字会导致该函数可复用性较差,因为无法获取该函数生成的数列。

    而为了提高 fab 函数的可复用性,最好不要直接打印数列,而是返回一个list.

    • 方法2:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def fab(max):
    n, a, b = 0, 0 ,1
    L = []
    while n < max:
    L.append(b)
    a, b = b, a + b
    n = n + 1
    return L
    # 执行fab(5),并循环输出fab(5)
    for n in fab(5):
    print(n)

    得到结果:

    1
    2
    3
    4
    5
    6
    7
    1
    1
    2
    3
    5
    ***Repl Closed***

    改写后的 fab 函数通过返回的 List 能满足复用性的要求,但是这里又会出现另一个问题,该函数在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,就最好不要用 List 来保存中间结果,而是用过 iterable 对象来迭代。

    • 方法3:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Fab(object):
    def __init__(self,max):
    self.max = max
    self.n, self.a, self.b = 0, 0, 1
    def __iter__(self):
    return self
    def next(self):
    if self.n < self.max:
    r = self.b
    self.a, self.b = self.b, self.a + self.b
    self.n = self.n + 1
    return r
    raise StopIteration()
    s = Fab(5);
    #通过执行next得到r每次r的值
    print(s.next())
    print(s.next())
    print(s.next())
    print(s.next())
    print(s.next())
    print(s.next())#因为只是Fab(5),所以这是第六次,会终止结果

    得到

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    1
    1
    2
    3
    5
    Traceback (most recent call last):
    File "fibonacci.py", line 25, in <module>
    print(s.next())
    File "fibonacci.py", line 16, in next
    raise StopIteration()
    StopIteration
    ***Repl Closed***

    然而,使用 calss 改写的这个版本,代码明显没有方法一使用 fab 函数来的简洁。这时候为了保证具有代码的简洁性的同时又可以获得 iterable 的效果,yield 就派上用场了。

    • 方法4:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def fab(max):
    n, a, b = 0, 0, 1
    while n < max:
    yield b
    a, b = b, a + b
    n = n + 1
    for n in fab(5):
    print(n)

    得到:

    1
    2
    3
    4
    5
    6
    7
    1
    1
    2
    3
    5
    ***Repl Closed***

    结果跟上面的方法都是一样的。但是这里仅仅是把 方法一的 print 改成 yield 就实现了代码简洁和获取 iterable 的效果。

    总结:yield 的作用就是把一个函数变成一个 generator(生成器),带有 yield 的函数也不再是一个简单的函数,Python 解释器会将其视为一个 generator ,调用 fab(5) 不会执行 fab 函数内部的代码,而是返回一个额iterable 对象。

  • for i in range(0,len(L)-1) if len(L) >= 2: 这是一个列表生成式(List Conprehensions),是 Python 内置的非常简单却强大的可以用来创建 List 的生产式。举个栗子:要生成 list [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 可以:

    1
    2
    3
    4
    print(list(range(1,11)))
    结果:
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

    但是如果要得到一个 [1x1, 2x2, 3x3, …, 10x10] 怎么做呢?不用列表生成器的话就是用循环完成:

    1
    2
    3
    4
    5
    6
    7
    8
    L=[]
    for x in range(1,11):
    L.append(x*x)
    print(L)
    结果:
    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

    但是这种循环一看就感觉比较繁琐,而用列表生成式就可以这样使用:

    1
    print([x*x for x in range(1,11)])

    结果和上面循环是一样的,同时我们还可以在后面加上 判断语句,比如现在只输出偶数的结果:

    1
    2
    3
    4
    print([x*x for x in range(1,11) if x%2==0])
    结果:
    [4, 16, 36, 64, 100]

    并且,这里面还可以进行两层循环:

    1
    2
    3
    4
    print([x*y for x in range(1,11) for y in range(2,6) if x%2==0])
    结果:
    [4, 6, 8, 10, 8, 12, 16, 20, 12, 18, 24, 30, 16, 24, 32, 40, 20, 30, 40, 50]

    在本次算法中,灵活运用到列表生成式,省去了很多代码,但是同时,代码的可读性变差了,当然熟悉 python 之后这点可读性可以去掉。

    ​

    输出说明

    算法代码片段的后面输出中,因为 L 定义是一个数组,而在 def 中没有加判断终结条件,所以在后面的 for 语句下,加入了 break ,终结代码。

    函数 triangles 执行后,在函数 triangles里面包含了很多个 L 数组,使用print(t) 则每次输出一个 L 数组,就会形成所以需要的杨辉三角。

本次算法学习到此结束。

12
chicbian

chicbian

11 日志
1 标签
© 2016 - 2018 chicbian
由 Hexo 强力驱动
主题 - NexT.Pisces