26204695创建于 4月16日历史提交
import re
import pickle
import time
import enum
import string
import numpy as np
import pandas as pd
import seaborn as sns

import nltk
from nltk.stem import WordNetLemmatizer
from nltk.corpus import stopwords

pd.set_option('display.max_rows', 200)
pd.set_option('display.max_columns', 200)


def savefile(data, path):
    with open(path, 'wb') as f:
        pickle.dump(data, f)
    
    
def readfile(path):
    f = open(path, 'rb')
    data = pickle.load(f)
    f.close()
    return data



def reduce_mem_usage(df, verbose=True):
    start_mem = df.memory_usage().sum() / 1024**2
    numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']

    for col in df.columns:
        col_type = df[col].dtypes
        if col_type in numerics:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)
                    
    end_mem = df.memory_usage().sum() / 1024**2
    print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
    print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
    return df


def recog_commit(line, index):
    ret = re.search(r'commit ([\w+]*)', line)
    if ret:
        return ret.group(1)[:7]
    else:
        print('commit', index, line)
        return None
  
  
def recog_author(line):
    ret = re.search(r'Author: .* <(.*)>', line)
    if ret:
        return ret.group(1)
    else:
        print(line)
        return None
    return None
  

def recog_time(line):
    line = line[8:-7]
    try:
        struct_time = time.strptime(line,"%a %b %d %H:%M:%S %Y")
    except:
        return 0
    return '{:04}{:02}{:02}'.format(struct_time.tm_year, struct_time.tm_mon, struct_time.tm_mday)  


def recog_mess(lines, index):
    ret = []
    lens = len(lines)
    while index < lens and \
               not lines[index].startswith('commit') and \
               not lines[index].startswith('diff --git') and \
               not lines[index].startswith('@@ '):
        line = lines[index].strip()
        if len(line) >= 1:                                               # commit 信息
            ret.append(lines[index])
        index += 1
    return ' '.join(ret), index


def recog_filepath(line):
    filepath = line.split(' ')[-1].strip()[2:]                   # 文件路径
    return filepath


def recog_hunk(line):
    funcname = line.split('@@')[-1].strip()   # hunk header-函数名
    return funcname


def recog_code(lines, index):
    codes, addcodes, delcodes = '', '', ''
    addline, delline = 0, 0
    lens = len(lines)
    while index < lens and \
               not lines[index].startswith('commit') and \
               not lines[index].startswith('diff --git') and \
               not lines[index].startswith('@@ '):
        line = lines[index].strip()
        if len(line) >= 1:
            codes+= line+' '                   # 上下文代码行
            if line.startswith('+'):
                addcodes+= line[2: ]+' '      # 增加代码行
                addline += 1
            elif line.startswith('-'):
                delcodes+= line[2: ]+' '       # 删除代码行
                delline += 1
        index += 1
    return codes, addcodes, delcodes, addline, delline, index


