主题 : java实现fp-growth算法
级别: 白丁
UID: 130392
积分:5 加为好友
威望: 1 精华: 0
主题:1 回复:2
注册时间:2019-06-28
在线时长:0
1#   发表于:2019-06-28 11:00:27  IP:114.137.*.*
最近公司项目上用到频繁项发现算法,于是就用java实现了一个fp-growth算法实现。

环境说明 版本说明 备注
操作系统 debian 9 无 
jdk openjdk 1.8 无 
关于fp-growth算法的原理请参考:

https://www.cnblogs.com/pinard/p/6307064.html 和《机器学习实战》。

FpTreeNode类

package com.slyk.sdp.algorithms.externalAlgorithms.fpTree;

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

/**
 * 描述:fpTree树节点
 * 
 * @param <T>
 *
 * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
 * @created on 2019年5月23日,下午8:01:46
 */
public class FpTreeNode<T> {
    /**
     * 当前节点频繁度
     */
    private long count = 0;
    
    /**
     * 节点内容值
     */
    private T nodeVal;
    
    /**
     * 父类节点
     */
    private FpTreeNode<T> parent = null;
    
    /**
     * 当前节点子节点
     */
    private List<FpTreeNode<T>> children = null;
    
    /**
     * helper
     */
    private FpTreeHelper<T> helper = null;
    
    public FpTreeNode(long count, T nodeVal, FpTreeNode<T> parent, List<FpTreeNode<T>> children,
            FpTreeHelper<T> helper) {
        super();
        this.count = count;
        this.nodeVal = nodeVal;
        this.parent = parent;
        this.children = children;
        this.helper = helper;
    }

    /**
     * 描述:添加子节点
     *
     * @param child
     * @return 被添加的子节点
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月23日,下午7:33:13
     */
    public FpTreeNode<T> addChild(FpTreeNode<T> child) {
        if (this.getChildren() == null) {
            children = new ArrayList<FpTreeNode<T>>();
        }
        child.setParent(this);
        this.children.add(child);
        return child;
    }
    
    /**
     * 描述:向当前节点添加路径
     * <br/>
     * List结构数据前一项为后一项数据父节点,例:<br/>
     * a,b,c,d</br>
     * <table border="1px" cellspacing="0px">
     * <tr><th>节点</th><th>父节点</th></tr>
     * <tr><td>a</td><td>null</td></tr>
     * <tr><td>b</td><td>a</td></tr>
     * <tr><td>c</td><td>b</td></tr>
     * <tr><td>d</td><td>c</td></tr>
     * </table>
     * 
     * @param path 树的一条路径,是某个事物下的数据记录列表
     * @param parentNode 路径第一个节点的父节点
     * @return 
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月25日,下午9:42:41
     */
    public void addPath(List<T> path, FpTreeNode<T> parentNode) {
        if (path == null || path.size() == 0) {
            return ;
        }
        
        T firstEl = path.get(0);
        if (parentNode != null 
                && helper.nodeCompare(firstEl, parentNode.getNodeVal())) {
            parentNode.increaseCountOne();
            parentNode.addPath(path.subList(1, path.size()), parentNode);
        } else {
            FpTreeNode<T> fnode = new FpTreeNode<T>(1, firstEl, null, null, this.getHelper()); 
            
            FpTreeNode<T> exsistChild = this.findChild(fnode.getNodeVal());
            if (exsistChild != null) {
                exsistChild.increaseCountOne();
                exsistChild.addPath(path.subList(1, path.size()), exsistChild);
            } else {
                FpTreeNode<T> node = this.addChild(fnode);
                node.addPath(path.subList(1, path.size()), node);
            }
        }
    }
    
    /**
     * 描述:计数器加一
     *
     * @return 当前节点计数器
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月23日,下午7:36:21
     */
    public long increaseCountOne() {
        return this.increaseCount(1);
    }
    
    /**
     * 描述:
     *
     * @param increasement
     * @return 当前节点计数器
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月23日,下午7:37:16
     */
    public long increaseCount(long increasement) {
        this.count += increasement;
        return this.count;
    }
    
    /**
     * 描述: 当前节点寻找指定子节点,有,则返回节点,无则返回null
     *
     * @param childVal
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月23日,下午7:41:42
     */
    public FpTreeNode<T> findChild(T childVal) {
        if (children == null) {
            return null;
        }
        for (FpTreeNode<T> child : children) {
            if (helper.nodeCompare(child.getNodeVal(), childVal)) {
                return child;
            }
        }
        return null;
    }

    
    public String toString() {
        return super.toString() + "-node (val:" + this.getNodeVal() + ", count: " + this.getCount() + ")";
    }

    public long getCount() {
        return count;
    }

    public void setCount(long count) {
        this.count = count;
    }

    public T getNodeVal() {
        return nodeVal;
    }

    public void setNodeVal(T nodeVal) {
        this.nodeVal = nodeVal;
    }

    public FpTreeNode<T> getParent() {
        return parent;
    }

    public void setParent(FpTreeNode<T> parent) {
        this.parent = parent;
    }

    public List<FpTreeNode<T>> getChildren() {
        return children;
    }

    public void setChildren(List<FpTreeNode<T>> children) {
        this.children = children;
    }

    public FpTreeHelper<T> getHelper() {
        return helper;
    }

    public void setHelper(FpTreeHelper<T> helper) {
        this.helper = helper;
    }
}
FpTreeHeader类

package com.slyk.sdp.algorithms.externalAlgorithms.fpTree;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import com.slyk.sdp.algorithms.externalAlgorithms.fpTree.util.ListSortUtils;

/**
 * 描述:fptree项头表
 * 
 * @param <K>
 *
 * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
 * @created on 2019年5月23日,下午8:05:14
 */
("hiding")
public class FpTreeHeader<K, Integer> extends LinkedHashMap <K, java.lang.Integer> {
    private static Logger logger = LoggerFactory.getLogger(FpTreeHeader.class);
    
    private static final long serialVersionUID = 1L;
    
    /**
     * 过滤、排序后的原始数据,用以做构建fptree输入数据
     */
    private List<List<K>> inputData = new LinkedList<List<K>>();
    
    /**
     * helper
     */
    private FpTreeHelper<K> helper;
    
    /**
     * 节点链,fptree构建后依据项头表建立的节点链列表
     */
    private Map<K, List<FpTreeNode<K>>> treeNodeMap = new LinkedHashMap<K, List<FpTreeNode<K>>>();
    
    /**
     * 描述:添加helper
     *
     * @param helper
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月29日,上午10:54:18
     */
    public FpTreeHeader<K, Integer> addHelper( FpTreeHelper<K> helper) {
        this.setHelper(helper);
        return this;
    }
    
    /**
     * 描述: 构建节点链列表
     *
     * @param node
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * Created On 2019年5月29日, 上午1:13:27
     */
    protected void buildNodeEntryList(FpTreeNode<K> node) {
        if (node.getCount() != -1) {
            List<FpTreeNode<K>> nodeList = treeNodeMap.get(node.getNodeVal());
            if (nodeList == null) {
                nodeList = new ArrayList<FpTreeNode<K>>();
                nodeList.add(node);
                treeNodeMap.put(node.getNodeVal(), nodeList);
            } else {
                nodeList.add(node);
            }
        }
        
        if (node.getChildren() == null) {
            return ;
        }
        
        for (FpTreeNode<K> child : node.getChildren()) {
            buildNodeEntryList(child);
        }
    }
    
    /**
     * 描述:构建项头表
     *
     * @param sourceData
     * @param absSupport
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月23日,下午8:36:58
     */
    ("unchecked")
    public FpTreeHeader<K, Integer> buildTable(List<List<K>> sourceData, int absSupport) {
        Assert.notNull(this.helper, "helper cannot be null, Set helper first!");
        
        logger.debug("构建项头表.");
        for (List<K> data : sourceData) {
            for (K k : data) {
                if (this.get(k) == null) {
                    this.put(k, 1);
                } else {
                    this.put(k, this.get(k) + 1);
                }
            }
        }
        
        // 过滤不满足项目
        Set<java.util.Map.Entry<K, java.lang.Integer>> set = this.entrySet();
        Iterator<java.util.Map.Entry<K, java.lang.Integer>> ite = set.iterator();
        while (ite.hasNext()) {
            java.util.Map.Entry<K, java.lang.Integer> entry = ite.next();
            if (entry.getValue() < absSupport) {
                ite.remove();
            }
        }
        
        // 项头表排序
        List<K> keylist = new ArrayList<K>(this.keySet());
        Map<K, Integer> thisRef = (Map<K, Integer>) new LinkedHashMap<String, Integer>();
        ListSortUtils.sort(keylist, this.getHelper().nodeEleCompare((FpTreeHeader<K, java.lang.Integer>) this));
        for (K k : keylist) {
            thisRef.put(k, (Integer) this.get(k));
        }
        this.clear();
        this.putAll((Map<? extends K, ? extends java.lang.Integer>) thisRef);
        
        // 对原始输入数据过滤并排序
        for (List<K> data : sourceData) {
            for (Iterator<K> itr = data.iterator(); itr.hasNext(); ) {
                K k = itr.next();
                if (!this.containsKey(k)) {
                    itr.remove();
                }
            }
            FpTreeHeader<K, java.lang.Integer> _this = (FpTreeHeader<K, java.lang.Integer>) this;
            ListSortUtils.sort(data, new Comparator<K>() {
                
                public int compare(K o1, K o2) {
                    int i = _this.get(o2) - _this.get(o1);
                    if (i == 0) {
                        Iterator<java.util.Map.Entry<K, java.lang.Integer>> itr = _this.entrySet().iterator();
                        int index1 = 0;
                        int index2 = 0;
                        for (int a = 0,b = 0; itr.hasNext(); ) {
                            a = a + 1;
                            b = b + 1;
                            java.util.Map.Entry<K, java.lang.Integer> entry = itr.next();
                            if (helper.nodeCompare(entry.getKey(), o1)) {
                                index1 = a;
                            } else if (helper.nodeCompare(entry.getKey(), o2)) {
                                index2 = b;
                            }
                        }
                        i = index1 - index2;
                    }
                    return i;
                }
                
            });
            if (!data.isEmpty()) {
                inputData.add(data);
            }
        }
        sourceData = null;
        logger.debug("构建项头表完成.");
        return this;
    }

    public List<List<K>> getInputData() {
        return inputData;
    }

    public void setInputData(List<List<K>> inputData) {
        this.inputData = inputData;
    }

    public FpTreeHelper<K> getHelper() {
        return helper;
    }

    public void setHelper(FpTreeHelper<K> helper) {
        this.helper = helper;
    }

    public Map<K, List<FpTreeNode<K>>> getTreeNodeMap() {
        return treeNodeMap;
    }

