Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

LeetCode--Binary Tree Level Order Traversal

$
0
0

Given a binary tree, return the level order traversal of its nodes’ values. (ie, from left to right, level by level).

For example:
Given binary tree [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7

return its level order traversal as:

[
  [3],
  [9,20],
  [15,7]
]

方法一:递归。层次遍历,先遍历左节点,再遍历右节点,每一层用一个动态数组存储数据。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>>result;
        traverse(root,1,result);
        return result;
    }
    void traverse(TreeNode* root,int level,vector<vector<int>>&result){
        if(!root) return;
        if(level>result.size()){
            result.push_back(vector<int>());
        }
        result[level-1].push_back(root->val);
        traverse(root->left,level+1,result);
        traverse(root->right,level+1,result);
    }
};

方法二:非递归。用一个current队列,遍历这一层数据,存到动态数组中,再用一个next队列,存储下一层数据,然后交换到current队列,循环进行。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>>result;
        queue<TreeNode*>current,next;
        if(!root) return result;
        else current.push(root);
        while(!current.empty()){
            vector<int>level;
            while(!current.empty()){
                TreeNode *node=current.front();
                current.pop();    
                level.push_back(node->val);
                if(node->left) next.push(node->left);
                if(node->right) next.push(node->right);
            }
            result.push_back(level);
            swap(current,next);
        }
        return result;
    }
};
作者:qq_20791919 发表于2017/11/21 12:12:49 原文链接
阅读:32 评论:0 查看评论

性能类型的BUG与功能类型的BUG的区别

《剑指offer》刷题笔记(时间效率):数组中出现次数超过一半的数字

$
0
0

《剑指offer》刷题笔记(时间效率):数组中出现次数超过一半的数字



题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

解题思路

思路一:结合快排。数组排序后,如果符合条件的数存在,则一定是数组中间那个数。(比如:1,2,2,2,3;或2,2,2,3,4;或2,3,4,4,4等等)
这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(NlogN)并非最优;

思路二:书上的解法,时间复杂度为O(N)。如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。其实和第一个思路差不多,都是利用次数。
在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。

C++版代码实现

思路一

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers)
    {
        if(numbers.empty()) return 0;

        sort(numbers.begin(), numbers.end()); // 排序,取数组中间那个数
        int middle = numbers[numbers.size() / 2];

        int count=0; // 出现次数
        for(int i=0; i < numbers.size(); ++i)
        {
            if(numbers[i] == middle) ++count;
        }

        return (count > numbers.size()/2) ? middle :  0;
    }
};

思路二

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers)
    {
        if(numbers.empty()) 
            return 0;

        // 遍历每个元素,并记录次数;若与前一个元素相同,则次数加1,否则次数减1
        int result = numbers[0];
        int times = 1; // 次数

        for(int i=1; i < numbers.size(); ++i)
        {
            if(times == 0)
            {
                // 更新result的值为当前元素,并置次数为1
                result = numbers[i];
                times = 1;
            }
            else if(numbers[i] == result)
            {
                ++times; // 相同则加1
            }
            else
            {
                --times; // 不同则减1               
            }
        }

        // 判断result是否符合条件,即出现次数大于数组长度的一半
        times = 0;
        for(int i=0; i < numbers.size(); ++i)
        {
            if(numbers[i] == result) 
            ++times;
        }

        return (times > numbers.size()/2) ? result : 0;
    }
};

Python版代码实现

思路一

import collections
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        tmp = collections.Counter(numbers)
        x = len(numbers)/2
        for k, v in tmp.items():
            if v > x:
                return k
        return 0

思路二

# -*- coding:utf-8 -*-
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        if len(numbers) == 0:
            return 0
        # 遍历每个元素,并记录次数;若与前一个元素相同,则次数加1,否则次数减1
        result = numbers[0]
        times = 1

        for i in range(len(numbers)):
            if times == 0:
                # 更新result的值为当前元素,并置次数为1
                result = numbers[i]
                times = 1
            elif numbers[i] == result:
                times += 1 #相同则加1
            else:
                times -= 1 #不同则减1 

        # 判断result是否符合条件,即出现次数大于数组长度的一半
        times = 0
        for i in range(len(numbers)):
            if numbers[i] == result:
                times += 1
        if times > len(numbers) /2:
            return result
        else:
            return 0

系列教程持续发布中,欢迎订阅、关注、收藏、评论、点赞哦~~( ̄▽ ̄~)~

完的汪(∪。∪)。。。zzz

作者:u011475210 发表于2017/11/21 14:13:14 原文链接
阅读:0 评论:0 查看评论

使用ioctl获取磁盘大小

$
0
0

获取磁盘空间的方法,大部分是根据读取/proc/mounts文件来获取得到,磁盘必须被挂载了之后才能看到,有的还是看不到的,比如:


但是此时在mounts中却查询不到SWAP分区的信息这样计算出来的磁盘大小就是不准确的。mounts文件内容如下:

[root@bogon ~]# cat /proc/mounts 
rootfs / rootfs rw 0 0
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
devtmpfs /dev devtmpfs rw,nosuid,size=485404k,nr_inodes=121351,mode=755 0 0
securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
tmpfs /run tmpfs rw,nosuid,nodev,mode=755 0 0
tmpfs /sys/fs/cgroup tmpfs ro,nosuid,nodev,noexec,mode=755 0 0
cgroup /sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd 0 0
pstore /sys/fs/pstore pstore rw,nosuid,nodev,noexec,relatime 0 0
cgroup /sys/fs/cgroup/net_cls,net_prio cgroup rw,nosuid,nodev,noexec,relatime,net_prio,net_cls 0 0
cgroup /sys/fs/cgroup/blkio cgroup rw,nosuid,nodev,noexec,relatime,blkio 0 0
cgroup /sys/fs/cgroup/cpu,cpuacct cgroup rw,nosuid,nodev,noexec,relatime,cpuacct,cpu 0 0
cgroup /sys/fs/cgroup/pids cgroup rw,nosuid,nodev,noexec,relatime,pids 0 0
cgroup /sys/fs/cgroup/hugetlb cgroup rw,nosuid,nodev,noexec,relatime,hugetlb 0 0
cgroup /sys/fs/cgroup/perf_event cgroup rw,nosuid,nodev,noexec,relatime,perf_event 0 0
cgroup /sys/fs/cgroup/devices cgroup rw,nosuid,nodev,noexec,relatime,devices 0 0
cgroup /sys/fs/cgroup/freezer cgroup rw,nosuid,nodev,noexec,relatime,freezer 0 0
cgroup /sys/fs/cgroup/memory cgroup rw,nosuid,nodev,noexec,relatime,memory 0 0
cgroup /sys/fs/cgroup/cpuset cgroup rw,nosuid,nodev,noexec,relatime,cpuset 0 0
configfs /sys/kernel/config configfs rw,relatime 0 0
/dev/sda3 / xfs rw,relatime,attr2,inode64,noquota 0 0
systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=26,pgrp=1,timeout=300,minproto=5,maxproto=5,direct 0 0
mqueue /dev/mqueue mqueue rw,relatime 0 0
debugfs /sys/kernel/debug debugfs rw,relatime 0 0
hugetlbfs /dev/hugepages hugetlbfs rw,relatime 0 0
nfsd /proc/fs/nfsd nfsd rw,relatime 0 0
/dev/sda1 /boot xfs rw,relatime,attr2,inode64,noquota 0 0
sunrpc /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0
tmpfs /run/user/0 tmpfs rw,nosuid,nodev,relatime,size=99992k,mode=700 0 0
tmpfs /run/user/1000 tmpfs rw,nosuid,nodev,relatime,size=99992k,mode=700,uid=1000,gid=1000 0 0

这时候使用ioctl函数就比较准确了,代码如下:

#include <stdio.h>  
#include <fcntl.h>  
#include <linux/fs.h>  
  
int main(void)  
{  
    int fd;  
    //off_t size  
    unsigned long long size;  
    int len;  
    int r;  
  
    if ((fd = open("/dev/sda", O_RDONLY)) < 0)  
    {  
        printf("open error %d\n");  
        return -1;  
    }  
  
    if ((r = ioctl(fd, BLKGETSIZE64, &size)) < 0)  
    {  
        printf("ioctl error \n");  
        return -1;   
    }  
  
    len = (size>>30);  
    printf("size of sda = %d G\n", len);  
    return 0;  
}  


运行结果如下:

[root@bogon disk]# gcc disk.c 
[root@bogon disk]# ./a.out 
size of sda = 40 G

这里还可以指定分区获取,比如获取sda1、sda2、sda3获取到的内容和lsblk查询出来的是一样的。

除此吃之外还可以使用fdisk这个源码就比较复杂了,也可以使用lsblk,这个源码还没看过。不过看过fdisk的源码相对于ioctrl函数直接获取代码量就大了去了。

作者:kongshuai19900505 发表于2017/11/21 14:22:00 原文链接
阅读:0 评论:0 查看评论

Android LiveData简介(一)

$
0
0
Android LiveData简介(一)


使用Android的LiveData,需要在gradle添加引用:

    compile "android.arch.lifecycle:runtime:1.0.0"
    compile "android.arch.lifecycle:extensions:1.0.0"
    annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
本文是基于三个引用的新版Version 1.0.0写的代码,我在使用其他版本比如Version 1.0.0alpha4,代码运行报错。如果遇到这种情况,请自行添加最新的版本引用。在1.0.0中,个别类和方法已经被Google Android官方废弃或者调整,比如LifecycleActivity已经过时,Android官方已经推荐开发者使用AppCompatActivity替换LifecycleActivity。一些关于Lifecycle的内容和技术,已经被Android官方写入AppCompatActivity,望周知。
LiveData实现基本的Android Activity/Fragment生命周期感知,本身其持有可观察数据,开发者可用LiveData的onActive/onInactive实现与Android生命周期解耦/耦合,进而通过LiveData的postValue或者setValue方法,通知观察者Observer数据的变化并请可观察的变化数据通过Observer的onChanged传导出来。

(一)使用LiveData,首先建立LiveData数据,一般继承自MutableLiveData。
MutableLiveData是LiveData的子类,添加了公共方法setValue和postValue,方便开发者直接使用。setValue必须在主线程调用。postValue可以在后台线程中调用。
本文写一个简单的MyData继承自MutableLiveData。MyData内部实现一个简单的功能,后台运行一个长时的线程任务,该线程实现一个简单功能:
(a)如果当前的Activity处于运行(用户可见)状态,则线程任务不断累计计数器并postValue一个值给任何Observer使用。
(b)如果当前Activity处于没有激活状态,则暂停线程任务,停止累计计数器。
(a)(b)两个功能由一个线程任务完成,具体实现可参见附录文章1。
在LiveData中,onActive方法回调表明当前Activity处于激活状态,也就是Activity处于生命周期的活动状态中(onStart,onResume),可以简单认为当前的Activity处于前台。
LiveData的onInactive处理涉及onActive剩下的生命周期,可以简单理解onInactive是Android的Activity/Fragment处于未激活(后台,比如当前Activity处于生命周期的onStop,onPause)。MyData.java:
package zhangphil.app;

import android.arch.lifecycle.MutableLiveData;
import android.util.Log;

public class MyData extends MutableLiveData<String> {
    private final String TAG = "LiveData";

    private int count = 0;
    private boolean RUN = true;

    private LongTimeWork mThread = new LongTimeWork();

    public MyData() {
        mThread.start();
    }

    @Override
    protected void onActive() {
        super.onActive();
        Log.d(TAG, "onActive");

        RUN = true;
        mThread.interrupt();
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        Log.d(TAG, "onInactive");

        RUN = false;
    }