def get_repo_total_data(lines, reponame):
    """
    处理文本内容,返回数据
    reponame —— 仓库名称
    commit —— commit sha 前7位
    author —— 邮箱表示 —— Delete
    date —— YearMonthDay共8位字符串表示
    mess —— ' '.join(list(信息))
    filepaths —— ' '.join(list(文件路径))
    funcs —— ' '.join(list(函数名))
    codes —— 总代码(含diff上下5行代码),list(总代码)
    addcodes —— 增加的代码,list(增加代码)
    delcodes —— 减少的代码,list(减少代码)
    addlines —— 总增加代码行数
    dellines —— 总减少代码行数
    """
    index = 0
    lens = len(lines)
    total_data = []
    while index < lens:
        temp = []
        commit = recog_commit(lines[index].strip(), index)
        index += 1
        if lines[index].strip().startswith('Merge'):
            index += 1
        # author = recog_author(lines[index].strip())
        recog_author(lines[index].strip())
        index += 1
        date = recog_time(lines[index])
        index += 1
        mess, index = recog_mess(lines, index)  # mess 是列表
        # files, filepaths, funcs = [], [], []
        filepaths, funcs = [], []
        codes, addcodes, delcodes = [], [], []
        addlines, dellines = 0, 0        
        while index < lens and lines[index].startswith('diff --git'):
            filepath = recog_filepath(lines[index])
            # file, filepath = recog_file(lines[index])
            # files.append(file)
            filepaths.append(filepath)
            index += 1
            while not  lines[index].startswith('index') and \
                     not lines[index].startswith('commit') and \
                     not lines[index].startswith('diff --git') and \
                     not lines[index].startswith('@@ '):
                index += 1
            if lines[index].startswith('index'): index += 1
            if lines[index].startswith('Binary files '): index += 1
            if lines[index].startswith('--- '): index += 1
            if lines[index].startswith('+++ '): index += 1
            if len(lines[index].strip()) == 0: index += 1
            # 不能直接+4,因为有的会多一行"new file mode xxxx" /delete file mode /rename 等等
            # print(index, lines[index])
            while index < lens and lines[index].startswith('@@ -'):
                funcname = recog_hunk(lines[index])
                funcs.append(funcname)
                index += 1
                code, addcode, delcode, addline, delline, index = recog_code(lines, index)
                codes.append(code)
                addcodes.append(addcode)
                delcodes.append(delcode)
                addlines += addline
                dellines  += delline
                # print(index, lines[index])
        # temp = [reponame, commit, author, date, mess, filepaths, funcs, codes, addcodes, delcodes, addlines, dellines]
        temp = [reponame, commit, date, mess, ' '.join(filepaths), ' '.join(funcs), ' '.join(codes), ' '.join(addcodes), ' '.join(delcodes), addlines, dellines]
        total_data.append(temp)
    return total_data


def funcs_preprocess(item):
    keyword = ['auto', 'double', 'int', 'struct', 'break', 'else', 'long', 'switch', 
          'case', 'enum', 'register', 'typedef', 'char', 'extern', 'return', 'union', 
          'const', 'float', 'short', 'unsigned', 'continue', 'for', 'signed', 'void',
          'default', 'goto', 'sizeof', 'volatile', 'do', 'if', 'while', 'static']
    # ret = re.split(r'[^0-9a-zA-Z]', item)     # annotated by me
    ret = re.split(r'\(', item)[0]              # added by me
    ret = re.findall(r'\b[a-zA-Z0-9_]+\b', ret) # added by me
    ret = list(set(ret))
    ret = [item for item in ret if item and item not in keyword]
    return ' '.join(ret)


def string_preprocess(ret):
    ret = ret.replace(r"\r\n", ' ').replace(r"\n", ' ').replace(r"\r", ' ')
    ret = re.sub(r' +', ' ', ret) 
    return ret

#  --------------------  特征处理函数  --------------------
def As_in_B(As: list, B:str):
    """
    获取A列表中元素item有多少出现在字符串B中
    """
    cnt = 0
    for A in As:
        if A in B:
            cnt += 1
    return cnt


def re_search(query: str, item: str):
    """
    正则匹配,有返回List,没有返回None
    """
    return re.findall(query, item)
    

def sns_countplot(x_data, hue_data, other_data, data):
    """
    作图函数,仍有些问题
    """
    g = sns.countplot(x = data[x_data], hue=data[hue_data])
    grouped_values = data.groupby([x_data, hue_data])[other_data].count().reset_index()
    for index,row in grouped_values.iterrows():
        g.text(row[x_data] + 0.2 - 0.4 * (index%2==0), row.cve, row.cve ,color="black",ha="center")
        

def get_files(x):
    return list(set([item.split('/')[-1].strip()  for item in x]))


def max_union(array1, array2):
    array1 = set(array1)
    array2 = set(array2)
    return len(array1 & array2) / max(len(array1), len(array2))


def union_token(*array_list):
    arr = set()
    for array in array_list:
        arr = arr | set(array)
    return arr


def inter_token(*array_list):
    arr = array_list[0]
    for array in array_list:
        arr = arr & array
    return arr


def union_list(*items):
    ret = []
    for item in items:
        ret.extend(item)
    return ret