    public void setTreeNodeMap(Map<K, List<FpTreeNode<K>>> treeNodeMap) {
        this.treeNodeMap = treeNodeMap;
    }
}
FpTree类:

package com.slyk.sdp.algorithms.externalAlgorithms.fpTree;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import com.slyk.sdp.algorithms.externalAlgorithms.fpTree.util.DoubleKeyMap;

/**
 * FPtree
 * 
 * 描述:@param <T>
 *
 * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
 * @created on 2019年6月3日,下午1:34:22
 */
public class FpTree<T> {
    private static Logger logger = LoggerFactory.getLogger(FpTree.class);
    
    /**
     * 项头表
     */
    private FpTreeHeader<T, Integer> fpTreeHeader;
    
    /**
     * helper
     */
    private FpTreeHelper<T> helper;
    
    /**
     * root node
     */
    private FpTreeNode<T> root;
    
    /**
     * 默认频繁度阈值
     */
    protected static final int DEFAULT_ABS_SUPPORT = 0xf;
    
    private int absSupport = DEFAULT_ABS_SUPPORT;
    
    /**
     * 默认置信度
     */
    private static final int DEFAULT_CONFIDENT = 3;
    
    /**
     * 置信度
     */
    private int confident = DEFAULT_CONFIDENT;
    
    /**
     * 描述:挖掘树
     * <br/>代码参考自《机器学习实战》
     *
     * @param outList
     * @param tree
     * @param basePat
     * @return
     * @throws ClassNotFoundException
     * @throws IOException
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月31日,下午5:50:45
     */
    public List<List<T>> fpGrowth(List<List<T>> outList, FpTree<T> tree, List<T> prefix) throws ClassNotFoundException, IOException {
        logger.debug("开始conditionFpTree数据挖掘计算.");
        //
        // 挖掘频繁项集的步骤如下:
        // 1 从FP树提取条件模式基
        // 2 用条件模式基构造FP树
        // 3 重复1和2直到树只包含一个元素
        //
        DoubleKeyMap<T, List<T>, Integer> cpbs = tree.buildPrefixPath();
        
        // 从项头表逆序访问
        ListIterator<Map.Entry<T, Integer>> li =
                new ArrayList<Map.Entry<T, Integer>>(
                        this.fpTreeHeader.entrySet()).listIterator(this.fpTreeHeader.size()); 
        for ( ;li.hasPrevious(); ) {
            Map.Entry<T, Integer> entry = li.previous(); 
            T fpHeaderItem = entry.getKey();
                       
            List<T> newBasePat = new ArrayList<T>(prefix);
            newBasePat.add(fpHeaderItem);
            
            this.getHelper().resultHandler(newBasePat, confident, entry.getValue());
            logger.debug("发现频繁项集:" + newBasePat.toString());
            
            Set<List<T>> set = cpbs.get(fpHeaderItem).keySet();
            Iterator<List<T>> setItr = set.iterator();
            Map<String, List<T>> cpbInputData = new LinkedHashMap<String, List<T>>();
            for ( ; setItr.hasNext(); ) {
                List<T> cpb = setItr.next();
                Integer count = cpbs.get(fpHeaderItem, cpb);
                for (int repeat = 0; repeat < count; repeat++) {
                    if (!cpb.isEmpty()) {
                        cpbInputData.put(cpb.toString() + "-" + repeat, cpb);
                    }
                }
            }
            FpTree<T> cpbTree = new FpTree<T>();
            cpbTree.setHelper(this.getHelper());
            cpbTree = cpbTree.init(this.getAbsSupport(), this.getConfident(), cpbInputData);
            if (!cpbTree.getFpTreeHeader().keySet().isEmpty()) {
                cpbTree.fpGrowth(outList, cpbTree, newBasePat);
            }
            
            cpbTree = null;
            cpbInputData = null;
        }
        logger.debug("完成conditionFpTree数据挖掘计算.");
        return outList;
    }
    
    /**
     * 描述:初始、构建fptree,为数据挖掘做准备
     * <br/>
     * <strong>
     * 输入sourceMap数据结构:</br>
     * KEY&emsp;&emsp;            LIST</br>
     * T0&emsp;&emsp;             1,2,3,4</br>
     * T1&emsp;&emsp;             1</br>
     * T2&emsp;&emsp;             3,4</br>
     * T3&emsp;&emsp;             2,3</br>
     * </strong>
     * 
     * @param absSupport
     * @param sourceMap
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月29日,上午11:53:11
     */
    public FpTree<T> init(Integer absSupport, Integer confident, Map<String, List<T>> sourceMap) {
        logger.debug("开始fptree构建.");
        this.absSupport = absSupport == null ? this.getAbsSupport() : absSupport;
        this.confident = confident == null ? this.getConfident() : confident;
        
        List<List<T>> sourceData = new ArrayList<List<T>>();
        Set<String> keys = sourceMap.keySet();
        for (String key : keys) {
            List<T> inList = sourceMap.get(key);
            sourceData.add(inList);
        }
        
        this.fpTreeHeader = new FpTreeHeader<T, Integer>().addHelper(helper)
                .buildTable(sourceData, absSupport);
        sourceData = null;
        
        root = buildTree();
        
//        logger.info("构建构建节点链.");
        this.fpTreeHeader.buildNodeEntryList(root);
//        logger.info("构建构建节点链完成.");
        logger.debug("完成fptree构建..");
        return this;
    } 
    
    /**
     * 描述:构建条件模式基
     *
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月29日,下午2:37:47
     */
    public DoubleKeyMap<T, List<T>, Integer> buildPrefixPath() {
        Assert.notNull(this.fpTreeHeader, "fpTreeHeader cannot be null, Set helper first!");
        logger.debug("构建条件模式基");
        DoubleKeyMap<T, List<T>, Integer> cpb = new DoubleKeyMap<T, List<T>, Integer>();
        // 从项头表逆序寻找条件模式集
        ListIterator<Map.Entry<T, Integer>> li =
                new ArrayList<Map.Entry<T, Integer>>(
                        this.fpTreeHeader.entrySet()).listIterator(this.fpTreeHeader.size()); 
        for ( ;li.hasPrevious(); ) {
            Map.Entry<T, Integer> entry = li.previous(); 
            T fpHeaderItem = entry.getKey();
            List<FpTreeNode<T>> nodeList = this.getFpTreeHeader().getTreeNodeMap().get(fpHeaderItem);
            for (FpTreeNode<T> node : nodeList) {
                cpb = findPrefixPath(node, cpb);
            }
        }
        logger.debug("完成构建条件模式基");
        return cpb;
    }
    
    /**
     * 描述:寻找条件模式基
     *
     * @param node 节点链中的节点
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月27日,上午1:05:45
     */
    private DoubleKeyMap<T, List<T>, Integer> findPrefixPath(FpTreeNode<T> node, DoubleKeyMap<T, List<T>, Integer> cpb) {
        Assert.notNull(this.fpTreeHeader, "fpTreeHeader cannot be null, Set helper first!");
        List<T> prefixPath = new ArrayList<T>();
        FpTreeNode<T> up = node.getParent();
        while (up.getParent() != null) {
            prefixPath.add(up.getNodeVal());
            up = up.getParent();
        }
        Collections.reverse(prefixPath);
        cpb.put(node.getNodeVal(), prefixPath, (int) node.getCount());
        return cpb;
    }
    
    /**
     * 描述:构建fptree
     *
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月24日,上午11:45:34
     */
    private FpTreeNode<T> buildTree() {
        Assert.notNull(this.helper, "helpser cannot be null, Set helper first!");
        Assert.notNull(this.fpTreeHeader, "fpTreeHeader cannot be null, Set helper first!");
        
        FpTreeNode<T> rootNode = new FpTreeNode<T>(-1, null, null, null, helper);
        
        int index = 0;
        for (List<T> path : this.fpTreeHeader.getInputData()) {
            rootNode.addPath(path, rootNode);
            index += 1;
            logger.debug("fptree完成进度:" + index + "/" + this.fpTreeHeader.getInputData().size());
        }
        
        return rootNode;
    }
    
    /**
     * 描述: 打印树,以便直观观察
     *
     * @param node
     * @param ins
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * Created On 2019年5月29日, 上午12:27:43
     */
    public void display(FpTreeNode<T> node, int ins) {
        if (node.getParent() == null) {
            logger.info("打印树形结构如下:");
        }
        System.out.print(StringUtils.repeat("  ", ins) + node.getNodeVal() + "  " + node.getCount() + "\n");
        if (node.getChildren() == null) {
            return ;
        }
        ins = ins + 1;
        for (FpTreeNode<T> subNode : node.getChildren()) {
            display(subNode, ins);
        }
    }
    
    /**
     * 描述:deepCopy
     *
     * @param src
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月30日,下午5:28:26
     */
    ("unchecked")
    public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {  
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();  
        ObjectOutputStream out = new ObjectOutputStream(byteOut);  
        out.writeObject(src); 
        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());  
        ObjectInputStream in = new ObjectInputStream(byteIn);  
        List<T> dest = (List<T>) in.readObject();  
        return dest;  
    }  

    public FpTreeHeader<T, Integer> getFpTreeHeader() {
        return fpTreeHeader;
    }

    public void setFpTreeHeader(FpTreeHeader<T, Integer> fpTreeHeader) {
        this.fpTreeHeader = fpTreeHeader;
    }

    public FpTreeHelper<T> getHelper() {
        return helper;
    }

    public void setHelper(FpTreeHelper<T> helper) {
        this.helper = helper;
    }

    public FpTreeNode<T> getRoot() {
        return root;
    }

    public void setRoot(FpTreeNode<T> root) {
        this.root = root;
    }

    public int getAbsSupport() {
        return absSupport;
    }

    public void setAbsSupport(int absSupport) {
        this.absSupport = absSupport;
    }

    public int getConfident() {
        return confident;
    }

    public void setConfident(int confident) {
        this.confident = confident;
    }
}
FpTreeHelper类

package com.slyk.sdp.algorithms.externalAlgorithms.fpTree;

import java.util.Comparator;
import java.util.List;

/**
 * 描述:fptree helper class
 * 
 * @param <T>
 *
 * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
 * @created on 2019年5月23日,下午7:49:46
 */
public interface FpTreeHelper<T> {
    /**
     * 描述:比较目标节点和源节点是否相等,等返回true,否则false
     *
     * @param target
     * @param source
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月23日,下午7:52:35
     */
    boolean nodeCompare(T target, T source);
    
    /**
     * 描述:比较节点内容
     *
     * @return
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年5月29日,上午10:42:35
     */
    Comparator<T> nodeEleCompare(FpTreeHeader<T, Integer> header);
    
