最近公司项目上用到频繁项发现算法,于是就用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   LIST</br>
* T0   1,2,3,4</br>
* T1   1</br>
* T2   3,4</br>
* T3   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 |
|