    private class LongTimeWork extends Thread {
        @Override
        public void run() {
            while (true) {
                try {
                    if (!RUN) {
                        Thread.sleep(Long.MAX_VALUE);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

                count++;
                postValue(String.valueOf(count));

                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


(二)构建Observer,在Observer的onChanged中等待变化数据传导过来。
在LiveData中的数据变化,通过postValue(可后台线程)或者setValue(主线程)设置后,将触发Observer的onChanged,开发者只需onChanged等待最新数据回调即可。
package zhangphil.app;

import android.arch.lifecycle.Observer;
import android.support.annotation.Nullable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MyObserver observer = new MyObserver();

        MyData data = new MyData();
        data.observe(this, observer);
    }

    private class MyObserver implements Observer<String> {
        @Override
        public void onChanged(@Nullable String o) {
            Toast.makeText(getApplicationContext(),String.valueOf(o),Toast.LENGTH_SHORT).show();
        }
    }
}

小结:Android的LiveData处理与Android生命周期相关的有生命周期属性的数据尤为便利。设想,一个任务,如果在Activity处于不可见比如用户按Home键不可见时候,此时可能不希望再做处理,那么就可以在LiveData的onInactive做一个逻辑处理,改变线程任务的状态,如果当前Activity又被用户调回前台,那么可以在LiveData的onActive重新恢复任务的运行。


附录:
1,《Android使用Thread的interrupt与sleep,重启或暂停线程任务》链接: http://blog.csdn.net/zhangphil/article/details/78584136 
作者:zhangphil 发表于2017/11/21 14:29:45 原文链接
阅读:0 评论:0 查看评论

Go实战--golang中使用echo框架中的cors(labstack/echo、rs/cors)

$
0
0

生命不止,继续 go go go!!!

继续搞搞echo框架,今天学习的是cors相关的内容。

什么是cors

CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

跨域资源共享( CORS )机制允许 Web 应用服务器进行跨域访问控制,从而使跨域数据传输得以安全进行。浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以降低跨域 HTTP 请求所带来的风险。

参考网站:
https://www.w3.org/TR/cors/
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

简单请求基本流程
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: http://foo.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8

其中,
Access-Control-Allow-Origin
该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

Access-Control-Allow-Credentials
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

echo框架中使用cors

现在使用echo中的cors,这里官方为我们提供了源码:

package main

import (
    "net/http"

    "github.com/labstack/echo"
    "github.com/labstack/echo/middleware"
)

var (
    users = []string{"Joe", "Veer", "Zion"}
)

func getUsers(c echo.Context) error {
    return c.JSON(http.StatusOK, users)
}

func main() {
    e := echo.New()
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

    // CORS default
    // Allows requests from any origin wth GET, HEAD, PUT, POST or DELETE method.
    // e.Use(middleware.CORS())

    // CORS restricted
    // Allows requests from any `https://labstack.com` or `https://labstack.net` origin
    // wth GET, PUT, POST or DELETE method.
    e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
        AllowOrigins: []string{"http://foo.com", "http://test.com"},
        AllowMethods: []string{echo.GET, echo.PUT, echo.POST, echo.DELETE},
    }))

    e.GET("/api/users", getUsers)

    e.Logger.Fatal(e.Start(":1323"))
}

命令行输入:

curl --D - -H 'Origin: http://foo.com' http://localhost:1323/api/users

输出:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://foo.com
Content-Type: application/json; charset=UTF-8
Vary: Origin
Date: Tue, 21 Nov 2017 06:14:23 GMT
Content-Length: 21

["Joe","Veer","Zion"]

rs/cors

Go net/http configurable handler to handle CORS requests

github地址:
https://github.com/rs/cors

Star: 624

文档地址:
https://godoc.org/github.com/rs/cors

代码1:

package main

import (
    "net/http"

    "github.com/rs/cors"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte("{\"hello\": \"world\"}"))
    })

    // cors.Default() setup the middleware with default options being
    // all origins accepted with simple methods (GET, POST). See
    // documentation below for more options.
    handler := cors.Default().Handler(mux)
    http.ListenAndServe(":8080", handler)
}

命令行输入:

curl -D - -H 'Origin: http://foo.com' http://localhost:8080

输出:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Content-Type: application/json
Vary: Origin
Date: Tue, 21 Nov 2017 06:19:53 GMT
Content-Length: 18

{"hello": "world"}

代码2:

package main

import (
    "net/http"

    "github.com/rs/cors"
)

func main() {
    c := cors.New(cors.Options{
        AllowedOrigins: []string{"http://foo.com"},
    })

    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        w.Write([]byte("{\"hello\": \"world\"}"))
    })

    http.ListenAndServe(":8080", c.Handler(handler))
}

命令行输入:

curl -D - -H 'Origin: http://foo.com' http://localhost:8080

输出:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://foo.com
Content-Type: application/json
Vary: Origin
Date: Tue, 21 Nov 2017 06:29:41 GMT
Content-Length: 18

{"hello": "world"}

这里写图片描述

作者:wangshubo1989 发表于2017/11/21 14:33:17 原文链接
阅读:0 评论:0 查看评论

尝试阅读ReentrantLock、AbstractQueuedSynchronizer源码

$
0
0

提起ReentrantLock,想必大家最熟悉的就是这lock()unlock()这两个方法了,那今天就从这入手吧!


一、类结构

三个内部类:SyncFairSyncNonfairSync

Sync : 同步器基类
FairSync : 实现公平锁的同步器
NonfairSync : 实现非公平锁的同步器

这里写图片描述

Sync 继承 AbstractQueuedSynchronizerFairSyncNonfairSync继承Sync

二、lock()流程(以公平锁为例)

ReentrantLock lock = new ReentrantLock(true);
lock.lock();

当调用lock方法时,当前线程尝试获取锁,下面来看下具体获取过程。

ReentrantLock.FairSync.lock()

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            //获取锁
            acquire(1);
        }

       ...
    }

调用父类的父类的acquire方法

AbstractQueuedSynchronizer.acquire()

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这个acquire方法还是有毫复杂的,流程如下:

1、尝试获取锁(tryAcquire
2、若尝试获取失败,则加入等待队列,并中断自己
3、若尝试获取成功,则占有锁

这里写图片描述

尝试获取锁

ReentrantLock.FairSync.tryAcquire()

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //c:当前锁是否已被线程拥有
            int c = getState();
            if (c == 0) {
            //1.当前锁未被拥有 
            //2.当前线程是否是等待队列的第一个,很公平
            //3.CAS方式设置state值 
            //4.已独占的方式获取当前锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //1.当前锁已被拥有
            //2.重入锁
            //3.设置state值
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

加入等待队列

AbstractQueuedSynchronizer使用双向链表来管理等待队列。结构如下:

这里写图片描述

假如threadA获取了锁,此时threadB尝试获取锁失败,则threadB加入等待队列;

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

1、新node假如队尾(有尾节点)

AbstractQueuedSynchronizer.addWaiter()

private Node addWaiter(Node mode) {
        //model表示独占还是共享模式
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
        //如果有尾节点,将待插入的节点插入到尾节点之后
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //将node节点置为尾节点
        enq(node);
        return node;
    }

这里写图片描述

2、自旋尝试获取锁

threadB被加入等待队列后,不断循环尝试获取锁。

AbstractQueuedSynchronizer.acquireQueued()

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //尝试获取失败是否需要阻塞当前线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //阻塞当前线程
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
acquireQueued.shouldParkAfterFailedAcquire()

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

当线程尝试获取锁失败后,会调用shouldParkAfterFailedAcquire方法要判断是否阻塞自己。AQS(AbstractQueuedSynchronizer)使用的是CLH队列,不断的轮询前驱节点的状态,根据前驱节点的状态来决定后面的工作。
每一个节点都有使用waitStatus来表示自己的状态;具体值有:
① 1(CANCELLED): 线程取消了竞争锁
② -1 (SIGNAL):当前线程的后继节点需要被唤醒(unpark
③ -2 、-3(下篇文章再细说)
④ 0 :默认值

–> 如果前驱节点的状态为-1,则阻塞当前节点;
–> 如果前驱节点状态为1,则循环删除前驱节点,知道前驱节点的waitStatus<=0
–> 如果前驱节点的状态为其他值,则讲前驱节点的waitStatus赋值为-1

注:对于waitStatus值代表的含义是什么?为什么这样设计?我也挺迷茫的,在后几篇文章中我会尽力的去理解

下面来看看阻塞线程的方法parkAndCheckInterrupt

AbstractQueuedSynchronizer.parkAndCheckInterrupt()

private final boolean parkAndCheckInterrupt() {
        //阻塞当前线程
        LockSupport.park(this);
        //返回中断状态,并清空
        return Thread.interrupted();
}

LockSupport.park/unparkObject类中的wait/notify/notifyAll方法类似,都有阻塞线程的作用;两者之后不能互相唤醒,LockSupport.unpark方法可以指定唤醒某一个线程,能够响应中断,但不会抛出中断异常。

最终threadB在执行完LockSupport.park(this)后,就阻塞了!

三、unlock()流程

ReentrantLock.unlock()

    public void unlock() {
        sync.release(1);
    }
AbstractQueuedSynchronizer.release()

//挺简单的
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
ReentrantLock.tryRelease()

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //如果当前线程不是锁拥有者,抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            //是否完全释放锁,
            //如果不是,则还可以重入
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

如果head指向的头结点不为null,且waitStatus !=0,则唤醒后继结点;

private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            //从队未往前查找,找到waitStatus<=0的所有节点中排在最前面的
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

唤醒线程以后,被唤醒的线程将从以下代码中继续往前走:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); // 刚刚线程被挂起在这里了
    return Thread.interrupted();
}
// 又回到这个方法了:acquireQueued(final Node node, int arg),这个时候,node的前驱是head了

三、未解之谜

1、waitStatus值的具体含义?
2、lock、unlock和中断的关系?

作者:disiwei1012 发表于2017/11/21 14:57:05 原文链接
阅读:2 评论:0 查看评论

OpenCV 自学笔记33. 计算图像的均值、标准差和平均梯度

$
0
0

OpenCV 自学笔记33. 计算图像的均值、标准差和平均梯度

均值、标准差和平均梯度是验证图像质量的常用指标。其中:

  • 均值反映了图像的亮度,均值越大说明图像亮度越大,反之越小;
  • 标准差反映了图像像素值与均值的离散程度,标准差越大说明图像的质量越好;
  • 平均梯度反映了图像的清晰度和纹理变化,平均梯度越大说明图像越清晰;

那么,如何使用OpenCV计算图像的均值、标准差和平均梯度呢?

OpenCV提供了几个函数,可以用来帮助我们计算。

1、计算图像的平均梯度

meanStdDev()函数用于计算一个矩阵的均值和标准差,它的声明如下:

void cv::meanStdDev (
    InputArray  src, 
    OutputArray     mean,
    OutputArray     stddev,
    InputArray  mask = noArray() 
)   

函数参数

  • src:输入的源图像或矩阵
  • mean:输出的均值矩阵
  • stddev:输出的标准差矩阵
  • mask:可选的掩码矩阵

使用meanStdDev计算均值和标准差的代码如下:

// 输入图像的路径
// 计算图像的标准差
void cal_mean_stddev(string path) {
    Mat src = imread(path);
    Mat gray, mat_mean, mat_stddev;
    cvtColor(src, gray, CV_RGB2GRAY); // 转换为灰度图
    meanStdDev(gray, mat_mean, mat_stddev);
    double m, s;
    m = mat_mean.at<double>(0, 0);
    s = mat_stddev.at<double>(0, 0);
    cout << path << "的灰度均值是:" << m << endl;
    cout << path << "的标准差是:" << s << endl;
}

标准差的计算公式如下:

这里写图片描述

其中, M * N表示图像的大小, P(i, j) 表示第i行、第j列的像素值, u表示均值。


2、计算图像的平均梯度

平均梯度的计算公式如下:

这里写图片描述

下面的代码参考了:http://blog.csdn.net/windydreams/article/details/22691349

// 输入图像的路径
// 计算图像的平均梯度
void cal_mean_gradient(string path) {
    Mat src = imread(path);
    Mat img;
    cvtColor(src, img, CV_RGB2GRAY); // 转换为灰度图
    img.convertTo(img, CV_64FC1);
    double tmp = 0;
    int rows = img.rows - 1;
    int cols = img.cols - 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            double dx = img.at<double>(i, j + 1) - img.at<double>(i, j);
            double dy = img.at<double>(i + 1, j) - img.at<double>(i, j);
            double ds = std::sqrt((dx*dx + dy*dy) / 2);
            tmp += ds;
        }
    }
    double imageAvG = tmp / (rows*cols);
    cout << path << "的平均梯度是:" << imageAvG << endl;
}

3、测试图像为:

images/image7.jpg
这里写图片描述

测试结果如下:
这里写图片描述

完整的测试程序如下:


/*************************************************

Copyright:bupt
Author: lijialin 1040591521@qq.com
Date:2017-11-21
Description:写这段代码的时候,只有上帝和我知道它是干嘛的
            现在只有上帝知道

**************************************************/

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;


/***************** 函数声明部分 start ****************/

void cal_mean_stddev(string path); // 计算图像的标准差
void cal_mean_gradient(string path); // 计算图像的平均梯度

/***************** 函数声明部分 end ****************/