    /**
     * 描述: 找到的结果处理
     *
     * @param result 单条记录
     * @param extArgs 附加参数
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * Created On 2019年6月2日, 上午1:36:35
     */
    void resultHandler(List<T> result, Object ...extArgs);
}
DoubleKeyMap类

package com.slyk.sdp.algorithms.externalAlgorithms.fpTree.util;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

public class DoubleKeyMap<Key1, Key2, Value> {
    Map<Key1, Map<Key2, Value>> data = new LinkedHashMap<Key1, Map<Key2, Value>>();

    public Value put(Key1 k1, Key2 k2, Value v) {
        Map<Key2, Value> data2 = data.get(k1);
        Value prev = null;
        if ( data2==null ) {
            data2 = new LinkedHashMap<Key2, Value>();
            data.put(k1, data2);
        }
        else {
            prev = data2.get(k2);
        }
        data2.put(k2, v);
        return prev;
    }

    public Value get(Key1 k1, Key2 k2) {
        Map<Key2, Value> data2 = data.get(k1);
        if ( data2==null ) return null;
        return data2.get(k2);
    }

    public Map<Key2, Value> get(Key1 k1) { return data.get(k1); }

    /** Get all values associated with primary key */
    public Collection<Value> values(Key1 k1) {
        Map<Key2, Value> data2 = data.get(k1);
        if ( data2==null ) return null;
        return data2.values();
    }

    /** get all primary keys */
    public Set<Key1> keySet() {
        return data.keySet();
    }

    /** get all secondary keys associated with a primary key */
    public Set<Key2> keySet(Key1 k1) {
        Map<Key2, Value> data2 = data.get(k1);
        if ( data2==null ) return null;
        return data2.keySet();
    }

    public Collection<Value> values() {
        Set<Value> s = new HashSet<Value>();
        for (Map<Key2, Value> k2 : data.values()) {
            for (Value v : k2.values()) {
                s.add(v);
            }
        }
        return s;
    }
    
    ("unchecked")
    public void print() {
        System.out.println("条件模式基集合:");
        Set<Key1> kset = this.keySet();
        for (Iterator<Key1> itrKey1 = kset.iterator(); itrKey1.hasNext();) {
            Key1 key1 = itrKey1.next();
            for (Iterator<Key2> itrKey2 = this.get(key1).keySet().iterator(); itrKey2.hasNext();) {
                Object key2 = itrKey2.next();
                System.out.println(key1 + ":" + key2 + "(" + this.get(key1, (Key2) key2) + ")");
            }
        }
    }
}
ListSortUtils类

package com.slyk.sdp.algorithms.externalAlgorithms.fpTree.util;

import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;

/**
 * 描述: 求集合排序,原本集合collections包有排序功能,但是jdk7后改为 timsort排序实现,此类实现存在问题,例如: int[]
 * sample = new int[]
 * {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
 * ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,1,0,-2,0,0,0,0};
 * 如上例子数据在timsort排序将会报错,因此排序算法将参考JDK5的LegacyMergeSort算法封装排序。
 *
 * @author <a href='mailto:xiaomingyang@aksl.com.cn'>xiaomingyang</a>
 *         2019年6月2日,下午11:35:16
 * @version v0.1
 */
public class ListSortUtils {
    /**
     * 描述:参考Collections.sort()方法
     *
     * @param list
     * @param c
     * @author <a href='mailto:xiaomingyang@shulianyikang.com'>xiaomingyang</a>
     * @created on 2019年6月3日,下午1:35:04
     */
    ({ "rawtypes", "unchecked" })
    public static <T> void sort(List<T> list, Comparator<? super T> c) {
        Object[] a = list.toArray();
        sort(a, (Comparator) c);
        ListIterator i = list.listIterator();
        for (int j = 0; j < a.length; j++) {
            i.next();
            i.set(a[j]);
        }
    }

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        T[] aux = (T[]) a.clone();
        if (c == null)
            mergeSort(aux, a, 0, a.length, 0);
        else
            mergeSort(aux, a, 0, a.length, 0, c);
    }

    private static final int INSERTIONSORT_THRESHOLD = 7;

    private static void swap(Object[] x, int a, int b) {
        Object t = x[a];
        x[a] = x[b];
        x[b] = t;
    }

    ({ "unchecked", "rawtypes" })
    private static void mergeSort(Object[] src, Object[] dest, int low,
            int high, int off) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i = low; i < high; i++)
                for (int j = i; j > low
                        && ((Comparable) dest[j - 1]).compareTo(dest[j]) > 0; j--)
                    swap(dest, j, j - 1);
            return;
        }

        // Recursively sort halves of dest into src
        int destLow = low;
        int destHigh = high;
        low += off;
        high += off;
        int mid = (low + high) >> 1;
        mergeSort(dest, src, low, mid, -off);
        mergeSort(dest, src, mid, high, -off);

        // If list is already sorted, just copy from src to dest. This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (((Comparable) src[mid - 1]).compareTo(src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // Merge sorted halves (now in src) into dest
        for (int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid
                    && ((Comparable) src[p]).compareTo(src[q]) <= 0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }

    ({ "unchecked", "rawtypes" })
    private static void mergeSort(Object[] src, Object[] dest, int low,
            int high, int off, Comparator c) {
        int length = high - low;

        // Insertion sort on smallest arrays
        if (length < INSERTIONSORT_THRESHOLD) {
            for (int i = low; i < high; i++)
                for (int j = i; j > low && c.compare(dest[j - 1], dest[j]) > 0; j--)
                    swap(dest, j, j - 1);
            return;
        }

        // Recursively sort halves of dest into src
        int destLow = low;
        int destHigh = high;
        low += off;
        high += off;
        int mid = (low + high) >> 1;
        mergeSort(dest, src, low, mid, -off, c);
        mergeSort(dest, src, mid, high, -off, c);

        // If list is already sorted, just copy from src to dest. This is an
        // optimization that results in faster sorts for nearly ordered lists.
        if (c.compare(src[mid - 1], src[mid]) <= 0) {
            System.arraycopy(src, low, dest, destLow, length);
            return;
        }

        // Merge sorted halves (now in src) into dest
        for (int i = destLow, p = low, q = mid; i < destHigh; i++) {
            if (q >= high || p < mid && c.compare(src[p], src[q]) <= 0)
                dest[i] = src[p++];
            else
                dest[i] = src[q++];
        }
    }
}
App类

package com.slyk.sdp.algorithms;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.slyk.sdp.algorithms.externalAlgorithms.fpTree.FpTree;
import com.slyk.sdp.algorithms.externalAlgorithms.fpTree.FpTreeHeader;
import com.slyk.sdp.algorithms.externalAlgorithms.fpTree.FpTreeHelper;

/**
 * Hello world!
 *
 */
public class App {
    private BufferedReader br;

    private static int frequency = 2;
    
    private static int confident = 3;

    public void getSourceData() throws IOException {
        Map<String, List<String>> inputMap = ReadFile();
        System.out.println(inputMap);
    }

    private Map<String, List<String>> ReadFile() throws IOException {
        Map<String, List<String>> inputMap = new LinkedHashMap<String, List<String>>();
        br = new BufferedReader(new InputStreamReader(this.getClass().getResourceAsStream("/input.csv")));
        String line = null;
        int lineNum = 0;
        while (null != (line = br.readLine())) {
            // System.out.println("T" + lineNum + ":" + line);
            lineNum += 1;
            List<String> list = Arrays.asList(line.split(","));
            inputMap.put("T" + lineNum, new ArrayList<String>(list));
        }
        return inputMap;
    }
    