#  -----------------------  Token 分词  -----------------------
class StateType(enum.IntEnum):
    INITIAL_STATE = 0
    UPPERCASE_STATE = 1
    LOWERCASE_STATE = 2
    NUMBER_STATE = 3
    SPECIAL_STATE = 4
    
    
def line_to_tokens(code):
    """
    采用简单的字符类型的方式将代码进行切分
    upper | upper lower
    upper | number
    upper | special
    lower | upper
    lower | number
    lower | special
    number | upper
    number | lower
    number | special
    special | upper
    special | lower
    special | number
    结果示例:"foo  ,1" -> ["foo", "  ", ",", "1"]
    """
    # normal state transitions that will result in splitting
    normal_transitions = [
      (StateType.UPPERCASE_STATE, StateType.NUMBER_STATE),
      (StateType.UPPERCASE_STATE, StateType.SPECIAL_STATE),
      (StateType.LOWERCASE_STATE, StateType.UPPERCASE_STATE),
      (StateType.LOWERCASE_STATE, StateType.NUMBER_STATE),
      (StateType.LOWERCASE_STATE, StateType.SPECIAL_STATE),
      (StateType.NUMBER_STATE, StateType.UPPERCASE_STATE),
      (StateType.NUMBER_STATE, StateType.LOWERCASE_STATE),
      (StateType.NUMBER_STATE, StateType.SPECIAL_STATE),
      (StateType.SPECIAL_STATE, StateType.UPPERCASE_STATE),
      (StateType.SPECIAL_STATE, StateType.LOWERCASE_STATE),
      (StateType.SPECIAL_STATE, StateType.NUMBER_STATE)]
    # output, state
    tokens = []
    state = StateType.INITIAL_STATE
    next_state = None
    memory = []
    for i, inputchar in enumerate(code):
        if inputchar.isupper():
            next_state = StateType.UPPERCASE_STATE
        elif inputchar.islower():
            next_state = StateType.LOWERCASE_STATE
        elif inputchar.isdigit():
            next_state = StateType.NUMBER_STATE
        else:
            next_state = StateType.SPECIAL_STATE

    # splitting cases
        if (state, next_state) in normal_transitions:
            tokens.append(''.join(memory))   # TheShape  -  The存储起来 Shape
            memory = []
        elif (state, next_state) == (StateType.UPPERCASE_STATE,
                                 StateType.LOWERCASE_STATE) and len(memory) > 1: # VSShape - VS Shape
            tokens.append(''.join(memory[:-1]))  
            memory = [memory[-1]]
        elif (state, next_state) == (StateType.SPECIAL_STATE,
                                 StateType.SPECIAL_STATE):
            if inputchar in [' ', '\t'] and inputchar == code[i-1]:   # 如果是空格或者\t 并且前一个字符也是,那么
                if len(memory) >= 20:   # 如果长度大于20,直接生成一个token
                    tokens.append(''.join(memory))
                    memory = []
            elif inputchar.isspace() or code[i-1].isspace(): # 如果是空格直接停止
                tokens.append(''.join(memory))
                memory = []

    # put inputchar into memory, always
        memory.append(inputchar)
        state = next_state
    if memory:
        tokens.append(''.join(memory))
    return tokens


def to_token(line, useful_token=None, unuseful_token=None):
    final_token = []   # 最后的token序列
    lmtzr = WordNetLemmatizer()
    stopwords_en = stopwords.words('english')
    
    tokens = re.split('[^0-9a-zA-Z]+', line)
    ret = []
    for token in tokens:
        ret.extend(line_to_tokens(token))
    # tokens = line_to_tokens(line)
    if unuseful_token:
        for token in ret:
            token_lower = token.lower()
            if token_lower in unuseful_token:
                ret.remove(token)

    if useful_token:
        for token in ret:
            token = token.lower()
            if token not in useful_token:
                continue
            token = lmtzr.lemmatize(token, 'v')   
            final_token.append(token) 
    else:
        for token in ret:
            token = token.lower()                
            if token in stopwords_en:          
                continue
            token = lmtzr.lemmatize(token, 'v')   
            final_token.append(token) 

    return final_token


def join_list_to_string(lst):
    return ' '.join(lst)