int main() {

    string path = "images/image7.jpg"; // image7.jpg在images目录下
    cal_mean_stddev(path);
    cal_mean_gradient(path);

    system("pause"); // 暂停
    return 0;
}

// 计算图像的标准差
void cal_mean_stddev(string path) {
    Mat src = imread(path);
    Mat gray, mat_mean, mat_stddev;
    cvtColor(src, gray, CV_RGB2GRAY); // 转换为灰度图
    meanStdDev(gray, mat_mean, mat_stddev);
    double m, s;
    m = mat_mean.at<double>(0, 0);
    s = mat_stddev.at<double>(0, 0);
    cout << path << "的灰度均值是:" << m << endl;
    cout << path << "的标准差是:" << s << endl;

}


// 计算图像的平均梯度
void cal_mean_gradient(string path) {
    Mat src = imread(path);
    Mat img;
    cvtColor(src, img, CV_RGB2GRAY); // 转换为灰度图
    img.convertTo(img, CV_64FC1);
    double tmp = 0;
    int rows = img.rows - 1;
    int cols = img.cols - 1;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            double dx = img.at<double>(i, j + 1) - img.at<double>(i, j);
            double dy = img.at<double>(i + 1, j) - img.at<double>(i, j);
            double ds = std::sqrt((dx*dx + dy*dy) / 2);
            tmp += ds;
        }
    }
    double imageAvG = tmp / (rows*cols);
    cout << path << "的平均梯度是:" << imageAvG << endl;
}

作者:u010429424 发表于2017/11/21 14:57:07 原文链接
阅读:0 评论:0 查看评论

leetcode-167. Two Sum II - Input array is sorted

$
0
0

167. Two Sum II - Input array is sorted

Given an array of integers that is already sorted in ascending order, find two numbers such that they add up to a specific target number.

The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2. Please note that your returned answers (both index1 and index2) are not zero-based.

You may assume that each input would have exactly one solution and you may not use the same element twice.

Input: numbers={2, 7, 11, 15}, target=9
Output: index1=1, index2=2


题解:

求 排序后的数组中 和为 target 的元素的下标,要求以数组返回,第一个下标小于第二个下标,且下标从 1 开始

保证数组中存在满足条件的两个数,且一个数字不会用两遍。


思路:

之后要遍历数组找到这两个数即可,一个从0开始,一个从最后开始。


solution:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
            int left = 0;
        	int right = numbers.length - 1;
        	int[] solu = new int[2];

        	while(true){
        		if(target - numbers[left] == numbers[right]) {
        				solu[0] = left + 1;
        				solu[1] = right + 1;
        				return solu;
        		}
        		else if (target - numbers[left] < numbers[right]) {
        			right--;
        		}
        		else left++;
        	}
        	//return solu;
        
    }
}




作者:qq_32832023 发表于2017/11/21 15:35:36 原文链接
阅读:23 评论:0 查看评论

Java操作Mysql

$
0
0

代码:


import java.sql.*;

/**
 * @author yangxin-ryan
 * @date 2017-11-21
 */
public class MysqlJdbcClient {

    public static void main(String[] args){
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://ip:3306/mysql";
        Connection conn = null;
        Statement stmt = null;
        try {
            Class.forName(driver);
            System.out.println("load mysql driver successful!");
        }catch (ClassNotFoundException error){
            System.out.println("Can't find Mysql driver!");
            error.printStackTrace();
        }
        try{
            conn = DriverManager.getConnection(url, "user", "password");
            stmt = conn.createStatement();
            System.out.println("Connection mysql successful!");
            String sql = "show databases";
            ResultSet rs = stmt.executeQuery(sql);
            while (rs.next()){
                System.out.println("--------------------");
                System.out.println("| " + rs.getString(1) + " |");
                System.out.println("--------------------");
            }
        }catch (SQLException error){
            error.printStackTrace();
        }finally{
            try{
                stmt.close();
                conn.close();
            }catch (SQLException closeError){
                closeError.printStackTrace();
            }
        }

    }
}


pom.xml


<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.hive/hive-metastore -->
    <dependency>
        <groupId>org.apache.hive</groupId>
        <artifactId>hive-metastore</artifactId>
        <version>2.3.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.hive/hive-jdbc -->
    <dependency>
        <groupId>org.apache.hive</groupId>
        <artifactId>hive-jdbc</artifactId>
        <version>2.3.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>6.0.6</version>
    </dependency>
</dependencies>

结果:



作者:u012965373 发表于2017/11/21 15:35:56 原文链接
阅读:23 评论:0 查看评论

Kafka中的消息是否会丢失和重复消费

$
0
0

        在之前的基础上,基本搞清楚了Kafka的机制及如何运用。这里思考一下:Kafka中的消息会不会丢失或重复消费呢?为什么呢?

        要确定Kafka的消息是否丢失或重复,从两个方面分析入手:消息发送和消息消费

        1、消息发送

         Kafka消息发送有两种方式:同步(sync)和异步(async),默认是同步方式,可通过producer.type属性进行配置。Kafka通过配置request.required.acks属性来确认消息的生产:

0---表示不进行消息接收是否成功的确认;

1---表示当Leader接收成功时确认;

-1---表示Leader和Follower都接收成功时确认;

综上所述,有6种消息生产的情况,下面分情况来分析消息丢失的场景:

(1)acks=0,不和Kafka集群进行消息接收确认,则当网络异常、缓冲区满了等情况时,消息可能丢失;

(2)acks=1、同步模式下,只有Leader确认接收成功后但挂掉了,副本没有同步,数据可能丢失;

        2、消息消费

        Kafka消息消费有两个consumer接口,Low-level API和High-level API:

Low-level API:消费者自己维护offset等值,可以实现对Kafka的完全控制;

High-level API:封装了对parition和offset的管理,使用简单;

如果使用高级接口High-level API,可能存在一个问题就是当消息消费者从集群中把消息取出来、并提交了新的消息offset值后,还没来得及消费就挂掉了,那么下次再消费时之前没消费成功的消息就“诡异”的消失了;    

        解决办法:

        针对消息丢失:同步模式下,确认机制设置为-1,即让消息写入Leader和Follower之后再确认消息发送成功;异步模式下,为防止缓冲区满,可以在配置文件设置不限制阻塞超时时间,当缓冲区满时让生产者一直处于阻塞状态;

        针对消息重复:将消息的唯一标识保存到外部介质中,每次消费时判断是否处理过即可。


        

        Kafka的Leader选举机制

        Kafka将每个Topic进行分区Patition,以提高消息的并行处理,同时为保证高可用性,每个分区都有一定数量的副本 Replica,这样当部分服务器不可用时副本所在服务器就可以接替上来,保证系统可用性。在Leader上负责读写,Follower负责数据的同步。当一个Leader发生故障如何从Follower中选择新Leader呢?

        Kafka在Zookeeper上针对每个Topic都维护了一个ISR(in-sync replica---已同步的副本)的集合,集合的增减Kafka都会更新该记录。如果某分区的Leader不可用,Kafka就从ISR集合中选择一个副本作为新的Leader。这样就可以容忍的失败数比较高,假如某Topic有N+1个副本,则可以容忍N个服务器不可用。

        如果ISR中副本都不可用,有两种处理方法:(1)等待ISR集合中副本复活后选择一个可用的副本;(2)选择集群中其他可用副本;

作者:u012050154 发表于2017/11/21 16:10:26 原文链接
阅读:15 评论:0 查看评论

数据结构与算法分析(Java语言描述)(31)—— 使用 Prim 算法求有权图的最小生成树(MST)

$
0
0

最小生成树

这里写图片描述

这里写图片描述


切分定理


这里写图片描述

这里写图片描述


LazyPrim

这里写图片描述

package com.dataStructure.weight_graph;

import com.dataStructure.heap.MinHeap;

import java.util.ArrayList;
import java.util.List;

public class LazyPrimMST{
    private WeightedGraph graph;    // 存放输入的图
    private boolean[] visited;  // 图中节点的访问状态
    private List<Edge> mst; // 最小生成树经过的边
    private Number mstWeight;   // 最小生成树的权重
    private MinHeap<Edge> minHeap;  // 用于缓存 edge 的最小堆

    // 构造函数,初始化私有字段
    public LazyPrimMST(WeightedGraph graph){
        this.graph = graph;
        visited = new boolean[graph.V()];
        mst = new ArrayList<>();
        mstWeight = 0;
        minHeap = new MinHeap<>(graph.E()); // 最小堆的大小等于边数

        visit(0);   // 从节点 0 开始访问图
        while (!minHeap.isEmpty()){ // 当最小堆非空
            Edge edge = minHeap.extractMin();   // 获取最小边
            if (visited[edge.getV()] == visited[edge.getW()])   // 如果最小边的两个端点都被访问
                continue;   // 跳出本次循环

            mst.add(edge);  // 将最小边放入 mst 中
            if (!visited[edge.getV()])  // 访问最小边两端点中未被访问的端点
                visit(edge.getV());
            else
                visit(edge.getW());
        }

        for (Edge edge:mst) // 计算最小生成树的权重
            mstWeight = mstWeight.doubleValue() + edge.getWeight().doubleValue();

    }

    public void visit(int i){   // 访问节点 i
        visited[i] = true;
        for (Edge edge:graph.adjacentNode(i))   // 将 i 的邻接节点(未被访问的)与 i 之间的边加入最小堆中
            if (!visited[edge.getAnotherNode(i)])
                minHeap.insert(edge);
    }

    public Number getMstWeight(){   // 返回最小生成树的权重
        return mstWeight;
    }

    public List<Edge> getMst() {    // 返回最小生成树
        return mst;
    }
}



//public class LazyPrimMST {
//    private WeightedGraph G;    // 图的引用
//    private MinHeap<Edge> pq;   // 最小堆, 算法辅助数据结构
//    private boolean[] visited;           // 标记数组, 在算法运行过程中标记节点i是否被访问
//    private List<Edge> mst;   // 最小生成树所包含的所有边
//    private Number mstWeight;           // 最小生成树的权值
//
//    // 构造函数, 使用Prim算法求图的最小生成树
//    public LazyPrimMST(WeightedGraph graph) {
//
//        // 算法初始化
//        G = graph;
//        pq = new MinHeap<>(G.E());  // 堆的大小等于 g 的边数
//        visited = new boolean[G.V()];   // 节点的访问状态
//        mst = new ArrayList<>();
//
//        // Lazy Prim
//        visit(0);
//        while (!pq.isEmpty()) {
//            // 使用最小堆找出已经访问的边中权值最小的边
//            Edge e = pq.extractMin();
//            // 如果这条边的两端都已经访问过了, 则扔掉这条边
//            if (visited[e.getV()] == visited[e.getW()])
//                continue;
//            // 否则, 这条边则应该存在在最小生成树中
//            mst.add(e);
//
//            // 访问和这条边连接的还没有被访问过的节点
//            if (!visited[e.getV()])
//                visit(e.getV());
//            else
//                visit(e.getW());
//        }
//
//        // 计算最小生成树的权值
//
////        mstWeight = mst.get(0).getWeight();
////        for (int i = 1; i < mst.size(); i++)
////            mstWeight = mstWeight.doubleValue() + mst.get(i).getWeight().doubleValue();
//
//        mstWeight = 0;
//        for (Edge edge:mst)
//            mstWeight = mstWeight.doubleValue() + edge.getWeight().doubleValue();
//
//    }
//
//    // 访问节点v
//    private void visit(int v) {
//
//        assert !visited[v];
//        visited[v] = true;
//
//        // 将和节点v相连接的所有未访问的边放入最小堆中
//        for (Edge e : G.adjacentNode(v))
//            if (!visited[e.getAnotherNode(v)])
//                pq.insert(e);
//    }
//
//    // 返回最小生成树的所有边
//    List<Edge> mstEdges() {
//        return mst;
//    }
//
//    ;
//
//    // 返回最小生成树的权值
//    Number result() {
//        return mstWeight;
//    }
//
//}

Prim

package com.dataStructure.weight_graph;

import com.dataStructure.heap.IndexMinHeap;

import java.util.ArrayList;
import java.util.List;

public class PrimMST {
    private WeightedGraph graph;    // 存放输入的图
    private boolean[] visited;  // 节点的访问状态
    private IndexMinHeap indexMinHeap;  // 最小索引堆
    private Edge[] edgeTo;  // edgeTo[i] 表示到 i 节点的 Edge
    private List<Edge> mst; // 组成最小生成树的边
    private Number mstWeight;   // 最小生成树的权重