    public void fpGrowth() throws IOException, ClassNotFoundException {
        List<List<String>> resultList = new ArrayList<List<String>>();
        Map<String, List<String>> inputMap = ReadFile();
        FpTree<String> tree = new FpTree<String>();
        tree.setHelper(new FpTreeHelper<String>() {
            private Comparator<String> comparetor = null;
            
            private File resultFile = new File("./result.txt");
            
            private BufferedWriter writer = null;
            
            private FileOutputStream fos = null;
            
            
            public boolean nodeCompare(String target, String source) {
                return StringUtils.equals(target, source);
            }

            /**
             * 节点内容需要按项头表统计数量逆序排序
             */
            
            public Comparator<String> nodeEleCompare(FpTreeHeader<String, Integer> header) {
                if (comparetor == null) {
                    comparetor = new Comparator<String>() {
                        
                        public int compare(String o1, String o2) {
                            int i = header.get(o2) - header.get(o1);
                            return i;
                        }  
                    };
                }
                return comparetor;
            }

            
            public void resultHandler(List<String> result, Object ...extArgs) {
                try {
                    if (result.size() < (int) extArgs[0]) {
                        return ;
                    }
                    resultList.add(result);
                    fos = new FileOutputStream(resultFile, true);
                    writer = new BufferedWriter(new OutputStreamWriter(fos));
                    writer.write(result.toString() + "   " + (int)extArgs[1] + "\r\n");
                    writer.flush();
                    fos.flush();
                    writer.close();
                    fos.close();
                    System.out.println(result);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        
        tree.fpGrowth(new ArrayList<List<String>>(), 
                tree.init(2, 2, inputMap), new ArrayList<String>());
        tree.display(tree.getRoot(), 2);
        System.out.println("===========================");
        for (Object o : resultList) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        App app = new App();
        app.fpGrowth();
    }
}
运行结果:

[, ]
[, , ]
[, ]
[, , a0a5dff287d5f43498a81934df34f6e1]
[, , a0a5dff287d5f43498a81934df34f6e1, ]
[, , ]
[, a0a5dff287d5f43498a81934df34f6e1]
[, a0a5dff287d5f43498a81934df34f6e1, ]
[, ]
[, ]
[, ]
[, ]
[dd416340a4613c46ed12b87717e4a8ac, ]
[dd416340a4613c46ed12b87717e4a8ac, , a0a5dff287d5f43498a81934df34f6e1]
[dd416340a4613c46ed12b87717e4a8ac, a0a5dff287d5f43498a81934df34f6e1]
[, a0a5dff287d5f43498a81934df34f6e1]
[, ]
[, , a0a5dff287d5f43498a81934df34f6e1]
[a0a5dff287d5f43498a81934df34f6e1, ]
 

注意的问题:

jdk7以上在做排序时候不要用Collection工具自带的排序,有坑滴;
关于fptree树,节点顺序并不会影响结果;
关于频繁项挖掘过程中,可能会出现很多频繁项集,所以在计算过程中注意内存问题,当然,解决办法就是在resultHandler方法中进行频繁项集的结果合并,参数将会传入频繁度,所以result中的数据可以和已有结果进行比较,如果result+频繁度已经在resultList中存在,则不处理此结果,如果resultList中的结果被包含与result中,更新resultList对应项,这样只保持最大结果集,舍弃中间结果来节省内存,同时对result应该做deepCopy,不然GC时释放不掉引用,内存会溢出;
《机器学习实战》中的代码有错,下面的是修改过的;
对于上亿上十亿的输入数据,速度提升办法是把数据切成几个片,每个片都跑fp-growth,跑完所有结果汇集起来继续切片,再跑,反复这个过程直到输入数据和输出数据一样就终止;
对于结果正确性的验证采用《机器学习实战》一书中的代码来验证

'''
Created on Jun , 
FP-Growth FP means frequent pattern
the FP-Growth algorithm needs: 
 FP-tree (class treeNode)
 header table (use dict)

This finds frequent itemsets similar to apriori but does not 
find association rules.  

: Peter
'''
class treeNode:
    def __init__(self, nameValue, numOccur, parentNode):
        self.name = nameValue
        self.count = numOccur
        self.nodeLink = None
        self.parent = parentNode      #needs to be updated
        self.children = {} 
    
    def inc(self, numOccur):
        self.count += numOccur
        
    def disp(self, ind=1):
        print '  '*ind, self.name, ' ', self.count
        for child in self.children.values():
            child.disp(ind+1)

def createTree(dataSet, minSup=1): #create FP-tree from dataset but don't mine
    headerTable = {}
    #go over dataSet twice
    for trans in dataSet:#first pass counts frequency of occurance
        for item in trans:
            headerTable[item] = headerTable.get(item, 0) + dataSet[trans]
    for k in headerTable.keys():  #remove items not meeting minSup
        if headerTable[k] < minSup: 
            del(headerTable[k])
    freqItemSet = set(headerTable.keys())
    #print 'freqItemSet: ',freqItemSet
    if len(freqItemSet) == 0: return None, None  #if no items meet min support -->get out
    for k in headerTable:
        headerTable[k] = [headerTable[k], None] #reformat headerTable to use Node link 
    #print 'headerTable: ',headerTable
    retTree = treeNode('Null Set', 1, None) #create tree
    for tranSet, count in dataSet.items():  #go through dataset  time
        localD = {}
        for item in tranSet:  #put transaction items in order
            if item in freqItemSet:
                localD[item] = headerTable[item][]
        if len(localD) > :
            orderedItems = [v[0] for v in sorted(localD.items(), key=lambda p: p[1], reverse=True)]
            updateTree(orderedItems, retTree, headerTable, count)#populate tree with ordered freq itemset
    return retTree, headerTable #return tree and header table

def updateTree(items, inTree, headerTable, count):
    if items[0] in inTree.children:#check if orderedItems[] in retTree.children
        inTree.children[items[0]].inc(count) #incrament count
    else:   #add items[] to inTree.children
        inTree.children[items[0]] = treeNode(items[], count, inTree)
        if headerTable[items[0]][1] == None: #update header table 
            headerTable[items[0]][1] = inTree.children[items[]]
        else:
            updateHeader(headerTable[items[]][1], inTree.children[items[]])
    if len(items) > 1:#call updateTree() with remaining ordered items
        updateTree(items[1::], inTree.children[items[]], headerTable, count)
        
def updateHeader(nodeToTest, targetNode):   #this version does not use recursion
    while (nodeToTest.nodeLink != None):    #Do not use recursion to traverse a linked list!
        nodeToTest = nodeToTest.nodeLink
    nodeToTest.nodeLink = targetNode
        
def ascendTree(leafNode, prefixPath): #ascends from leaf node to root
    if leafNode.parent != None:
        prefixPath.append(leafNode.name)
        ascendTree(leafNode.parent, prefixPath)
    
def findPrefixPath(basePat, treeNode): #treeNode comes from header table
    condPats = {}
    while treeNode != None:
        prefixPath = []
        ascendTree(treeNode, prefixPath)
        if len(prefixPath) > 1: 
            condPats[frozenset(prefixPath[1:])] = treeNode.count
        treeNode = treeNode.nodeLink
    return condPats

def mineTree(inTree, headerTable, minSup, preFix, freqItemList):
    bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1])]#(sort header table)
    for basePat in bigL:  #start from bottom of header table
        newFreqSet = preFix.copy()
        newFreqSet.add(basePat)
        #print 'finalFrequent Item: ',newFreqSet    #append to set
        freqItemList.append(newFreqSet)
        condPattBases = findPrefixPath(basePat, headerTable[basePat][1])
        #print 'condPattBases :',basePat, condPattBases
        # construct cond FP-tree from cond. pattern base
        myCondTree, myHead = createTree(condPattBases, minSup)
        #print 'head from conditional tree: ', myHead
        if myHead != None: # mine cond. FP-tree
            #print 'conditional tree for: ',newFreqSet
            #myCondTree.disp()            
            mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)

def loadSimpDat():
    simpDat = [['r', 'z', 'h', 'j', 'p'],
               ['z', 'y', 'x', 'w', 'v', 'u', 't', 's'],
               ['z'],
               ['r', 'x', 'n', 'o', 's'],
               ['y', 'r', 'x', 'z', 'q', 't', 'p'],
               ['y', 'z', 'x', 'e', 'q', 's', 't', 'm']]
    return simpDat

def createInitSet(dataSet):
    retDict = {}
    for trans in dataSet:
        # retDict[frozenset(trans)] = 
        retDict[frozenset(trans)] = retDict.get(frozenset(trans), 0) + 1
    return retDict

import twitter
from time import sleep
import re

def textParse(bigString):
    urlsRemoved = re.sub('(http:[/][/]|www.)([a-z]|[A-Z]|[0-9]|[/.]|[~])*', '', bigString)    
    listOfTokens = re.split(r'\W*', urlsRemoved)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]

def getLotsOfTweets(searchStr):
    CONSUMER_KEY = ''
    CONSUMER_SECRET = ''
    ACCESS_TOKEN_KEY = ''
    ACCESS_TOKEN_SECRET = ''
    api = twitter.Api(consumer_key=CONSUMER_KEY, consumer_secret=CONSUMER_SECRET,
                      access_token_key=ACCESS_TOKEN_KEY, 
                      access_token_secret=ACCESS_TOKEN_SECRET)
    #you can get  results  pages *  per page
    resultsPages = []
    for i in range(1,15):
        print "fetching page %d" % i
        searchResults = api.GetSearch(searchStr, per_page=100, page=i)
        resultsPages.append(searchResults)
        sleep(6)
    return resultsPages

def mineTweets(tweetArr, minSup=5):
    parsedList = []
    for i in range(14):
        for j in range(100):
            parsedList.append(textParse(tweetArr[i][j].text))
    initSet = createInitSet(parsedList)
    myFPtree, myHeaderTab = createTree(initSet, minSup)
    myFreqList = []
    mineTree(myFPtree, myHeaderTab, minSup, set([]), myFreqList)
    return myFreqList

simpDat = []
for line in open('/home/xxxxxxx/yyyyyy/input.csv', 'r'):
    simpDat.append(line.replace("\n", "").split(","))

minSup = 2
initSet = createInitSet(simpDat)
myFPtree, myHeaderTab = createTree(initSet, minSup)
myFreqList = []
mineTree(myFPtree, myHeaderTab, minSup, set([]), myFreqList)
for i in myFreqList:
    if len(i) < 2:
        continue
    print i

print '============================'
myFPtree.disp()
#############################
#minSup = 
#simpDat = loadSimpDat()
#initSet = createInitSet(simpDat)
#myFPtree, myHeaderTab = createTree(initSet, minSup)
#myFPtree.disp()
#myFreqList = []
#mineTree(myFPtree, myHeaderTab, minSup, set([]), myFreqList)
#for i in myFreqList:
#    print i
结果:

set(['157d68a0e73262e443ad843e7d29195f', '4f312b0f37bf9d86333114a4f467ddc3'])
set(['6f9ef114e4478a961bf3e59d6441f243', '40b0229cbf83c8857e8c9e7d79bec8e9'])
set(['a0a5dff287d5f43498a81934df34f6e1', '6f9ef114e4478a961bf3e59d6441f243', '40b0229cbf83c8857e8c9e7d79bec8e9'])
set(['a0a5dff287d5f43498a81934df34f6e1', '40b0229cbf83c8857e8c9e7d79bec8e9'])
set(['a0a5dff287d5f43498a81934df34f6e1', 'dd416340a4613c46ed12b87717e4a8ac'])
set(['40b0229cbf83c8857e8c9e7d79bec8e9', 'dd416340a4613c46ed12b87717e4a8ac'])
set(['a0a5dff287d5f43498a81934df34f6e1', '40b0229cbf83c8857e8c9e7d79bec8e9', 'dd416340a4613c46ed12b87717e4a8ac'])
set(['a0a5dff287d5f43498a81934df34f6e1', '4e953a9700fc3a4de58eb3b066eb4c74'])
set(['a0a5dff287d5f43498a81934df34f6e1', '6f9ef114e4478a961bf3e59d6441f243', '4e953a9700fc3a4de58eb3b066eb4c74'])
set(['4e953a9700fc3a4de58eb3b066eb4c74', '40b0229cbf83c8857e8c9e7d79bec8e9'])
set(['a0a5dff287d5f43498a81934df34f6e1', '4e953a9700fc3a4de58eb3b066eb4c74', '40b0229cbf83c8857e8c9e7d79bec8e9'])
set(['6f9ef114e4478a961bf3e59d6441f243', '4e953a9700fc3a4de58eb3b066eb4c74', '40b0229cbf83c8857e8c9e7d79bec8e9'])
set(['a0a5dff287d5f43498a81934df34f6e1', '6f9ef114e4478a961bf3e59d6441f243', '4e953a9700fc3a4de58eb3b066eb4c74', '40b0229cbf83c8857e8c9e7d79bec8e9'])
set(['6f9ef114e4478a961bf3e59d6441f243', '4e953a9700fc3a4de58eb3b066eb4c74'])
set(['60dec45d8b4bbda8681e85f05e979f95', '4b041b8fab2bd834e6acc34a541520cb'])
set(['6f9ef114e4478a961bf3e59d6441f243', '6d7e042a000deca140c330ab99896ebd'])
set(['4e953a9700fc3a4de58eb3b066eb4c74', '6d7e042a000deca140c330ab99896ebd'])
set(['6f9ef114e4478a961bf3e59d6441f243', '4e953a9700fc3a4de58eb3b066eb4c74', '6d7e042a000deca140c330ab99896ebd'])
set(['a0a5dff287d5f43498a81934df34f6e1', '6f9ef114e4478a961bf3e59d6441f243'])
两者结果完全一致。

input.csv


e46289d37fa76918074ed8119f65a5f2



b881f5f8dc378d3049d1b9720e10e063

fe90159c19ea82a30c75f42b5fe25fc1