    // 构造函数
    public PrimMST(WeightedGraph graph) {
        // 私有字段初始化
        this.graph = graph;
        visited = new boolean[graph.V()];
        indexMinHeap = new IndexMinHeap(graph.V());
        edgeTo = new Edge[graph.V()];
        mst = new ArrayList<>();
        mstWeight = 0;
        for (int i = 0; i < graph.V(); i++) {
//            visited[i] = false;
            edgeTo[i] = null;
        }

        // Prim
        visit(0);   // 访问节点 0
        while (!indexMinHeap.isEmpty()) {    // 最小索引堆不为空
            int i = indexMinHeap.extractMinIndex(); // 获取当前堆中最小权重对应的节点
            mst.add(edgeTo[i]); // 将 edgeTo[i] 加入 mst 中
            visit(i);   // 访问节点 i
        }

        for (Edge edge : mst) // 计算最小生成树的权重
            mstWeight = mstWeight.doubleValue() + edge.getWeight().doubleValue();
    }

    public void visit(int i) {
        visited[i] = true;  // 节点的访问状态置为 true
        for (Edge edge : graph.adjacentNode(i)) { // 遍历 i 的邻接节点
            int w = edge.getAnotherNode(i);
            if (!visited[w]) {   // 对于未被访问的邻接节点 w
                if (edgeTo[w] == null) { // 如果尚未考虑过 w
                    edgeTo[w] = edge;   // 设置到 w 的边为 edge
                    indexMinHeap.insert(w, (Comparable) edge.getWeight());  // 插入到最小索引堆中
                } else if (edge.compareTo(edgeTo[w]) < 0) {   // 如果考虑过 w
                    // 且 edge 比 edgeTo[w] 的权重更小
                    edgeTo[w] = edge;   // 更新到 w 的边为 edge
                    indexMinHeap.change(w, (Comparable) edge.getWeight());  // 更新最小索引堆中 w 对应值
                }
            }
        }
    }

    public List<Edge> getMst() {
        return mst;
    }

    public Number getMstWeight() {
        return mstWeight;
    }

    // 测试 Prim
    public static void main(String[] args) {

        String filename = "/testG1.txt";
        int V = 8;

        SparseGraph g = new SparseGraph(V, false);
        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);

        // Test Prim MST
        System.out.println("Test Prim MST:");
        PrimMST primMST = new PrimMST(g);
        List<Edge> mst = primMST.getMst();
        for (int i = 0; i < mst.size(); i++)
            System.out.println(mst.get(i));
        System.out.println("The MST weight is: " + primMST.getMstWeight());

        System.out.println();
    }
}


//public class PrimMST {
//    private WeightedGraph G;              // 图的引用
//    private IndexMinHeap ipq;     // 最小索引堆, 算法辅助数据结构
//    private Edge[] edgeTo;        // 访问的点所对应的边, 算法辅助数据结构
//    private boolean[] marked;             // 标记数组, 在算法运行过程中标记节点i是否被访问
//    private List<Edge> mst;     // 最小生成树所包含的所有边
//    private Number mstWeight;             // 最小生成树的权值
//
//    // 构造函数, 使用Prim算法求图的最小生成树
//    public PrimMST(WeightedGraph graph) {
//
//        G = graph;
//        assert (graph.E() >= 1);
//        ipq = new IndexMinHeap(graph.V());
//
//        // 算法初始化
//        marked = new boolean[G.V()];
//        edgeTo = new Edge[G.V()];
//        for (int i = 0; i < G.V(); i++) {
//            marked[i] = false;
//            edgeTo[i] = null;
//        }
//        mst = new ArrayList<>();
//
//        // Prim
//        visit(0);
//        while (!ipq.isEmpty()) {
//            // 使用最小索引堆找出已经访问的边中权值最小的边
//            // 最小索引堆中存储的是点的索引, 通过点的索引找到相对应的边
//            int v = ipq.extractMinIndex();
//            assert (edgeTo[v] != null);
//            mst.add(edgeTo[v]);
//            visit(v);
//        }
//
//        // 计算最小生成树的权值
//        mstWeight = mst.get(0).getWeight();
//        for (int i = 1; i < mst.size(); i++)
//            mstWeight = mstWeight.doubleValue() + mst.get(i).getWeight().doubleValue();
//    }
//
//    // 访问节点v
//    void visit(int v) {
//
//        assert !marked[v];
//        marked[v] = true;
//
//        // 将和节点v相连接的未访问的另一端点, 和与之相连接的边, 放入最小堆中
//        for (Edge edge : G.adjacentNode(v)) {
//            int w = edge.getAnotherNode(v);
//            // 如果边的另一端点未被访问
//            if (!marked[w]) {
//                // 如果从没有考虑过这个端点, 直接将这个端点和与之相连接的边加入索引堆
//                if (edgeTo[w] == null) {
//                    edgeTo[w] = edge;
//                    ipq.insert(w, (Comparable)edge.getWeight());
//                }
//                // 如果曾经考虑这个端点, 但现在的边比之前考虑的边更短, 则进行替换
//                else if (edge.compareTo(edgeTo[w]) < 0) {
//                    edgeTo[w] = edge;
//                    ipq.change(w, (Comparable)edge.getWeight());
//                }
//            }
//        }
//
//    }
//
//    // 返回最小生成树的所有边
//    List<Edge> mstEdges() {
//        return mst;
//    }
//
//    // 返回最小生成树的权值
//    Number result() {
//        return mstWeight;
//    }
//
//
//    // 测试 Prim
//    public static void main(String[] args) {
//
//        String filename = "/testG1.txt";
//        int V = 8;
//
//        SparseGraph g = new SparseGraph(V, false);
//        ReadWeightedGraph readGraph = new ReadWeightedGraph(g, filename);
//
//        // Test Prim MST
//        System.out.println("Test Prim MST:");
//        PrimMST primMST = new PrimMST(g);
//        List<Edge> mst = primMST.mstEdges();
//        for (int i = 0; i < mst.size(); i++)
//            System.out.println(mst.get(i));
//        System.out.println("The MST weight is: " + primMST.result());
//
//        System.out.println();
//    }
//
//}

作者:HeatDeath 发表于2017/11/21 16:36:27 原文链接
阅读:7 评论:0 查看评论

Wdf框架中WdfDriverGlobals对象的创建

$
0
0

    前面写过一篇<WDF基本对象和句柄定义>,反响一般,不过这不会成为阻挡我继续写下去的绊脚石~本篇我们继续来分析Wdf框架。

    WdfDriverGlobals对象的身影活跃在wdf框架的各个角落,几乎每个DDI接口内部都会使用它:

_Must_inspect_result_
__drv_maxIRQL(PASSIVE_LEVEL)
NTSTATUS
WDFEXPORT(WdfDriverCreate)(
    PWDF_DRIVER_GLOBALS DriverGlobals,
    MdDriverObject DriverObject,
    PCUNICODE_STRING RegistryPath,
    PWDF_OBJECT_ATTRIBUTES DriverAttributes,
    PWDF_DRIVER_CONFIG DriverConfig,
    WDFDRIVER* Driver
    )
{
   ...
    pFxDriverGlobals = GetFxDriverGlobals(DriverGlobals);
    ...
}
不得不说,WdfDriverGlobals有着举足轻重的作用。在好奇心的驱使下,我查找了该对象的分配和创建过程:

NTSTATUS
FxLibraryCommonRegisterClient(
    __inout PWDF_BIND_INFO        Info,
    __deref_out PWDF_DRIVER_GLOBALS *WdfDriverGlobals,
    __in_opt PCLIENT_INFO          ClientInfo
    )
{
    NTSTATUS           status;
    UNICODE_STRING serviceName = { 0 };

    ASSERT(Info->FuncCount);
    *WdfDriverGlobals = NULL;
    ...
    *WdfDriverGlobals = FxAllocateDriverGlobals();

    if (*WdfDriverGlobals) { ...
    虽然SourceInsight中可以找到大量对WdfDriverGlobals的引用,但唯有这处比较特别:FxLibraryCommonRegisterClient函数的第二个参数是一个二级指针类型。这涉及到每个C语言入门者都会遇到的难题:如何在函数内部为指针分配内存并回传给调用者,答案之一是通过二级指针实现。因此,我比较确定这个函数的作用之一就是调用FxAllocateDriverGlobals函数为WdfDriverGlobals分配内存并初始化。

PWDF_DRIVER_GLOBALS
FxAllocateDriverGlobals(
    VOID
    )
{
    PFX_DRIVER_GLOBALS  pFxDriverGlobals;
    KIRQL               irql;
    NTSTATUS            status;
    //MxAllocatePoolWithTag内部调用ExAllocatePoolWithTag,分配非分页内存
    pFxDriverGlobals = (PFX_DRIVER_GLOBALS)
        MxMemory::MxAllocatePoolWithTag(NonPagedPool, sizeof(FX_DRIVER_GLOBALS), FX_TAG);

    if (pFxDriverGlobals == NULL) {
        return NULL;
    }
    //准备初始化
    RtlZeroMemory(pFxDriverGlobals, sizeof(FX_DRIVER_GLOBALS));
    ...
    return &pFxDriverGlobals->Public; //<----敲黑板 划重点 请各位注意函数的返回值
}

    FxAllocateDriverGlobals函数本身不是很复杂,除了函数返回值,其他并不值得用大篇段落介绍。请注意代码中pFxDriverGlobals的类型为PFX_DRIVER_GLOBALS,而函数的返回类型为PWDF_DRIVER_GLOBALS。WDF_DRIVER_GLOBALS作为FX_DRIVER_GLOBALS结构的最后一部分,保存在其Public域内:

typedef struct _FX_DRIVER_GLOBALS {
public:
    ULONG
    __inline
    AddRef(
        __in_opt   PVOID Tag = NULL,
        __in       LONG Line = 0,
        __in_opt   PSTR File = NULL
        );
...
    PFX_TELEMETRY_CONTEXT TelemetryContext;

    DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) WDF_DRIVER_GLOBALS  Public;

} FX_DRIVER_GLOBALS, *PFX_DRIVER_GLOBALS;
    我们可能以WDF_DRIVER_GLOBALS*作为参数调用DDI接口,而DDI接口内部会通过GetFxDriverGlobals宏(确切的说是CONTAINING_RECORD宏),根据DriverGlobals的地址以及DriverGlobals对应的Public域在结构中的偏移计算出调用FxAllocateDriverGlobals时分配的对象的起始地址。不过,真不知道MS为什么要大费周章的这么折腾...

__inline
PFX_DRIVER_GLOBALS
GetFxDriverGlobals(
    __in PWDF_DRIVER_GLOBALS DriverGlobals
    )
{
    return CONTAINING_RECORD( DriverGlobals, FX_DRIVER_GLOBALS, Public );
}

    

    前面说了函数FxLibraryCommonRegisterClient调用FxAllocateDriverGlobals分配内存,但纵览整份WDF框架的源码都没有发现哪里调用FxLibraryCommonRegisterClient这个函数。Wdf框架总不可能一直在传递空的DriverGlobals对象,要不然OS早崩溃了。所以,我初步断定,很可能MS并没有把调用FxLibraryCommonRegisterClient函数的代码开源出来,原因不详。虽然MS不开源,但是根据调用栈来反推调用函数得源码应该不难。这就得靠windbg动态调试来做到了:

kd> x Wdf01000!FxLibraryCommonRegisterClient ;查找符号
fffff80e`55b25a64 Wdf01000!FxLibraryCommonRegisterClient (struct _WDF_BIND_INFO *, struct _WDF_DRIVER_GLOBALS **, struct _CLIENT_INFO *)
kd> bu Wdf01000!FxLibraryCommonRegisterClient ;系统启动时,Wdf01000.sys还没加载,所以要下延迟断点
kd> g
Breakpoint 0 hit
Wdf01000!FxLibraryCommonRegisterClient:
fffff80e`55b25a64 4c8bdc          mov     r11,rsp
kd> kb 
RetAddr           : Args to Child                                                           : Call Site
fffff80e`55b25a26 : 00000010`00000b32 ffffbe0a`67727453 00000000`00000002 fffff803`f6ed529d : Wdf01000!FxLibraryCommonRegisterClient [minkernel\wdf\framework\kmdf\src\librarycommon\fxlibrarycommon.cpp @ 344]
fffff80e`55c0d3a5 : 00000000`00000000 fffff80e`57704148 fffff80e`57704160 fffff803`f6a21832 : Wdf01000!LibraryRegisterClient+0x56 [minkernel\wdf\framework\kmdf\src\dynamic\version\version.cpp @ 517]
fffff80e`57702634 : ffffe201`0425e230 00000000`00000000 00000000`00000078 00000000`00000000 : WDFLDR!WdfVersionBind+0xd5 [minkernel\wdf\framework\kmdf\src\dynamic\loader\wdfldr.cpp @ 1954]
fffff803`f6e6257a : 00000000`00000000 ffffbe0a`add31440 ffffae09`6c7f6e60 ffffffff`800000f0 : CompositeBus!FxDriverEntryWorker+0x74
fffff803`f6e64c8b : 00000000`00000000 00000000`00000000 00000000`00000004 ffffe201`00000004 : nt!IopLoadDriver+0x4da
fffff803`f6e652a8 : ffffffff`80000001 ffffbe0a`add316d0 ffffffff`800000ec 00000000`00000000 : nt!PipCallDriverAddDeviceQueryRoutine+0x1b3
fffff803`f6e68009 : 00000000`00000000 ffffbe0a`add316b0 00000000`6e657050 00000000`0000001a : nt!PnpCallDriverQueryServiceHelper+0xcc
fffff803`f6e718b8 : ffffae09`6ada0d20 ffffbe0a`add318f0 ffffae09`6ada0d20 ffffae09`6ad9fd20 : nt!PipCallDriverAddDevice+0x385
fffff803`f6fdd495 : ffffae09`6c775b30 ffffbe0a`add31b19 ffffae09`6c775b30 ffffae09`6c775b80 : nt!PipProcessDevNodeTree+0x164
fffff803`f6b17f94 : ffffae01`00000003 ffffae09`00000000 ffffae09`00000000 00000000`00000000 : nt!PiProcessStartSystemDevices+0x59
fffff803`f6a5a645 : ffffae09`6acde700 ffffae09`6ac7b6e0 fffff803`f6d7a960 ffffae09`0000000c : nt!PnpDeviceActionWorker+0x474
fffff803`f6ae33a7 : 00000000`00000000 00000000`00000080 ffffae09`6ac84480 ffffae09`6acde700 : nt!ExpWorkerThread+0xf5
fffff803`f6b68d66 : fffff803`f5e90180 ffffae09`6acde700 fffff803`f6ae3360 00000000`00000000 : nt!PspSystemThreadStartup+0x47
00000000`00000000 : ffffbe0a`add32000 ffffbe0a`add2c000 00000000`00000000 00000000`00000000 : nt!KiStartSystemThread+0x16
最底部的nt!KiStartSystemThread显示当前系统还在启动阶段,在此阶段中,Wdfldr.sys!WdfVersionBind调用函数FxLibraryCommonRegisterClient,并最终分配全局对象WdfDriverGlobals。正是这WdfLdr的代码,MS没有公开。好在有pdb文件,可以用IDA逆向分析。加载WdfLdr.sys并设置pdb路径后,F5(Generate pseudocode),可以得到如下伪代码:

__int64 __fastcall WdfVersionBind(_DRIVER_OBJECT *DriverObject, _UNICODE_STRING *RegistryPath, _WDF_BIND_INFO *Info, void ***Globals)
{
  void ***v4; // rsi@1
  _WDF_BIND_INFO *v5; // rdi@1
  _UNICODE_STRING *v6; // rbp@1
  int (__cdecl *v7)(_WDF_BIND_INFO *, void ***, void **); // rax@3
  _CLIENT_MODULE *v8; // rcx@3
  __int64 result; // rax@3
  _LIBRARY_MODULE *Module; // [sp+30h] [bp-38h]@1
  _CLIENT_MODULE *v11; // [sp+38h] [bp-30h]@3
  int v12; // [sp+40h] [bp-28h]@1
  _UNICODE_STRING *v13; // [sp+48h] [bp-20h]@1

  v12 = 16;
  v4 = Globals;
  Module = 0i64;
  v5 = Info;
  v13 = 0i64;
  v6 = RegistryPath;
  if ( WdfLdrDiags & 1 )
  {
    DbgPrint("WdfLdr: WdfVersionBind - ");
    DbgPrint("WdfLdr: WdfVersionBind: enter\n");
  }
  v11 = 0i64;
  JUMPOUT(v4, 0i64, sub_1C000D920);
  JUMPOUT(v5, 0i64, sub_1C000D920);
  JUMPOUT(v5->FuncTable, 0i64, sub_1C000D920);
  JUMPOUT(ReferenceVersion(v5, &Module), 0, sub_1C000D8DC);
  JUMPOUT(LibraryLinkInClient(v5->Module, v6, v5, 0i64, &v11), 0, sub_1C000D87C);
  v13 = v6;
  v7 = Module->LibraryInfo->LibraryRegisterClient;
  LODWORD(result) = _guard_dispatch_icall_fptr(v5, v4);
  JUMPOUT(result, 0, sub_1C000D8AC);
  v8 = v11;
  v11->Globals = *v4;
  v8->Context = &v12;
  JUMPOUT(result, 0, &loc_1C000D8E1);
  JUMPOUT(WdfLdrDiags & 1, 0, sub_1C000D8FE);
  return (unsigned int)result;
}
    伪代码行中
v7 = Module->LibraryInfo->LibraryRegisterClient;
LibraryRegisterClient应该就是我们的FxLibraryCommonRegisterClient函数,如果是这样的话,Module可能包含了Wdf01000.sys的信息。让我么来验证一下:

根据上面WdfVersionBind的伪代码,大概可以猜到Wdf01000.sys的模块信息存放在变量_LIBRARY_MODULE *Module;中。所以,我们可以在调用LibraryLinkInClient时,查看Module信息(fastcall,参数Library存放在rcx中)。

IDA分析得出的LibraryLinkInClient函数原型:

__int64 __fastcall LibraryLinkInClient(_LIBRARY_MODULE *Library, _UNICODE_STRING *RegistryPath, _WDF_BIND_INFO *Info, void *Context, _CLIENT_MODULE **Client)
    接下来,一起验证一下IDA伪代码中的v7 = Module->LibraryInfo->LibraryRegisterClient;调用的的确是Wdf01000.sys中的FxLibraryCommonRegisterClient

kd> bp WDFLDR!LibraryLinkInClient
kd> g
Breakpoint 2 hit
WDFLDR!LibraryLinkInClient:
fffff80e`55c024a0 48895c2408      mov     qword ptr [rsp+8],rbx

kd> dt WDFLDR!_LIBRARY_MODULE @rcx ;查看LibraryLinkInClient的第一个参数。参数类型为LIBRARY_MODULE,保存在rcx中
   +0x000 LibraryRefCount  : 0n1
   +0x008 LibraryListEntry : _LIST_ENTRY [ 0xfffff80e`55c0a238 - 0xfffff80e`55c0a238 ]
   +0x018 ClientRefCount   : 0n10
   +0x01c IsBootDriver     : 0x1 ''
   +0x01d ImplicitlyLoaded : 0x1 ''
   +0x020 Service          : _UNICODE_STRING "\Registry\Machine\System\CurrentControlSet\Services\Wdf01000"
   +0x030 ImageName        : _UNICODE_STRING "Wdf01000.sys" ;模块信息为Wdf01000.sys
   +0x040 ImageAddress     : 0xfffff80e`55b10000 Void
   +0x048 ImageSize        : 0xe3000
   +0x050 LibraryFileObject : 0xffffae09`6ac7a210 _FILE_OBJECT
   +0x058 LibraryDriverObject : 0xffffae09`6b9b4b20 _DRIVER_OBJECT
   +0x060 LibraryInfo      : 0xfffff80e`55bcf140 _WDF_LIBRARY_INFO ;Wdf01000.sys设置的回调函数的地址
   +0x068 ClientsListHead  : _LIST_ENTRY [ 0xffffae09`6c767110 - 0xffffae09`6af091b0 ]
   +0x078 ClientsListLock  : _ERESOURCE
   +0x0e0 Version          : _WDF_VERSION
   +0x0f0 LoaderEvent      : _KEVENT
   +0x108 LoaderThread     : (null) 
   +0x110 ClassListHead    : _LIST_ENTRY [ 0xffffae09`6ba1bd08 - 0xffffae09`6ba1bd08 ]
;显示Wdf01000.sys设置的回调函数
kd> dt _WDF_LIBRARY_INFO 0xfffff80e`55bcf140 
Wdf01000!_WDF_LIBRARY_INFO
   +0x000 Size             : 0x38
   +0x008 LibraryCommission : 0xfffff80e`55b46870     long  Wdf01000!LibraryCommission+0
   +0x010 LibraryDecommission : 0xfffff80e`55b759c0     long  Wdf01000!LibraryDecommission+0
   +0x018 LibraryRegisterClient : 0xfffff80e`55b259d0     long  Wdf01000!LibraryRegisterClient+0 
   ;这是我们上文分析的创建WdfDriverGlobals的函数入口
   +0x020 LibraryUnregisterClient : 0xfffff80e`55b759e0     long  Wdf01000!LibraryUnregisterClient+0
   +0x028 Version          : _WDF_VERSION

kd> .srcfix C:\srcfix\Windows-Driver-Frameworks-master\src\framework ;设置Wdf框架源码路径
Source search path is: SRV*;C:\srcfix\Windows-Driver-Frameworks-master\src\framework
kd> lsa Wdf01000!LibraryRegisterClient ;显示源码,WDFLDR!_LIBRARY_MODULE->_WDF_LIBRARY_INFO->LibraryRegisterClient指向Wdf01000!LibraryRegisterClient
   458:     clientInfo = (PCLIENT_INFO)*Context;
   459:     *Context = NULL;
   460: 
   461:     ASSERT(Info->Version.Major == WdfLibraryInfo.Version.Major);
>  462: 
    windbg显示的信息和前面我的猜测有点出路,看来要结合SourceInsight看看是不是遗漏什么。原来Wdf01000.sys在DriverEntry中通过WdfLdr!WdfRegisterLibrary注册了WdfLibraryInfo结构:

extern "C" {
#pragma prefast(suppress:__WARNING_ENCODE_MEMBER_FUNCTION_POINTER, "kernel component.");
WDF_LIBRARY_INFO  WdfLibraryInfo = {
    sizeof(WDF_LIBRARY_INFO),
    (PFNLIBRARYCOMMISSION)        WDF_LIBRARY_COMMISSION,
    (PFNLIBRARYDECOMMISSION)      WDF_LIBRARY_DECOMMISSION,
    (PFNLIBRARYREGISTERCLIENT)    WDF_LIBRARY_REGISTER_CLIENT,
    (PFNLIBRARYUNREGISTERCLIENT)  WDF_LIBRARY_UNREGISTER_CLIENT,
    { __WDF_MAJOR_VERSION, __WDF_MINOR_VERSION, __WDF_BUILD_NUMBER }
};
这个结构体变量初始化看着有点怪怪的,各个成员更像是一段宏,而不是某个函数地址。那就根据这些宏的名字,按图索骥,找到定义他们的地方;

#define WDF_LIBRARY_COMMISSION          LibraryCommission
#define WDF_LIBRARY_DECOMMISSION        LibraryDecommission
#define WDF_LIBRARY_REGISTER_CLIENT     LibraryRegisterClient
#define WDF_LIBRARY_UNREGISTER_CLIENT   LibraryUnregisterClient
恩,我们顺利找到了宏WDF_LIBRARY_REGISTER_CLIENT对应的函数名:LibraryRegisterClient,而这个函数名就是前面windbg lsa命令显示的源码,由它调用了FxLibraryCommonRegisterClient去分配和初始化WdfDriverGlobals对象,供以后其他DDI使用:

extern "C"
_Must_inspect_result_
NTSTATUS
WDF_LIBRARY_REGISTER_CLIENT(
    __in  PWDF_BIND_INFO        Info,
    __deref_out   PWDF_DRIVER_GLOBALS * WdfDriverGlobals,
    __deref_inout PVOID             * Context
    )
{
    NTSTATUS           status = STATUS_INVALID_PARAMETER;
    PFX_DRIVER_GLOBALS pFxDriverGlobals;
    WCHAR              insertString[EVTLOG_MESSAGE_SIZE];
    ULONG              rawData[RAW_DATA_SIZE];
    PCLIENT_INFO       clientInfo = NULL;
    ...
    status = FxLibraryCommonRegisterClient(Info,
                                           WdfDriverGlobals,
                                           clientInfo);




作者:lixiangminghate 发表于2017/11/21 16:41:52 原文链接
阅读:15 评论:0 查看评论

TensorFlow入门(八)tensorboard 的一个简单示例

$
0
0

关于 tensorboard 的一点心得

  • 1.一定要学会使用 tf.variable_scope() 和 tf.name_scope(),否则稍微复杂一点的网络都会乱七八糟。你可以通过上图中的 graph 来看看自己构建的网络结构。
  • 2.使用 tensorboard 来看 training 和 validation 的 loss 和 accuracy 变化对于调参非常非常有帮助。经验足的炼丹选手通过 tensorboard 可以很容易地判断出 batch_size 和 learning rate 设置合不合理。

想要了解更多,可以参考详解 TensorBoard-如何调参

例1 - 在 tensorboard 中记录 tensor 的变化。

import tensorflow as tf
config  = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)
import os
import shutil

"""TensorBoard 简单例子。
tf.summary.scalar('var_name', var)        # 记录标量的变化
tf.summary.histogram('vec_name', vec)     # 记录向量或者矩阵,tensor的数值分布变化。

merged = tf.summary.merge_all()           # 把所有的记录并把他们写到 log_dir 中
train_writer = tf.summary.FileWriter(log_dir + '/add_example', sess.graph)  # 保存位置

运行完后,在命令行中输入 tensorboard --logdir=log_dir_path(你保存到log路径)
"""

log_dir = 'summary/graph/'
if os.path.exists(log_dir):   # 删掉以前的summary,以免重合
    shutil.rmtree(log_dir)
os.makedirs(log_dir)
print 'created log_dir path'

with tf.name_scope('add_example'):
    a = tf.Variable(tf.truncated_normal([100,1], mean=0.5, stddev=0.5), name='var_a')
    tf.summary.histogram('a_hist', a)
    b = tf.Variable(tf.truncated_normal([100,1], mean=-0.5, stddev=1.0), name='var_b')
    tf.summary.histogram('b_hist', b)
    increase_b = tf.assign(b, b + 0.2)
    c = tf.add(a, b)
    tf.summary.histogram('c_hist', c)
    c_mean = tf.reduce_mean(c)
    tf.summary.scalar('c_mean', c_mean)
merged = tf.summary.merge_all()
writer = tf.summary.FileWriter(log_dir+'add_example', sess.graph)


sess.run(tf.global_variables_initializer())
for step in xrange(500):
    sess.run([merged, increase_b])    # 每步改变一次 b 的值
    summary = sess.run(merged)
    writer.add_summary(summary, step)
writer.close()
created log_dir path

例2: 分 train 和 test 两部分进行记录。

每隔10个step记录一次test结果。一般在网络训练过程中,我们都会隔一段时间跑一次 valid。

import tensorflow as tf
import os
import shutil
import numpy as np
config  = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)

"""TensorBoard 简单例子。
tf.summary.scalar('var_name', var)        # 记录标量的变化
tf.summary.histogram('vec_name', vec)     # 记录向量或者矩阵,tensor的数值分布变化。

merged = tf.summary.merge_all()           # 把所有的记录并把他们写到 log_dir 中
train_writer = tf.summary.FileWriter(log_dir + '/train', sess.graph)  # 保存位置
test_writer = tf.summary.FileWriter(log_dir + '/test', sess.graph)
运行完后,在命令行中输入 tensorboard --logdir=log_dir_path(你保存到log路径)
"""

log_dir = 'summary/graph2/'
if os.path.exists(log_dir):   # 删掉以前的summary,以免重合
    shutil.rmtree(log_dir)
os.makedirs(log_dir)
print 'created log_dir path'

a = tf.placeholder(dtype=tf.float32, shape=[100,1], name='a')

with tf.name_scope('add_example'):
    b = tf.Variable(tf.truncated_normal([100,1], mean=-0.5, stddev=1.0), name='var_b')
    tf.summary.histogram('b_hist', b)
    increase_b = tf.assign(b, b + 0.2)
    c = tf.add(a, b)
    tf.summary.histogram('c_hist', c)
    c_mean = tf.reduce_mean(c)
    tf.summary.scalar('c_mean', c_mean)
merged = tf.summary.merge_all()
train_writer = tf.summary.FileWriter(log_dir + '/train', sess.graph)  # 保存位置
test_writer = tf.summary.FileWriter(log_dir + '/test', sess.graph)


sess.run(tf.global_variables_initializer())
for step in xrange(500):
    if (step+1) % 10 == 0:
        _a = np.random.randn(100,1) 
        summary, _ = sess.run([merged, increase_b], feed_dict={a: _a})    # 每步改变一次 b 的值
        test_writer.add_summary(summary, step)
    else:
        _a = np.random.randn(100,1) + step*0.2
        summary, _ = sess.run([merged, increase_b], feed_dict={a: _a})    # 每步改变一次 b 的值
        train_writer.add_summary(summary, step)
train_writer.close()
test_writer.close()
print('END!')
created log_dir path
END!

训练完后,在summary目录下输入 tensorboard –logdir graph2 就能看到结果了。如下图所示:

from IPython.display import Image
Image('figs/graph2.png')

png

作者:Jerr__y 发表于2017/11/21 16:45:38 原文链接
阅读:25 评论:0 查看评论

TensorFlow入门(九)使用 tf.train.Saver()保存模型

$
0
0

关于模型保存的一点心得

saver = tf.train.Saver(max_to_keep=3)

在定义 saver 的时候一般会定义最多保存模型的数量,一般来说,如果模型本身很大,我们需要考虑到硬盘大小。如果你需要在当前训练好的模型的基础上进行 fine-tune,那么尽可能多的保存模型,后继 fine-tune 不一定从最好的 ckpt 进行,因为有可能一下子就过拟合了。但是如果保存太多,硬盘也有压力呀。如果只想保留最好的模型,方法就是每次迭代到一定步数就在验证集上计算一次 accuracy 或者 f1 值,如果本次结果比上次好才保存新的模型,否则没必要保存。

如果你想用不同 epoch 保存下来的模型进行融合的话,3到5 个模型已经足够了,假设这各融合的模型成为 M,而最好的一个单模型称为 m_best, 这样融合的话对于M 确实可以比 m_best 更好。但是如果拿这个模型和其他结构的模型再做融合的话,M 的效果并没有 m_best 好,因为M 相当于做了平均操作,减少了该模型的“特性”。

但是又有一种新的融合方式,就是利用调整学习率来获取多个局部最优点,就是当 loss 降不下了,保存一个 ckpt, 然后开大学习率继续寻找下一个局部最优点,然后用这些 ckpt 来做融合,还没试过,单模型肯定是有提高的,就是不知道还会不会出现上面再与其他模型融合就没提高的情况。

如何使用 tf.train.Saver() 来保存模型

之前一直出错,主要是因为坑爹的编码问题。所以要注意文件的路径绝对不不要出现什么中文呀。

import tensorflow as tf
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)