bb7c0faa9029b682b32e0997f3cc7b70

d63fbd5f0d6ac07328e33358cb058a88






fe90159c19ea82a30c75f42b5fe25fc1
dc9182ae415b862068cc47750965de96



f84846d7ac6bc61b326ad880cbf28acf
ca954af7a2442530914fcfa720c724fe






ef280f9608bfc2c73cc1f238db25060a

a9b822be6e43936173779e3641cccf37





fe640b2dd98e4b1ab800169645b5562a

bc154087659431c11af968bbc165f79e,
c92f34dc31db156f4cd691feb313a19f

ae5c3471f12387be2adebc7922067ff0

cbd465702d40fa7da1cbf835d7530029

c5d0e8298c8231faacf37c39d418d250




c521a7d8077f759012c14c6bfb531cbb
f16a381e577c5ea74b5e2c02a6e64a58


bc85909fa786f72bb236a56a8f17acd6


f35b403a015f03b9f4a7abad0ba7c291

f44422bd25e349884ea5b151708d4168

df81af52d06780671644c62b3545e350

e6cd0120d57e5060021ce8abd17edec9
acaa67e0837a1c506b4ee33778c3d34d

b26b42fa48cc68886ca0018d9893fcb5


c3bde1bd54b7318ec7fd8ba0a5ef2799


e6cd0120d57e5060021ce8abd17edec9



a2469be912c6cb69aecde7af497c5524,
ece4979927cb7d661e2ce393d9084bf1


f107e3729809537344a0a4531faed727
ece4979927cb7d661e2ce393d9084bf1



bded82f85592223993fa54de9d509fae




f75d36e5fcaaddcfeff0e05a2cf15f1d



e68f2f8b35cc47352ce750bdc5f40504

b995567c39f8f378112456f0b1594c86



a93e88e0cd314c1ed59649b720f9c411

d7ddb097e2138e1c72563b6ccb2af019
b3a19fd58ad492f377431c4000e947b1
ebefb82e4d8e381f2cb9a635b45dd9af






d92a2254790d518b031c99fb3b947641


dd797073a916722a1fa66a681b5988be
f6951cefbe653efb7a0fcf3ae0885d11

b881f5f8dc378d3049d1b9720e10e063
d967e2a9d2e8cdfa87e7f635136f8112


e68f2f8b35cc47352ce750bdc5f40504
eeb94f7eb6bace26301d5aa1ebcab163



f34424d3f47c5f6d60e146589e3e7e20














cb7d46f2edbb05fdc2d46c12e6167714


c9d44105b828c6f72ec5db6d47b8b7b3


e6ce399afaea9e9f7ef02c6ddfa1855f
ccf99ee92991020228083dd81fa4e550
ed648db5c82e86772c1c3f76fb2c42e2

edcb078fc1456b07dfdc1cf90c44e3e1




efe3b79b8dfbc5534a37c67fd08e3fe0
a93e88e0cd314c1ed59649b720f9c411

bf93f3cce7b449c9fe5cd623d3d99858
ebc1d84a9f5f8e64b19c3d9c11c3fb8d
bad451268583315c8689ea4e737b6c26
c942b22440dfbbf083726d8b843ab2bb





d2e9271fd4a4a1a79a9cbbeee4f4a5e1
b51aec0cfcf0eecbaf583f913cc18f3b


b881f5f8dc378d3049d1b9720e10e063

cbd465702d40fa7da1cbf835d7530029
baca875e1c185711a26e02177ae10a5d



b57508a80ba85a740080aa172b5d60b1


c5b24851a4a69d6d8c8e5bb899d3b812


c5d0e8298c8231faacf37c39d418d250

f35b403a015f03b9f4a7abad0ba7c291



cb7d46f2edbb05fdc2d46c12e6167714
fa3afefc843f28b1d2ba2780da5feb92

a2469be912c6cb69aecde7af497c5524,afe72d2ef971f6330b38c9f1ab219910


fc5de224caaa5ccfa9abce15370fc950
c6b56eade24f7a81fb4e3e7f92c27b47
e0ca8ccc957022a3c7ea3ceeb513ca23


ae1cb4efb580320ac44784e5b53a5749
c5b24851a4a69d6d8c8e5bb899d3b812
b31953accf1e2c2c03a21e525ed12afb




a4c2d4207408f14b4ffdacae20ca08f7








f6951cefbe653efb7a0fcf3ae0885d11

fd11c037678755346d5d87939e8121ad
d953147cd0d4a73585e0501bde56cb68








d7ddb097e2138e1c72563b6ccb2af019
fcb40c5656a7d53a0a31ce9a45134954

db65748051ef42ca5159d4f5f675cd66
bf93f3cce7b449c9fe5cd623d3d99858





a04fc54f775f2776321673d69c80d4ea
ddfd36cbf7eec6cd62016fb8608a51ce
a1c469d02bf1c38afb04a490e89db776

ccf99ee92991020228083dd81fa4e550
b09a79d6ad6b026bef05c78645335902
df1733992db1b4645207a5b06a1f953a
bb0692d4a7e038da83171b7279c9cdba
d953147cd0d4a73585e0501bde56cb68
d192e8eca0b215e58fc76ed5e7cdb8d3
e5aa65b4162a6c0b5f39dd20f8a53e83







f375fcfa1a0d974c2792f50a9439fd09

ccf99ee92991020228083dd81fa4e550




c6d822cdadf581d31cc468ebad5869a8
fc792d8bb7ad8d13c97776e18c714ce0



f96c0e7252688bf0b362e95c7dc55542

f068075227855de709cd7bea20b94712
e049fd711a582a90c5b9e73f4464d4f1


bcfa63831c099ac7ad457e7b30471635


ee1f14b3efbe3786f4c2ea50ca93c950
e46289d37fa76918074ed8119f65a5f2


c6d822cdadf581d31cc468ebad5869a8

a1bd88629f25c42bd362429ab5c81361
d897960d3da4f72ee5dd1425972f479f


b51aec0cfcf0eecbaf583f913cc18f3b




,,dd416340a4613c46ed12b87717e4a8ac,a0a5dff287d5f43498a81934df34f6e1


f5b37c482583796b56e43bd8f5724b9b





c9a9ecf56ee8b676d05588d2919232cd


e2555e80b296186557426f4a05012d3c










afc11567288caf51db32557dfe1cf238

cbbcb70943244bc738c5c1c0040f835a
b31953accf1e2c2c03a21e525ed12afb

a6fb1718e44cf177699ee9cac7177d7c

b57508a80ba85a740080aa172b5d60b1
a04fc54f775f2776321673d69c80d4ea



f16a381e577c5ea74b5e2c02a6e64a58



dd797073a916722a1fa66a681b5988be
bcd183c6dcdb056c8a0180703f9824c5




b5b9efebcb34dcab469ac4ec2e3e00da






ee1f14b3efbe3786f4c2ea50ca93c950


b5e720842c2c1b3fb3c71309fa4434bc
a1d469adbf3bd0adc4379131d52b4379


e049fd711a582a90c5b9e73f4464d4f1

e1b7b88ad76d7480beb24c0e489e3034


a81d5716b461240ff91eb40eb99a0768

c521a7d8077f759012c14c6bfb531cbb

e29e1dcb51131a6ea34fee85b7e6b882
e8bb78b1dffdfe27d544fcf9228b5b7f
cc83b05076086e5e31858b1dfe2860c3


ffeb1c1402edd94e78f7b6c0b9ec5e4c




eb89b214cbe983a4470f97aa0c4b4bcd
aa32806dcb51596555489e93f4c4340f


f732cd2547622a112d7cf896c67a9ab2



ffeb1c1402edd94e78f7b6c0b9ec5e4c




d897960d3da4f72ee5dd1425972f479f
bac3ece29aae1e4d38c96add5a19da76
c350c71a1567b63b24a5bbfc6224c14f
,





e103d09f895b97f45c376a60b2c56151
b8e635be69e7ef8295f38fe9147a9d9d
c92f34dc31db156f4cd691feb313a19f


f9c0d1a6c3dea3aa02042972502d6a5e




f75d36e5fcaaddcfeff0e05a2cf15f1d

ae45d1c21e9f38f3dc08d859f503f09a


b5f2705bc4649da0b7af267533fe7fdf




bcfa63831c099ac7ad457e7b30471635





a1c469d02bf1c38afb04a490e89db776

f10a8e559deb772c2b8b8f547e38b21e
bf3c84b69ae0c29fab3dbe8b3a9033ff
bf3bab9f98cf513bd09ffe6e773ab8d8




f34424d3f47c5f6d60e146589e3e7e20
a0a5dff287d5f43498a81934df34f6e1
c40990246bd03c18ef187a6bd514d464

f62e8250143a61e087015b0677894932

d953147cd0d4a73585e0501bde56cb68


a0ee6406194f05b6e21a3b3930dd1eac










ee079757ecd9b7c73e31920c6cd4067f


f9c0d1a6c3dea3aa02042972502d6a5e

db44fc95101c2e96c74843de12819506

fe1ee42973d48bc63fbee3c6d9f78160
ca954af7a2442530914fcfa720c724fe

b59706cad5c7c00a4b7316526096bd09

a63caf3220978f6f673292f79a493e7a


ffeb1c1402edd94e78f7b6c0b9ec5e4c
ea84fb80acfa95bb725afad25ce26baf










bb0692d4a7e038da83171b7279c9cdba
e6ce399afaea9e9f7ef02c6ddfa1855f


d149059cefe1e067a06889abaf6ae36d


ebc1d84a9f5f8e64b19c3d9c11c3fb8d








bded82f85592223993fa54de9d509fae
fe640b2dd98e4b1ab800169645b5562a





d08912a6eb92812348a55698010f3871
d6ab8077608f5bf6ed64e735ff17f1ec








df81af52d06780671644c62b3545e350




ca5b0b284a41f95cc7684e3e791e5e43

fe90159c19ea82a30c75f42b5fe25fc1
e1b7b88ad76d7480beb24c0e489e3034












b2e7cdbee461467dfda0f216f677b370


eb89b214cbe983a4470f97aa0c4b4bcd



df1733992db1b4645207a5b06a1f953a


a30d42b1b088c84c2da2ce16fca5b798
edaef13be77ad2a413af6bb4498b1ccd


f96c0e7252688bf0b362e95c7dc55542
fd11c037678755346d5d87939e8121ad


f75d36e5fcaaddcfeff0e05a2cf15f1d

,

a30d42b1b088c84c2da2ce16fca5b798



d92a2254790d518b031c99fb3b947641

e88457aa452f777eef85fbbfd95ec85e
a7cec9cad4df1525a31bdfc0f45ae03e
a1bd88629f25c42bd362429ab5c81361
efe3b79b8dfbc5534a37c67fd08e3fe0
e22df26ec5200ae93010595f0677c1a6
d08912a6eb92812348a55698010f3871

c6b56eade24f7a81fb4e3e7f92c27b47





a30d42b1b088c84c2da2ce16fca5b798
c9d44105b828c6f72ec5db6d47b8b7b3

a1d469adbf3bd0adc4379131d52b4379
b788a1dc78950ebceef7adb6ff5ff49b

add9d2ecffc679c3b016dbaa35398357


fc5de224caaa5ccfa9abce15370fc950


e45f7c307e67b13f457504ce1e93adc4



c63a50793c8bc85164ca67185bbd5ce9
b59706cad5c7c00a4b7316526096bd09

b33ad589799efe4f0ae9a5bb57c2c734



bac3ece29aae1e4d38c96add5a19da76
ef280f9608bfc2c73cc1f238db25060a








ee060398a75b39b2a03c9add1f7978e1





c1873377ef35a86716e7db29fa78afab

db44fc95101c2e96c74843de12819506


ae45d1c21e9f38f3dc08d859f503f09a



acaa67e0837a1c506b4ee33778c3d34d

e5aa65b4162a6c0b5f39dd20f8a53e83
e78d8dbdbec094b48888c2a9bdf3db31





ea84fb80acfa95bb725afad25ce26baf
ece4979927cb7d661e2ce393d9084bf1


ae5c3471f12387be2adebc7922067ff0

a7cec9cad4df1525a31bdfc0f45ae03e
faf9d9dd07e9be093311e0eba21182b1

e2026b3693a8d0d1ac381b2541831b9f









cde35f01dcc2e3b6fb1aca36ae0ee237
aecb5c9cf10ec6d43cf1377ac5f5c728


b896fec03441019f564909b07db0b3d6




b26b42fa48cc68886ca0018d9893fcb5


fc792d8bb7ad8d13c97776e18c714ce0
dd416340a4613c46ed12b87717e4a8ac




ece4979927cb7d661e2ce393d9084bf1
c1873377ef35a86716e7db29fa78afab

a63caf3220978f6f673292f79a493e7a








ab3d2eb28cbc976e2fafdad1b7ebd482

e45f7c307e67b13f457504ce1e93adc4

f75d36e5fcaaddcfeff0e05a2cf15f1d

cc83b05076086e5e31858b1dfe2860c3
b26ea9cb75c69197895f8e9c9aa819c8


b7499b38e46510bbcb3ca3606dfc4091
ebc1d84a9f5f8e64b19c3d9c11c3fb8d


bf93f3cce7b449c9fe5cd623d3d99858






a30d42b1b088c84c2da2ce16fca5b798


b2f82c344a844898d26953f64df2ff83

d92a2254790d518b031c99fb3b947641



c022301d3e96a8b59a0370c57d285f34

e22df26ec5200ae93010595f0677c1a6
a93e88e0cd314c1ed59649b720f9c411
e0ca8ccc957022a3c7ea3ceeb513ca23
a04fc54f775f2776321673d69c80d4ea

c9a9ecf56ee8b676d05588d2919232cd
f5b37c482583796b56e43bd8f5724b9b


b51aec0cfcf0eecbaf583f913cc18f3b



bac6597d8d6c0ca72075c462c4d17363
e29e1dcb51131a6ea34fee85b7e6b882
b73d8629ae2d008a8184b8761b4a9c87


b5f2705bc4649da0b7af267533fe7fdf


e78d8dbdbec094b48888c2a9bdf3db31


b758b0b14bbc1e1ad613914b959275aa


,




bac6597d8d6c0ca72075c462c4d17363

b3f3a8609446e2af6513baef5f7ba8fa

b995567c39f8f378112456f0b1594c86


f805d549425c7328c40729b84606ba07


d190cc378aabcfdad876818bfd92a118




b73d8629ae2d008a8184b8761b4a9c87
dcd815cb3dbd401112ab1a91fa9ef3a0







ee079757ecd9b7c73e31920c6cd4067f

ce692dd8417c8e34746156ce9b799e91





bb7c0faa9029b682b32e0997f3cc7b70






ee060398a75b39b2a03c9add1f7978e1






cc634c526fe4da575eac6077a3071644
ee060398a75b39b2a03c9add1f7978e1


f16a381e577c5ea74b5e2c02a6e64a58
ae1cb4efb580320ac44784e5b53a5749



f9c0d1a6c3dea3aa02042972502d6a5e,



fe640b2dd98e4b1ab800169645b5562a
c942b22440dfbbf083726d8b843ab2bb
c5b24851a4a69d6d8c8e5bb899d3b812

dd746d62539d3641ccee848781413762





b26b42fa48cc68886ca0018d9893fcb5

b3a19fd58ad492f377431c4000e947b1

bc00cf32fdfe9944f7b4b92ad9596035



fca1f2a2526124de979f3c15a53bc084
f38a2fe99994feb158aa4a165cca1e64

c3bde1bd54b7318ec7fd8ba0a5ef2799

f75d36e5fcaaddcfeff0e05a2cf15f1d


f62e8250143a61e087015b0677894932

dcd815cb3dbd401112ab1a91fa9ef3a0


a6fb1718e44cf177699ee9cac7177d7c

f96c0e7252688bf0b362e95c7dc55542
e103d09f895b97f45c376a60b2c56151


e049fd711a582a90c5b9e73f4464d4f1

d79f1283e52a2c0ccf82c9516153f8cf
b3f3a8609446e2af6513baef5f7ba8fa

c63a50793c8bc85164ca67185bbd5ce9
dce725e1c5ecf28cdf26d89b0b796f8b

f96c0e7252688bf0b362e95c7dc55542


dcdf26f3c7f87440b56877744814cd88

c022301d3e96a8b59a0370c57d285f34
bcd183c6dcdb056c8a0180703f9824c5

c843f95b066d1e84cf5be5e354efebd7


c843f95b066d1e84cf5be5e354efebd7


bac3ece29aae1e4d38c96add5a19da76

baca875e1c185711a26e02177ae10a5d
cc83b05076086e5e31858b1dfe2860c3





bad451268583315c8689ea4e737b6c26
dd3d6e9ada3c64b33eb230ecd0c570c4





fcb40c5656a7d53a0a31ce9a45134954



b7499b38e46510bbcb3ca3606dfc4091
,,ef280f9608bfc2c73cc1f238db25060a

d967e2a9d2e8cdfa87e7f635136f8112




f20c1b43cb9ce21b83778e28fa70801e



fe1ee42973d48bc63fbee3c6d9f78160
a81d5716b461240ff91eb40eb99a0768


f8d344bb4d8c6d9ecfad6577f56d5678









d92a2254790d518b031c99fb3b947641






a1385619e5caccf4b3b83d393c81642d



df81af52d06780671644c62b3545e350
d79f1283e52a2c0ccf82c9516153f8cf
f805d549425c7328c40729b84606ba07











d190cc378aabcfdad876818bfd92a118
dd746d62539d3641ccee848781413762














c521a7d8077f759012c14c6bfb531cbb



d03a1d460db0681e17d2fee4cd66335d
a9df6c8208e4ca184b689d42b99ea1cb

ed49e9c4c6a3f1169175a50ce27cc183

,f75d36e5fcaaddcfeff0e05a2cf15f1d
afc11567288caf51db32557dfe1cf238
f6951cefbe653efb7a0fcf3ae0885d11


b788a1dc78950ebceef7adb6ff5ff49b
e2555e80b296186557426f4a05012d3c




aefda5b55ebb20af07751f2d7373de46
b09a79d6ad6b026bef05c78645335902
bc85909fa786f72bb236a56a8f17acd6
e86e11d6310bd20eade2c7de37b6595a

e2555e80b296186557426f4a05012d3c

cf5f59646b9ee163bbb9c5bf55e7e39e
fdfbc6c05521ff446e9d20449213284a

dd3d6e9ada3c64b33eb230ecd0c570c4

bded82f85592223993fa54de9d509fae

f08b12a6e95cf6163c524ef687d61fff



d149059cefe1e067a06889abaf6ae36d


e2026b3693a8d0d1ac381b2541831b9f


,






bc154087659431c11af968bbc165f79e
e84fb53e9aa65d7b24f6da448ef66c4f



b5b9efebcb34dcab469ac4ec2e3e00da


d7ddb097e2138e1c72563b6ccb2af019



fcb40c5656a7d53a0a31ce9a45134954

a0a5dff287d5f43498a81934df34f6e1

ca954af7a2442530914fcfa720c724fe


cacd5b8ed67e9e94a289de0ac25a8ea5

cb58e48b611cdedea041a63e53b3d600





fb58f86d024e5aa4c7d33752428997aa
,f08b12a6e95cf6163c524ef687d61fff



d190cc378aabcfdad876818bfd92a118


ab3d2eb28cbc976e2fafdad1b7ebd482
b758b0b14bbc1e1ad613914b959275aa

ed49e9c4c6a3f1169175a50ce27cc183
f8d344bb4d8c6d9ecfad6577f56d5678

a0ee6406194f05b6e21a3b3930dd1eac






dd797073a916722a1fa66a681b5988be
ebc1d84a9f5f8e64b19c3d9c11c3fb8d

baff2f8123d5c6c13585237aee6cc7e9

c3bde1bd54b7318ec7fd8ba0a5ef2799
bba63cbc3e68d767fe13e3e609f0d7b7
















ccf99ee92991020228083dd81fa4e550

ee060398a75b39b2a03c9add1f7978e1
f9bcddadfdb5acf9c164d867f0c745d3
f9bcddadfdb5acf9c164d867f0c745d3
e23a131210f3a730d088fe9524ccfba0

c6d822cdadf581d31cc468ebad5869a8


d6ab8077608f5bf6ed64e735ff17f1ec
fb58f86d024e5aa4c7d33752428997aa
c350c71a1567b63b24a5bbfc6224c14f
cb7d46f2edbb05fdc2d46c12e6167714,
b896fec03441019f564909b07db0b3d6
bf33cbc2370274877342e745f8f8dcc8
ed49e9c4c6a3f1169175a50ce27cc183
a30d42b1b088c84c2da2ce16fca5b798
e14f9a25c81fea04d919ff1d15acc569





dcd815cb3dbd401112ab1a91fa9ef3a0






add9d2ecffc679c3b016dbaa35398357

aecb5c9cf10ec6d43cf1377ac5f5c728
ad32188ad826593f20f1f27213708737


db65748051ef42ca5159d4f5f675cd66

bf3bab9f98cf513bd09ffe6e773ab8d8

a7cec9cad4df1525a31bdfc0f45ae03e


dcdf26f3c7f87440b56877744814cd88

b2e7cdbee461467dfda0f216f677b370