# Create some variables.
v1 = tf.Variable([1.0, 2.3], name="v1")
v2 = tf.Variable(55.5, name="v2")

# Add an op to initialize the variables.
init_op = tf.global_variables_initializer()

# Add ops to save and restore all the variables.
saver = tf.train.Saver()

ckpt_path = './ckpt/test-model.ckpt'
# Later, launch the model, initialize the variables, do some work, save the
# variables to disk.
sess.run(init_op)
save_path = saver.save(sess, ckpt_path, global_step=1)
print("Model saved in file: %s" % save_path)
Model saved in file: ./ckpt/test-model.ckpt-1

注意,在上面保存完了模型之后。应该把 kernel restart 之后才能使用下面的模型导入。否则会因为两次命名 “v1” 而导致名字错误。

import tensorflow as tf
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)

# Create some variables.
v1 = tf.Variable([11.0, 16.3], name="v1")
v2 = tf.Variable(33.5, name="v2")

# Add ops to save and restore all the variables.
saver = tf.train.Saver()

# Later, launch the model, use the saver to restore variables from disk, and
# do some work with the model.
# Restore variables from disk.
ckpt_path = './ckpt/test-model.ckpt'
saver.restore(sess, ckpt_path + '-'+ str(1))
print("Model restored.")

print sess.run(v1)
print sess.run(v2)
INFO:tensorflow:Restoring parameters from ./ckpt/test-model.ckpt-1
Model restored.
[ 1.          2.29999995]
55.5

导入模型之前,必须重新再定义一遍变量。

但是并不需要全部变量都重新进行定义,只定义我们需要的变量就行了。

也就是说,你所定义的变量一定要在 checkpoint 中存在;但不是所有在checkpoint中的变量,你都要重新定义。

import tensorflow as tf
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)

# Create some variables.
v1 = tf.Variable([11.0, 16.3], name="v1")

# Add ops to save and restore all the variables.
saver = tf.train.Saver()

# Later, launch the model, use the saver to restore variables from disk, and
# do some work with the model.
# Restore variables from disk.
ckpt_path = './ckpt/test-model.ckpt'
saver.restore(sess, ckpt_path + '-'+ str(1))
print("Model restored.")

print sess.run(v1)
INFO:tensorflow:Restoring parameters from ./ckpt/test-model.ckpt-1
Model restored.
[ 1.          2.29999995]

tf.Saver([tensors_to_be_saved]) 中可以传入一个 list,把要保存的 tensors 传入,如果没有给定这个list的话,他会默认保存当前所有的 tensors。一般来说,tf.Saver 可以和 tf.variable_scope() 巧妙搭配,可以参考: 【迁移学习】往一个已经保存好的模型添加新的变量并进行微调

作者:Jerr__y 发表于2017/11/21 16:59:28 原文链接
阅读:24 评论:0 查看评论

TensorFlow入门(十-I)tfrecord 固定维度数据读写

$
0
0

本例代码:https://github.com/yongyehuang/Tensorflow-Tutorial/tree/master/python/the_use_of_tfrecord

关于 tfrecord 的使用,分别介绍 tfrecord 进行三种不同类型数据的处理方法。
- 维度固定的 numpy 矩阵
- 可变长度的 序列 数据
- 图片数据

在 tf1.3 及以后版本中,推出了新的 Dataset API, 之前赶实验还没研究,可能以后都不太会用下面的方式写了。这些代码都是之前写好的,因为注释中都写得比较清楚了,所以直接上代码。

tfrecord_1_numpy_writer.py

# -*- coding:utf-8 -*- 

import tensorflow as tf
import numpy as np
from tqdm import tqdm

'''tfrecord 写入数据.
将固定shape的矩阵写入 tfrecord 文件。这种形式的数据写入 tfrecord 是最简单的。
refer: http://blog.csdn.net/qq_16949707/article/details/53483493
'''

# **1.创建文件,可以创建多个文件,在读取的时候只需要提供所有文件名列表就行了
writer1 = tf.python_io.TFRecordWriter('../data/test1.tfrecord')
writer2 = tf.python_io.TFRecordWriter('../data/test2.tfrecord')

"""
有一点需要注意的就是我们需要把矩阵转为数组形式才能写入
就是需要经过下面的 reshape 操作
在读取的时候再 reshape 回原始的 shape 就可以了
"""
X = np.arange(0, 100).reshape([50, -1]).astype(np.float32)
y = np.arange(50)

for i in tqdm(xrange(len(X))):  # **2.对于每个样本
    if i >= len(y) / 2:
        writer = writer2
    else:
        writer = writer1
    X_sample = X[i].tolist()
    y_sample = y[i]
    # **3.定义数据类型,按照这里固定的形式写,有float_list(好像只有32位), int64_list, bytes_list.
    example = tf.train.Example(
        features=tf.train.Features(
            feature={'X': tf.train.Feature(float_list=tf.train.FloatList(value=X_sample)),
                     'y': tf.train.Feature(int64_list=tf.train.Int64List(value=[y_sample]))}))
    # **4.序列化数据并写入文件中
    serialized = example.SerializeToString()
    writer.write(serialized)

print('Finished.')
writer1.close()
writer2.close()

tfrecord_1_numpy_reader.py

# -*- coding:utf-8 -*- 

import tensorflow as tf

'''read data
从 tfrecord 文件中读取数据,对应数据的格式为固定shape的数据。
'''

# **1.把所有的 tfrecord 文件名列表写入队列中
filename_queue = tf.train.string_input_producer(['../data/test1.tfrecord', '../data/test2.tfrecord'], num_epochs=None,
                                                shuffle=True)
# **2.创建一个读取器
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
# **3.根据你写入的格式对应说明读取的格式
features = tf.parse_single_example(serialized_example,
                                   features={
                                       'X': tf.FixedLenFeature([2], tf.float32),  # 注意如果不是标量,需要说明数组长度
                                       'y': tf.FixedLenFeature([], tf.int64)}     # 而标量就不用说明
                                   )