f805d549425c7328c40729b84606ba07




f75d36e5fcaaddcfeff0e05a2cf15f1d

fc792d8bb7ad8d13c97776e18c714ce0


b644630d9adb01802fdf040a4b3d3891
daee0e66771781338d9b974bac146218


b758b0b14bbc1e1ad613914b959275aa
b2e7cdbee461467dfda0f216f677b370

f7ad1f228ae3049f9d6d2b6d6c53c035


f107e3729809537344a0a4531faed727
d953147cd0d4a73585e0501bde56cb68
e6cd0120d57e5060021ce8abd17edec9


baff2f8123d5c6c13585237aee6cc7e9
c1873377ef35a86716e7db29fa78afab
dce725e1c5ecf28cdf26d89b0b796f8b
ccd558b0a457902007b10ffe8e43626a


efe3b79b8dfbc5534a37c67fd08e3fe0
de6707573b047f0d607c2bfac8a8b0fc
e6ce399afaea9e9f7ef02c6ddfa1855f


b644630d9adb01802fdf040a4b3d3891



d63fbd5f0d6ac07328e33358cb058a88
d40d704f68437bb2c4499614b45a2b73


e14f9a25c81fea04d919ff1d15acc569

e29e1dcb51131a6ea34fee85b7e6b882

b7499b38e46510bbcb3ca3606dfc4091





b73d8629ae2d008a8184b8761b4a9c87

a60305ce45d1bed9759420c05bb74200
fa3afefc843f28b1d2ba2780da5feb92



fbdd29796d1d0fadeb05bd0e264f5df3

ae1cb4efb580320ac44784e5b53a5749


bf3c84b69ae0c29fab3dbe8b3a9033ff




ad32188ad826593f20f1f27213708737

f9c0d1a6c3dea3aa02042972502d6a5e


ddfd36cbf7eec6cd62016fb8608a51ce
ccf99ee92991020228083dd81fa4e550
e14f9a25c81fea04d919ff1d15acc569
ccf99ee92991020228083dd81fa4e550
fd1c283728ced59db32ac882c2ac1c97



f732cd2547622a112d7cf896c67a9ab2
ae5c3471f12387be2adebc7922067ff0

ccf99ee92991020228083dd81fa4e550



ed648db5c82e86772c1c3f76fb2c42e2
d2e9271fd4a4a1a79a9cbbeee4f4a5e1





ccd558b0a457902007b10ffe8e43626a
b881f5f8dc378d3049d1b9720e10e063
,f20c1b43cb9ce21b83778e28fa70801e
b33ad589799efe4f0ae9a5bb57c2c734
bfc7954327958deb552352afc9e337c8
efe3b79b8dfbc5534a37c67fd08e3fe0


b09a79d6ad6b026bef05c78645335902
f84846d7ac6bc61b326ad880cbf28acf
c5720d79367be461bb5ddc9cc12d879d

ccf99ee92991020228083dd81fa4e550
b2e7cdbee461467dfda0f216f677b370
bb71b3cee023a640cba5c7176f5697cc



daee0e66771781338d9b974bac146218
ccf99ee92991020228083dd81fa4e550



e86e11d6310bd20eade2c7de37b6595a




f44422bd25e349884ea5b151708d4168
ddfd36cbf7eec6cd62016fb8608a51ce
fd1c283728ced59db32ac882c2ac1c97
eb89b214cbe983a4470f97aa0c4b4bcd
a81d5716b461240ff91eb40eb99a0768
bf3c84b69ae0c29fab3dbe8b3a9033ff

ce692dd8417c8e34746156ce9b799e91

e2555e80b296186557426f4a05012d3c


e280f8f908ed6232649d7275c4f3c263
fd1c283728ced59db32ac882c2ac1c97



c5720d79367be461bb5ddc9cc12d879d


db44fc95101c2e96c74843de12819506

d6ab8077608f5bf6ed64e735ff17f1ec


,dd416340a4613c46ed12b87717e4a8ac,,,a0a5dff287d5f43498a81934df34f6e1
fbdd29796d1d0fadeb05bd0e264f5df3






a04fc54f775f2776321673d69c80d4ea




f5d256fda01b89f2952ac64ac7cbe846


c9d44105b828c6f72ec5db6d47b8b7b3

a1c3759c68f6d088d5e6fc8bef76e739
a1bd88629f25c42bd362429ab5c81361

c92f34dc31db156f4cd691feb313a19f
b59706cad5c7c00a4b7316526096bd09


f6951cefbe653efb7a0fcf3ae0885d11

f38a2fe99994feb158aa4a165cca1e64


a74029be428eb715469c37c5c087051d

b644630d9adb01802fdf040a4b3d3891


d897960d3da4f72ee5dd1425972f479f
b57508a80ba85a740080aa172b5d60b1


a81d5716b461240ff91eb40eb99a0768







b2e7cdbee461467dfda0f216f677b370


c6b56eade24f7a81fb4e3e7f92c27b47




aefda5b55ebb20af07751f2d7373de46



e103d09f895b97f45c376a60b2c56151

f7ad1f228ae3049f9d6d2b6d6c53c035
daee0e66771781338d9b974bac146218
f16a381e577c5ea74b5e2c02a6e64a58
fdfbc6c05521ff446e9d20449213284a
f08b12a6e95cf6163c524ef687d61fff


f5b37c482583796b56e43bd8f5724b9b


d24dec1f4eaa71f4cd40c6f2ff2f8c4c



cde35f01dcc2e3b6fb1aca36ae0ee237



c843f95b066d1e84cf5be5e354efebd7




f75d36e5fcaaddcfeff0e05a2cf15f1d


c5720d79367be461bb5ddc9cc12d879d
a30d42b1b088c84c2da2ce16fca5b798

abd6ebbf2a002a1b194630daecdbd2d4

eb158e420f553716a872ad93cc0ab002
cbd465702d40fa7da1cbf835d7530029

cbbcb70943244bc738c5c1c0040f835a
d6ab8077608f5bf6ed64e735ff17f1ec







d40d704f68437bb2c4499614b45a2b73

f7ad1f228ae3049f9d6d2b6d6c53c035
cb58e48b611cdedea041a63e53b3d600




cacd5b8ed67e9e94a289de0ac25a8ea5
a1fe35eb510f9f68846fa704c2ac3c8f

f10a8e559deb772c2b8b8f547e38b21e
fb58f86d024e5aa4c7d33752428997aa
dc71ab56ab5e0075a7edf3b6af248022

d3af2d890b6c1331abc0cf1bab1b66e4
b995567c39f8f378112456f0b1594c86


bfc7954327958deb552352afc9e337c8
f9c0d1a6c3dea3aa02042972502d6a5e

f10a8e559deb772c2b8b8f547e38b21e

a1d469adbf3bd0adc4379131d52b4379

b2f82c344a844898d26953f64df2ff83


e049fd711a582a90c5b9e73f4464d4f1


f96c0e7252688bf0b362e95c7dc55542





fca1f2a2526124de979f3c15a53bc084


d7ddb097e2138e1c72563b6ccb2af019
b2ef9941ccb75ad33ad5902e97f307bd



,,
f375fcfa1a0d974c2792f50a9439fd09
dcdf26f3c7f87440b56877744814cd88

aa32806dcb51596555489e93f4c4340f

c9a9ecf56ee8b676d05588d2919232cd
f20c1b43cb9ce21b83778e28fa70801e



a81d5716b461240ff91eb40eb99a0768
bad451268583315c8689ea4e737b6c26
fe1ee42973d48bc63fbee3c6d9f78160
d190cc378aabcfdad876818bfd92a118





bcd183c6dcdb056c8a0180703f9824c5
ca5b0b284a41f95cc7684e3e791e5e43
f75d36e5fcaaddcfeff0e05a2cf15f1d




ec826b4a85aec5221c8d6510f1dc7927
d24dec1f4eaa71f4cd40c6f2ff2f8c4c


eb158e420f553716a872ad93cc0ab002

,
f107e3729809537344a0a4531faed727





bc154087659431c11af968bbc165f79e


abd6ebbf2a002a1b194630daecdbd2d4

a1fe35eb510f9f68846fa704c2ac3c8f





ef280f9608bfc2c73cc1f238db25060a







bf33cbc2370274877342e745f8f8dcc8

bac6597d8d6c0ca72075c462c4d17363



b5e720842c2c1b3fb3c71309fa4434bc




eb158e420f553716a872ad93cc0ab002




f5d256fda01b89f2952ac64ac7cbe846
e103d09f895b97f45c376a60b2c56151
ee1f14b3efbe3786f4c2ea50ca93c950

d2e9271fd4a4a1a79a9cbbeee4f4a5e1

e22df26ec5200ae93010595f0677c1a6



cde35f01dcc2e3b6fb1aca36ae0ee237
f732cd2547622a112d7cf896c67a9ab2
bf33cbc2370274877342e745f8f8dcc8
a9df6c8208e4ca184b689d42b99ea1cb

f75d36e5fcaaddcfeff0e05a2cf15f1d





bba63cbc3e68d767fe13e3e609f0d7b7
b5b9efebcb34dcab469ac4ec2e3e00da
f84846d7ac6bc61b326ad880cbf28acf

b26b42fa48cc68886ca0018d9893fcb5
f9bcddadfdb5acf9c164d867f0c745d3
d08912a6eb92812348a55698010f3871
cc634c526fe4da575eac6077a3071644
b995567c39f8f378112456f0b1594c86







f9bcddadfdb5acf9c164d867f0c745d3

e049fd711a582a90c5b9e73f4464d4f1

d3af2d890b6c1331abc0cf1bab1b66e4
bc00cf32fdfe9944f7b4b92ad9596035
b51aec0cfcf0eecbaf583f913cc18f3b







fd11c037678755346d5d87939e8121ad


aefda5b55ebb20af07751f2d7373de46


b59706cad5c7c00a4b7316526096bd09
cf3c57ea59d001362293960a88f112b1






baff2f8123d5c6c13585237aee6cc7e9






fc5de224caaa5ccfa9abce15370fc950

df1733992db1b4645207a5b06a1f953a

d5835f133f559870443026042f3315a2
ae45d1c21e9f38f3dc08d859f503f09a












cbd465702d40fa7da1cbf835d7530029


a81d5716b461240ff91eb40eb99a0768
de6707573b047f0d607c2bfac8a8b0fc
b8e635be69e7ef8295f38fe9147a9d9d
a0ee6406194f05b6e21a3b3930dd1eac
f34424d3f47c5f6d60e146589e3e7e20
c521a7d8077f759012c14c6bfb531cbb


f08b12a6e95cf6163c524ef687d61fff
d40d704f68437bb2c4499614b45a2b73
fa3afefc843f28b1d2ba2780da5feb92

e8bb78b1dffdfe27d544fcf9228b5b7f


d24dec1f4eaa71f4cd40c6f2ff2f8c4c
cf3c57ea59d001362293960a88f112b1


c350c71a1567b63b24a5bbfc6224c14f


a7dc01d775e22a34c4caf0cc9bc90273
cbbcb70943244bc738c5c1c0040f835a


b51aec0cfcf0eecbaf583f913cc18f3b
b995567c39f8f378112456f0b1594c86
b486e4c64373befe35c0dd65be80e1b1



dc71ab56ab5e0075a7edf3b6af248022

ea84fb80acfa95bb725afad25ce26baf
c63a50793c8bc85164ca67185bbd5ce9









a1fe35eb510f9f68846fa704c2ac3c8f
a60305ce45d1bed9759420c05bb74200


d5835f133f559870443026042f3315a2
edcb078fc1456b07dfdc1cf90c44e3e1

e84fb53e9aa65d7b24f6da448ef66c4f

dce725e1c5ecf28cdf26d89b0b796f8b




ee060398a75b39b2a03c9add1f7978e1
bb7c0faa9029b682b32e0997f3cc7b70
fdfbc6c05521ff446e9d20449213284a

a7cec9cad4df1525a31bdfc0f45ae03e
f068075227855de709cd7bea20b94712


c40990246bd03c18ef187a6bd514d464
f375fcfa1a0d974c2792f50a9439fd09

ee060398a75b39b2a03c9add1f7978e1

dd416340a4613c46ed12b87717e4a8ac
ebefb82e4d8e381f2cb9a635b45dd9af

add9d2ecffc679c3b016dbaa35398357
e88457aa452f777eef85fbbfd95ec85e






a1c469d02bf1c38afb04a490e89db776

d03a1d460db0681e17d2fee4cd66335d

aecb5c9cf10ec6d43cf1377ac5f5c728
e5aa65b4162a6c0b5f39dd20f8a53e83
c9d44105b828c6f72ec5db6d47b8b7b3


a30d42b1b088c84c2da2ce16fca5b798

bb71b3cee023a640cba5c7176f5697cc













db1c55573becfbee3903ae573224b179




e049fd711a582a90c5b9e73f4464d4f1


eeb94f7eb6bace26301d5aa1ebcab163
a1385619e5caccf4b3b83d393c81642d



fca1f2a2526124de979f3c15a53bc084
baca875e1c185711a26e02177ae10a5d

b31953accf1e2c2c03a21e525ed12afb
a9b822be6e43936173779e3641cccf37


ad32188ad826593f20f1f27213708737

b788a1dc78950ebceef7adb6ff5ff49b

dc9182ae415b862068cc47750965de96
d967e2a9d2e8cdfa87e7f635136f8112
cbd465702d40fa7da1cbf835d7530029


e1b7b88ad76d7480beb24c0e489e3034
afe72d2ef971f6330b38c9f1ab219910

e8bb78b1dffdfe27d544fcf9228b5b7f

bb71b3cee023a640cba5c7176f5697cc
f8d344bb4d8c6d9ecfad6577f56d5678

afe72d2ef971f6330b38c9f1ab219910



bfc7954327958deb552352afc9e337c8




f62e8250143a61e087015b0677894932


b26ea9cb75c69197895f8e9c9aa819c8

cb58e48b611cdedea041a63e53b3d600

c5d0e8298c8231faacf37c39d418d250
edcb078fc1456b07dfdc1cf90c44e3e1
a1c3759c68f6d088d5e6fc8bef76e739





b3f3a8609446e2af6513baef5f7ba8fa
ed49e9c4c6a3f1169175a50ce27cc183

ed648db5c82e86772c1c3f76fb2c42e2


dc9182ae415b862068cc47750965de96

cf3c57ea59d001362293960a88f112b1

e84fb53e9aa65d7b24f6da448ef66c4f
a63caf3220978f6f673292f79a493e7a

b5e720842c2c1b3fb3c71309fa4434bc


e86e11d6310bd20eade2c7de37b6595a
c6b56eade24f7a81fb4e3e7f92c27b47






cacd5b8ed67e9e94a289de0ac25a8ea5
bfc7954327958deb552352afc9e337c8



a7dc01d775e22a34c4caf0cc9bc90273
bba63cbc3e68d767fe13e3e609f0d7b7

b3a19fd58ad492f377431c4000e947b1
bcfa63831c099ac7ad457e7b30471635



e45f7c307e67b13f457504ce1e93adc4
ca5b0b284a41f95cc7684e3e791e5e43
a9b822be6e43936173779e3641cccf37



ccd558b0a457902007b10ffe8e43626a
bc00cf32fdfe9944f7b4b92ad9596035
acaa67e0837a1c506b4ee33778c3d34d


b486e4c64373befe35c0dd65be80e1b1
d79f1283e52a2c0ccf82c9516153f8cf
b8e635be69e7ef8295f38fe9147a9d9d

fdfbc6c05521ff446e9d20449213284a


c022301d3e96a8b59a0370c57d285f34



afc11567288caf51db32557dfe1cf238



db65748051ef42ca5159d4f5f675cd66
ee060398a75b39b2a03c9add1f7978e1

b758b0b14bbc1e1ad613914b959275aa
cf5f59646b9ee163bbb9c5bf55e7e39e
a81d5716b461240ff91eb40eb99a0768
a2469be912c6cb69aecde7af497c5524,


aa32806dcb51596555489e93f4c4340f

db1c55573becfbee3903ae573224b179



c942b22440dfbbf083726d8b843ab2bb

e46289d37fa76918074ed8119f65a5f2






b26ea9cb75c69197895f8e9c9aa819c8
edaef13be77ad2a413af6bb4498b1ccd


ed49e9c4c6a3f1169175a50ce27cc183






c40990246bd03c18ef187a6bd514d464



f805d549425c7328c40729b84606ba07


cf5f59646b9ee163bbb9c5bf55e7e39e
ee079757ecd9b7c73e31920c6cd4067f
f35b403a015f03b9f4a7abad0ba7c291


f44422bd25e349884ea5b151708d4168


db1c55573becfbee3903ae573224b179

a1c3759c68f6d088d5e6fc8bef76e739


a4c2d4207408f14b4ffdacae20ca08f7

d192e8eca0b215e58fc76ed5e7cdb8d3






,



faf9d9dd07e9be093311e0eba21182b1

b896fec03441019f564909b07db0b3d6
f5d256fda01b89f2952ac64ac7cbe846
edaef13be77ad2a413af6bb4498b1ccd


a4c2d4207408f14b4ffdacae20ca08f7
f84846d7ac6bc61b326ad880cbf28acf


d03a1d460db0681e17d2fee4cd66335d



de6707573b047f0d607c2bfac8a8b0fc


d63fbd5f0d6ac07328e33358cb058a88

b51aec0cfcf0eecbaf583f913cc18f3b
e280f8f908ed6232649d7275c4f3c263

b5e720842c2c1b3fb3c71309fa4434bc



faf9d9dd07e9be093311e0eba21182b1



ab3d2eb28cbc976e2fafdad1b7ebd482

f7ad1f228ae3049f9d6d2b6d6c53c035




,,,,a0a5dff287d5f43498a81934df34f6e1


dd746d62539d3641ccee848781413762
d5835f133f559870443026042f3315a2

bf3bab9f98cf513bd09ffe6e773ab8d8




c3bde1bd54b7318ec7fd8ba0a5ef2799







a60305ce45d1bed9759420c05bb74200



d192e8eca0b215e58fc76ed5e7cdb8d3




df81af52d06780671644c62b3545e350
e78d8dbdbec094b48888c2a9bdf3db31

bb0692d4a7e038da83171b7279c9cdba








cc83b05076086e5e31858b1dfe2860c3


f068075227855de709cd7bea20b94712









ae5c3471f12387be2adebc7922067ff0




a1385619e5caccf4b3b83d393c81642d
ee060398a75b39b2a03c9add1f7978e1
dd3d6e9ada3c64b33eb230ecd0c570c4



abd6ebbf2a002a1b194630daecdbd2d4

b2f82c344a844898d26953f64df2ff83

faf9d9dd07e9be093311e0eba21182b1

a7dc01d775e22a34c4caf0cc9bc90273
e049fd711a582a90c5b9e73f4464d4f1

d3af2d890b6c1331abc0cf1bab1b66e4

a74029be428eb715469c37c5c087051d




f20c1b43cb9ce21b83778e28fa70801e



b5e720842c2c1b3fb3c71309fa4434bc
b2ef9941ccb75ad33ad5902e97f307bd
e88457aa452f777eef85fbbfd95ec85e
d149059cefe1e067a06889abaf6ae36d
e2026b3693a8d0d1ac381b2541831b9f

dc71ab56ab5e0075a7edf3b6af248022






fbdd29796d1d0fadeb05bd0e264f5df3


f732cd2547622a112d7cf896c67a9ab2


a04fc54f775f2776321673d69c80d4ea
,




eb158e420f553716a872ad93cc0ab002



b2ef9941ccb75ad33ad5902e97f307bd

a74029be428eb715469c37c5c087051d
d7ddb097e2138e1c72563b6ccb2af019





f10a8e559deb772c2b8b8f547e38b21e
dc71ab56ab5e0075a7edf3b6af248022






ce692dd8417c8e34746156ce9b799e91
cc634c526fe4da575eac6077a3071644





e0ca8ccc957022a3c7ea3ceeb513ca23
e23a131210f3a730d088fe9524ccfba0
b486e4c64373befe35c0dd65be80e1b1
bc85909fa786f72bb236a56a8f17acd6


ebefb82e4d8e381f2cb9a635b45dd9af







b5f2705bc4649da0b7af267533fe7fdf



b26b42fa48cc68886ca0018d9893fcb5

eeb94f7eb6bace26301d5aa1ebcab163
a04fc54f775f2776321673d69c80d4ea





e280f8f908ed6232649d7275c4f3c263
e68f2f8b35cc47352ce750bdc5f40504

e23a131210f3a730d088fe9524ccfba0
f805d549425c7328c40729b84606ba07
,



a9df6c8208e4ca184b689d42b99ea1cb


afe72d2ef971f6330b38c9f1ab219910
f38a2fe99994feb158aa4a165cca1e64



b33ad589799efe4f0ae9a5bb57c2c734



 

 

 

原文:https://www.cnblogs.com/xmy20051643/p/11100548.html
级别: 白丁
UID: 130392
积分:5 加为好友
威望: 1 精华: 0
主题:1 回复:2
注册时间:2019-06-28
在线时长:0
2#   发表于:2019-06-28 11:01:03  IP:114.137.*.*
想学习的可以扒一下代码
1 共1页