X_out = features['X']
y_out = features['y']

print(X_out)
print(y_out)
# **4.通过 tf.train.shuffle_batch 或者 tf.train.batch 函数读取数据
"""
在shuffle_batch 函数中,有几个参数的作用如下:
capacity: 队列的容量,容量越大的话,shuffle 得就更加均匀,但是占用内存也会更多
num_threads: 读取进程数,进程越多,读取速度相对会快些,根据个人配置决定
min_after_dequeue: 保证队列中最少的数据量。
   假设我们设定了队列的容量C,在我们取走部分数据m以后,队列中只剩下了 (C-m) 个数据。然后队列会不断补充数据进来,
   如果后勤供应(CPU性能,线程数量)补充速度慢的话,那么下一次取数据的时候,可能才补充了一点点,如果补充完后的数据个数少于
   min_after_dequeue 的话,不能取走数据,得继续等它补充超过 min_after_dequeue 个样本以后才让取走数据。
   这样做保证了队列中混着足够多的数据,从而才能保证 shuffle 取值更加随机。
   但是,min_after_dequeue 不能设置太大,否则补充时间很长,读取速度会很慢。
"""
X_batch, y_batch = tf.train.shuffle_batch([X_out, y_out], batch_size=2,
                                          capacity=200, min_after_dequeue=100, num_threads=2)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)

# **5.启动队列进行数据读取
# 下面的 coord 是个线程协调器,把启动队列的时候加上线程协调器。
# 这样,在数据读取完毕以后,调用协调器把线程全部都关了。
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
y_outputs = list()
for i in xrange(5):
    _X_batch, _y_batch = sess.run([X_batch, y_batch])
    print('** batch %d' % i)
    print('_X_batch:', _X_batch)
    print('_y_batch:', _y_batch)
    y_outputs.extend(_y_batch.tolist())
print(y_outputs)

# **6.最后记得把队列关掉
coord.request_stop()
coord.join(threads)
作者:Jerr__y 发表于2017/11/21 17:13:10 原文链接
阅读:23 评论:0 查看评论

TensorFlow入门(十-II)tfrecord 可变长度的序列数据

$
0
0

本例代码:https://github.com/yongyehuang/Tensorflow-Tutorial/tree/master/python/the_use_of_tfrecord

关于 tfrecord 的使用,分别介绍 tfrecord 进行三种不同类型数据的处理方法。
- 维度固定的 numpy 矩阵
- 可变长度的 序列 数据
- 图片数据

在 tf1.3 及以后版本中,推出了新的 Dataset API, 之前赶实验还没研究,可能以后都不太会用下面的方式写了。这些代码都是之前写好的,因为注释中都写得比较清楚了,所以直接上代码。

tfrecord_2_sequence_writer.py

# -*- coding:utf-8 -*- 

import tensorflow as tf
import numpy as np
from tqdm import tqdm

'''tfrecord 写入序列数据,每个样本的长度不固定。
和固定 shape 的数据处理方式类似,前者使用 tf.train.Example() 方式,而对于变长序列数据,需要使用 
tf.train.SequenceExample()。 在 tf.train.SequenceExample() 中,又包括了两部分:
context 来放置非序列化部分;
feature_lists 放置变长序列。

refer: 
https://github.com/tensorflow/magenta/blob/master/magenta/common/sequence_example_lib.py
https://github.com/dennybritz/tf-rnn
http://leix.me/2017/01/09/tensorflow-practical-guides/
https://github.com/siavash9000/im2txt_demo/blob/master/im2txt/im2txt/ops/inputs.py
'''

# **1.创建文件
writer1 = tf.python_io.TFRecordWriter('../../data/seq_test1.tfrecord')
writer2 = tf.python_io.TFRecordWriter('../../data/seq_test2.tfrecord')

# 非序列数据
labels = [1, 2, 3, 4, 5, 1, 2, 3, 4]
# 长度不固定的序列
frames = [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4], [5, 5, 5, 5, 5],
          [1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]


writer = writer1
for i in tqdm(xrange(len(labels))):  # **2.对于每个样本
    if i == len(labels) / 2:
        writer = writer2
        print('\nThere are %d sample writen into writer1' % i)
    label = labels[i]
    frame = frames[i]
    # 非序列化
    label_feature = tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
    # 序列化
    frame_feature = [
        tf.train.Feature(int64_list=tf.train.Int64List(value=[frame_])) for frame_ in frame
    ]

    seq_example = tf.train.SequenceExample(
        # context 来放置非序列化部分
        context=tf.train.Features(feature={
            "label": label_feature
        }),
        # feature_lists 放置变长序列
        feature_lists=tf.train.FeatureLists(feature_list={
            "frame": tf.train.FeatureList(feature=frame_feature),
        })
    )

    serialized = seq_example.SerializeToString()
    writer.write(serialized)  # **4.写入文件中

print('Finished.')
writer1.close()
writer2.close()

tfrecord_2_sequence_reader.py

# -*- coding:utf-8 -*- 

import tensorflow as tf

'''read data
从 tfrecord 文件中读取数据,对应数据的格式为固定shape的数据。
'''

# **1.把所有的 tfrecord 文件名列表写入队列中
filename_queue = tf.train.string_input_producer(['../data/training.tfrecord'], num_epochs=None, shuffle=True)
# **2.创建一个读取器
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
# **3.根据你写入的格式对应说明读取的格式
features = tf.parse_single_example(serialized_example,
                                   features={
                                       'image': tf.FixedLenFeature([], tf.string),
                                       'label': tf.FixedLenFeature([], tf.int64)
                                   }
                                   )
img = features['image']
# 这里需要对图片进行解码
img = tf.image.decode_png(img, channels=3)  # 这里,也可以解码为 1 通道
img = tf.reshape(img, [28, 28, 3])  # 28*28*3

label = features['label']

print('img is', img)
print('label is', label)
# **4.通过 tf.train.shuffle_batch 或者 tf.train.batch 函数读取数据
"""
这里,你会发现每次取出来的数据都是一个类别的,除非你把 capacity 和 min_after_dequeue 设得很大,如
X_batch, y_batch = tf.train.shuffle_batch([img, label], batch_size=100,
                                          capacity=20000, min_after_dequeue=10000, num_threads=3)
这是因为在打包的时候都是一个类别一个类别的顺序打包的,所以每次填数据都是按照那个顺序填充进来。
只有当我们把队列容量舍得非常大,这样在队列中才会混杂各个类别的数据。但是这样非常不好,因为这样的话,
读取速度就会非常慢。所以解决方法是:
1.在写入数据的时候先进行数据 shuffle。
2.多存几个 tfrecord 文件,比如 64 个。
"""

X_batch, y_batch = tf.train.shuffle_batch([img, label], batch_size=100,
                                          capacity=200, min_after_dequeue=100, num_threads=3)

sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)

# **5.启动队列进行数据读取
# 下面的 coord 是个线程协调器,把启动队列的时候加上线程协调器。
# 这样,在数据读取完毕以后,调用协调器把线程全部都关了。
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
y_outputs = list()
for i in xrange(5):
    _X_batch, _y_batch = sess.run([X_batch, y_batch])
    print('** batch %d' % i)
    print('_X_batch.shape:', _X_batch.shape)
    print('_y_batch:', _y_batch)
    y_outputs.extend(_y_batch.tolist())
print(y_outputs)

# **6.最后记得把队列关掉
coord.request_stop()
coord.join(threads)

sequence_example_lib.py

# -*- coding:utf-8 -*- 

# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility functions for working with tf.train.SequenceExamples.
https://github.com/tensorflow/magenta/blob/master/magenta/common/sequence_example_lib.py
"""

import math
import tensorflow as tf

QUEUE_CAPACITY = 500
SHUFFLE_MIN_AFTER_DEQUEUE = QUEUE_CAPACITY // 5


def make_sequence_example(inputs, labels):
    """Returns a SequenceExample for the given inputs and labels.

    Args:
      inputs: A list of input vectors. Each input vector is a list of floats.
      labels: A list of ints.

    Returns:
      A tf.train.SequenceExample containing inputs and labels.
    """
    input_features = [
        tf.train.Feature(float_list=tf.train.FloatList(value=input_))
        for input_ in inputs]
    label_features = [
        tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
        for label in labels]
    feature_list = {
        'inputs': tf.train.FeatureList(feature=input_features),
        'labels': tf.train.FeatureList(feature=label_features)
    }
    feature_lists = tf.train.FeatureLists(feature_list=feature_list)
    return tf.train.SequenceExample(feature_lists=feature_lists)


def _shuffle_inputs(input_tensors, capacity, min_after_dequeue, num_threads):
    """Shuffles tensors in `input_tensors`, maintaining grouping."""
    shuffle_queue = tf.RandomShuffleQueue(
        capacity, min_after_dequeue, dtypes=[t.dtype for t in input_tensors])
    enqueue_op = shuffle_queue.enqueue(input_tensors)
    runner = tf.train.QueueRunner(shuffle_queue, [enqueue_op] * num_threads)
    tf.train.add_queue_runner(runner)

    output_tensors = shuffle_queue.dequeue()

    for i in range(len(input_tensors)):
        output_tensors[i].set_shape(input_tensors[i].shape)

    return output_tensors


def get_padded_batch(file_list, batch_size, input_size,
                     num_enqueuing_threads=4, shuffle=False):
    """Reads batches of SequenceExamples from TFRecords and pads them.

    Can deal with variable length SequenceExamples by padding each batch to the
    length of the longest sequence with zeros.

    Args:
      file_list: A list of paths to TFRecord files containing SequenceExamples.
      batch_size: The number of SequenceExamples to include in each batch.
      input_size: The size of each input vector. The returned batch of inputs
          will have a shape [batch_size, num_steps, input_size].
      num_enqueuing_threads: The number of threads to use for enqueuing
          SequenceExamples.
      shuffle: Whether to shuffle the batches.

    Returns:
      inputs: A tensor of shape [batch_size, num_steps, input_size] of floats32s.
      labels: A tensor of shape [batch_size, num_steps] of int64s.
      lengths: A tensor of shape [batch_size] of int32s. The lengths of each
          SequenceExample before padding.
    Raises:
      ValueError: If `shuffle` is True and `num_enqueuing_threads` is less than 2.
    """
    file_queue = tf.train.string_input_producer(file_list)
    reader = tf.TFRecordReader()
    _, serialized_example = reader.read(file_queue)

    sequence_features = {
        'inputs': tf.FixedLenSequenceFeature(shape=[input_size],
                                             dtype=tf.float32),
        'labels': tf.FixedLenSequenceFeature(shape=[],
                                             dtype=tf.int64)}

    _, sequence = tf.parse_single_sequence_example(
        serialized_example, sequence_features=sequence_features)

    length = tf.shape(sequence['inputs'])[0]  # 序列长度
    input_tensors = [sequence['inputs'], sequence['labels'], length]

    if shuffle:
        if num_enqueuing_threads < 2:
            raise ValueError(
                '`num_enqueuing_threads` must be at least 2 when shuffling.')
        shuffle_threads = int(math.ceil(num_enqueuing_threads) / 2.)

        # Since there may be fewer records than SHUFFLE_MIN_AFTER_DEQUEUE, take the
        # minimum of that number and the number of records.
        min_after_dequeue = count_records(
            file_list, stop_at=SHUFFLE_MIN_AFTER_DEQUEUE)
        input_tensors = _shuffle_inputs(
            input_tensors, capacity=QUEUE_CAPACITY,
            min_after_dequeue=min_after_dequeue,
            num_threads=shuffle_threads)

        num_enqueuing_threads -= shuffle_threads

    tf.logging.info(input_tensors)
    return tf.train.batch(
        input_tensors,
        batch_size=batch_size,
        capacity=QUEUE_CAPACITY,
        num_threads=num_enqueuing_threads,
        dynamic_pad=True,
        allow_smaller_final_batch=False)


def count_records(file_list, stop_at=None):
    """Counts number of records in files from `file_list` up to `stop_at`.

    Args:
      file_list: List of TFRecord files to count records in.
      stop_at: Optional number of records to stop counting at.

    Returns:
      Integer number of records in files from `file_list` up to `stop_at`.
    """
    num_records = 0
    for tfrecord_file in file_list:
        tf.logging.info('Counting records in %s.', tfrecord_file)
        for _ in tf.python_io.tf_record_iterator(tfrecord_file):
            num_records += 1
            if stop_at and num_records >= stop_at:
                tf.logging.info('Number of records is at least %d.', num_records)
                return num_records
    tf.logging.info('Total records: %d', num_records)
    return num_records


def flatten_maybe_padded_sequences(maybe_padded_sequences, lengths=None):
    """Flattens the batch of sequences, removing padding (if applicable).

    Args:
      maybe_padded_sequences: A tensor of possibly padded sequences to flatten,
          sized `[N, M, ...]` where M = max(lengths).
      lengths: Optional length of each sequence, sized `[N]`. If None, assumes no
          padding.

    Returns:
       flatten_maybe_padded_sequences: The flattened sequence tensor, sized
           `[sum(lengths), ...]`.
    """

    def flatten_unpadded_sequences():
        # The sequences are equal length, so we should just flatten over the first
        # two dimensions.
        return tf.reshape(maybe_padded_sequences,
                          [-1] + maybe_padded_sequences.shape.as_list()[2:])

    if lengths is None:
        return flatten_unpadded_sequences()

    def flatten_padded_sequences():
        indices = tf.where(tf.sequence_mask(lengths))
        return tf.gather_nd(maybe_padded_sequences, indices)

    return tf.cond(
        tf.equal(tf.reduce_min(lengths), tf.shape(maybe_padded_sequences)[1]),
        flatten_unpadded_sequences,
        flatten_padded_sequences)
作者:Jerr__y 发表于2017/11/21 17:16:06 原文链接
阅读:26 评论:0 查看评论

POJ3749 破译密码【密码】

$
0
0

Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 9915   Accepted: 6020

Description

据说最早的密码来自于罗马的凯撒大帝。消息加密的办法是:对消息原文中的每个字母,分别用该字母之后的第5个字母替换(例如:消息原文中的每个字母A都分别替换成字母F)。而你要获得消息原文,也就是要将这个过程反过来。

密码字母:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z M
原文字母:V W X Y Z A B C D E F G H I J K L M N O P Q R S T U

注意:只有字母会发生替换,其他非字母的字符不变,并且消息原文的所有字母都是大写的。

Input

最多不超过100个数据集组成,每个数据集之间不会有空行,每个数据集由3部分组成:

  1. 起始行:START
  2. 密码消息:由1到200个字符组成一行,表示凯撒发出的一条消息.
  3. 结束行:END


在最后一个数据集之后,是另一行:ENDOFINPUT

Output

每个数据集对应一行,是凯撒的原始消息。

Sample Input

START
NS BFW, JAJSYX TK NRUTWYFSHJ FWJ YMJ WJXZQY TK YWNANFQ HFZXJX
END
START
N BTZQI WFYMJW GJ KNWXY NS F QNYYQJ NGJWNFS ANQQFLJ YMFS XJHTSI NS WTRJ
END
START
IFSLJW PSTBX KZQQ BJQQ YMFY HFJXFW NX RTWJ IFSLJWTZX YMFS MJ
END
ENDOFINPUT

Sample Output

IN WAR, EVENTS OF IMPORTANCE ARE THE RESULT OF TRIVIAL CAUSES
I WOULD RATHER BE FIRST IN A LITTLE IBERIAN VILLAGE THAN SECOND IN ROME
DANGER KNOWS FULL WELL THAT CAESAR IS MORE DANGEROUS THAN HE

问题链接POJ3749 破译密码

问题简述:(略)

问题分析:查表法仍然是一种好办法。有些程序员通过观察,找出编码规律,用程序来译码,程序没有通用性,不值得推荐和借鉴。

程序说明

  译码过程,使用字符指针来处理,是一种好办法。先计算字符串长度,再用长度值来控制循环处理是一种倒腾。

  这个问题与参考链接的问题实际上是同一个问题,代码直接拿过来用了。

参考链接HDU1048 POJ1298 ZOJ1392 UVALive2540 The Hardest Problem Ever【密码】


AC的C语言程序如下:

/* HDU1048 The Hardest Problem Ever(简洁版) */

#include <stdio.h>
#include <string.h>

char start[]= "START";
char end[]= "END";
char endofinput[]= "ENDOFINPUT";
char cipher[] = "VWXYZABCDEFGHIJKLMNOPQRSTU";

int main(void)
{
    char s[1024], *p;

    for(;;) {
        gets(s);

        // 判断开始:START
        if(strcmp(s, start) == 0)
            continue;

        // 判断报文结束:END
        if(strcmp(s, end) == 0)
            continue;

        // 判断结束:ENDOFINPUT
        if(strcmp(s, endofinput) == 0)
            break;

        // 译码
        p = s;
        while(*p) {
            if('A' <= *p && *p <='Z') {
                *p = cipher[*p - 'A'];
            }
            p++;
        }

        // 输出结果
        printf("%s\n", s);
    }

    return 0;
}




作者:tigerisland45 发表于2017/11/21 17:18:19 原文链接
阅读:26 评论:0 查看评论

TensorFlow入门(十-III)tfrecord 图片数据 读写

$
0
0

本例代码:https://github.com/yongyehuang/Tensorflow-Tutorial/tree/master/python/the_use_of_tfrecord

关于 tfrecord 的使用,分别介绍 tfrecord 进行三种不同类型数据的处理方法。
- 维度固定的 numpy 矩阵
- 可变长度的 序列 数据
- 图片数据

在 tf1.3 及以后版本中,推出了新的 Dataset API, 之前赶实验还没研究,可能以后都不太会用下面的方式写了。这些代码都是之前写好的,因为注释中都写得比较清楚了,所以直接上代码。

tfrecord_3_img_writer.py

# -*- coding:utf-8 -*- 

import tensorflow as tf
import numpy as np
from tqdm import tqdm
import sys
import os
import time

'''tfrecord 写入数据.
将图片数据写入 tfrecord 文件。以 MNIST png格式数据集为例。

首先将图片解压到 ../../MNIST_data/mnist_png/ 目录下。
解压以后会有 training 和 testing 两个数据集。在每个数据集下,有十个文件夹,分别存放了这10个类别的数据。
每个文件夹名为对应的类别编码。

现在网上关于打包图片的例子非常多,实现方式各式各样,效率也相差非常多。
选择合适的方式能够有效地节省时间和硬盘空间。
有几点需要注意:
1.打包 tfrecord 的时候,千万不要使用 Image.open() 或者 matplotlib.image.imread() 等方式读取。
 1张小于10kb的png图片,前者(Image.open) 打开后,生成的对象100+kb, 后者直接生成 numpy 数组,大概是原图片的几百倍大小。
 所以应该直接使用 tf.gfile.FastGFile() 方式读入图片。
2.从 tfrecord 中取数据的时候,再用 tf.image.decode_png() 对图片进行解码。
3.不要随便使用 tf.image.resize_image_with_crop_or_pad 等函数,可以直接使用 tf.reshape()。前者速度极慢。
'''

# png 文件路径
TRAINING_DIR = '../../MNIST_data/mnist_png/training/'
TESTING_DIR = '../../MNIST_data/mnist_png/testing/'
# tfrecord 文件保存路径,这里只保存一个 tfrecord 文件
TRAINING_TFRECORD_NAME = 'training.tfrecord'
TESTING_TFRECORD_NAME = 'testing.tfrecord'

DICT_LABEL_TO_ID = {  # 把 label(文件名) 转为对应 id
    '0': 0,
    '1': 1,
    '2': 2,
    '3': 3,
    '4': 4,
    '5': 5,
    '6': 6,
    '7': 7,
    '8': 8,
    '9': 9,
}


def bytes_feature(values):
    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[values]))


def int64_feature(values):
    return tf.train.Feature(int64_list=tf.train.Int64List(value=[values]))


def convert_tfrecord_dataset(dataset_dir, tfrecord_name, tfrecord_path='../data/'):
    """ convert samples to tfrecord dataset.
    Args:
        dataset_dir: 数据集的路径。
        tfrecord_name: 保存为 tfrecord 文件名
        tfrecord_path: 保存 tfrecord 文件的路径。
    """
    if not os.path.exists(dataset_dir):
        print(u'png文件路径错误,请检查是否已经解压png文件。')
        exit()
    if not os.path.exists(os.path.dirname(tfrecord_path)):
        os.makedirs(os.path.dirname(tfrecord_path))
    tfrecord_file = os.path.join(tfrecord_path, tfrecord_name)
    class_names = os.listdir(dataset_dir)
    n_class = len(class_names)
    print(u'一共有 %d 个类别' % n_class)
    with tf.python_io.TFRecordWriter(tfrecord_file) as writer:
        for class_name in class_names:  # 对于每个类别
            class_dir = os.path.join(dataset_dir, class_name)  # 获取类别对应的文件夹路径
            file_names = os.listdir(class_dir)  # 在该文件夹下,获取所有图片文件名
            label_id = DICT_LABEL_TO_ID.get(class_name)  # 获取类别 id
            print(u'\n正在处理类别 %d 的数据' % label_id)
            time0 = time.time()
            n_sample = len(file_names)
            for i in range(n_sample):
                file_name = file_names[i]
                sys.stdout.write('\r>> Converting image %d/%d , %g s' % (
                    i + 1, n_sample, time.time() - time0))
                png_path = os.path.join(class_dir, file_name)  # 获取每个图片的路径
                # CNN inputs using
                img = tf.gfile.FastGFile(png_path, 'rb').read()  # 读入图片
                example = tf.train.Example(
                    features=tf.train.Features(
                        feature={
                            'image': bytes_feature(img),
                            'label': int64_feature(label_id)
                        }))
                serialized = example.SerializeToString()
                writer.write(serialized)
    print('\nFinished writing data to tfrecord files.')


if __name__ == '__main__':
    convert_tfrecord_dataset(TRAINING_DIR, TRAINING_TFRECORD_NAME)
    convert_tfrecord_dataset(TESTING_DIR, TESTING_TFRECORD_NAME)

tfrecord_3_img_reader.py

# -*- coding:utf-8 -*- 

import tensorflow as tf

'''read data
从 tfrecord 文件中读取数据,对应数据的格式为固定shape的数据。
'''

# **1.把所有的 tfrecord 文件名列表写入队列中
filename_queue = tf.train.string_input_producer(['../data/training.tfrecord'], num_epochs=None, shuffle=True)
# **2.创建一个读取器
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
# **3.根据你写入的格式对应说明读取的格式
features = tf.parse_single_example(serialized_example,
                                   features={
                                       'image': tf.FixedLenFeature([], tf.string),
                                       'label': tf.FixedLenFeature([], tf.int64)
                                   }
                                   )
img = features['image']
# 这里需要对图片进行解码
img = tf.image.decode_png(img, channels=3)  # 这里,也可以解码为 1 通道
img = tf.reshape(img, [28, 28, 3])  # 28*28*3

label = features['label']

print('img is', img)
print('label is', label)
# **4.通过 tf.train.shuffle_batch 或者 tf.train.batch 函数读取数据
"""
这里,你会发现每次取出来的数据都是一个类别的,除非你把 capacity 和 min_after_dequeue 设得很大,如
X_batch, y_batch = tf.train.shuffle_batch([img, label], batch_size=100,
                                          capacity=20000, min_after_dequeue=10000, num_threads=3)
这是因为在打包的时候都是一个类别一个类别的顺序打包的,所以每次填数据都是按照那个顺序填充进来。
只有当我们把队列容量舍得非常大,这样在队列中才会混杂各个类别的数据。但是这样非常不好,因为这样的话,
读取速度就会非常慢。所以解决方法是:
1.在写入数据的时候先进行数据 shuffle。
2.多存几个 tfrecord 文件,比如 64 个。
"""

X_batch, y_batch = tf.train.shuffle_batch([img, label], batch_size=100,
                                          capacity=200, min_after_dequeue=100, num_threads=3)

sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)

# **5.启动队列进行数据读取
# 下面的 coord 是个线程协调器,把启动队列的时候加上线程协调器。
# 这样,在数据读取完毕以后,调用协调器把线程全部都关了。
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
y_outputs = list()
for i in xrange(5):
    _X_batch, _y_batch = sess.run([X_batch, y_batch])
    print('** batch %d' % i)
    print('_X_batch.shape:', _X_batch.shape)
    print('_y_batch:', _y_batch)
    y_outputs.extend(_y_batch.tolist())
print(y_outputs)

# **6.最后记得把队列关掉
coord.request_stop()
coord.join(threads)

作者:Jerr__y 发表于2017/11/21 17:19:57 原文链接
阅读:23 评论:0 查看评论

怎么利用产品的基线和功能架构来做冒烟测试(快速测试)

Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>