2015-02-17 5 views
1

У меня есть две очень похожие петли, и эти два содержат внутренний цикл, который очень похож на третий цикл (eh ... :)). Иллюстрированный с кодом выглядит близко к этому:Дедублирующий код в несколько разных функциях

# First function 
def fmeasure_kfold1(array, nfolds): 
    ret = [] 

    # Kfold1 and kfold2 both have this outer loop 
    for train_index, test_index in KFold(len(array), nfolds): 
     correlation = analyze(array[train_index]) 

     for build in array[test_index]: # <- All functions have this loop 

      # Retrieved tests is calculated inside the build loop in kfold1 
      retrieved_tests = get_tests(set(build['modules']), correlation) 

      relevant_tests = set(build['tests']) 
      fval = calc_f(relevant_tests, retrieved_tests) 
      if fval is not None: 
       ret.append(fval) 

    return ret 

# Second function 
def fmeasure_kfold2(array, nfolds): 
    ret = [] 

    # Kfold1 and kfold2 both have this outer loop 
    for train_index, test_index in KFold(len(array), nfolds): 
     correlation = analyze(array[train_index]) 

     # Retrieved tests is calculated outside the build loop in kfold2 
     retrieved_tests = _sum_tests(correlation) 

     for build in array[test_index]: # <- All functions have this loop 

      relevant_tests = set(build['tests']) 
      fval = calc_f(relevant_tests, retrieved_tests) 
      if fval is not None: 
       ret.append(fval) 

    return ret 

# Third function 
def fmeasure_all(array): 
    ret = [] 
    for build in array: # <- All functions have this loop 

     relevant = set(build['tests']) 
     fval = calc_f2(relevant) # <- Instead of calc_f, I call calc_f2 
     if fval is not None: 
      ret.append(fval) 

    return ret 

Первые две функции отличаются только в порядке, и в какое время, они вычисляют retrieved_tests. Третья функция отличается от внутренней петли первых двух функций тем, что она вызывает calc_f2 и не использует retrieved_tests.

В действительности код более сложный, но пока дублирование меня раздражало, я решил, что смогу жить с ним. Тем не менее, в последнее время я вносил в него изменения, и это раздражает, чтобы изменить его в двух или трех местах одновременно.

Есть ли хороший способ объединить дублированный код? Единственный способ, с помощью которого я мог думать, заключался в том, чтобы вводить классы, которые вводят множество шаблонов, и я хотел бы сохранить функции как чистые функции, если это возможно.


Редактировать

Это содержимое calc_f и calc_f2:

def calc_f(relevant, retrieved): 
    """Calculate the F-measure given relevant and retrieved tests.""" 
    recall = len(relevant & retrieved)/len(relevant) 
    prec = len(relevant & retrieved)/len(retrieved) 
    fmeasure = f_measure(recall, prec) 

    return (fmeasure, recall, prec) 


def calc_f2(relevant, nbr_tests=1000): 
    """Calculate the F-measure given relevant tests.""" 
    recall = 1 
    prec = len(relevant)/nbr_tests 
    fmeasure = f_measure(recall, prec) 

    return (fmeasure, recall, prec) 

f_measure вычисляет harmonic mean точности и отзывом.

В принципе, calc_f2 берет много ярлыков, поскольку требуемые тесты не требуются.

+0

Как 'calc_f' и' calc_f2' отличаются? –

+0

Я отредактировал вопрос, чтобы добавить информацию об этих функциях. – imolit

+0

Изначально это 'calc_f2' принимает два аргумента (и использует второй аргумент), но' fmeasure_all' вызывает его только одним аргументом. Я полагаю, что это следствие «упрощения» кода. –

ответ

0

Имеет общую функцию, которая принимает дополнительный параметр, который контролирует, где вычислять retrieved_tests.

например.

def fmeasure_kfold_generic(array, nfolds, mode): 
    ret = [] 

    # Kfold1 and kfold2 both have this outer loop 
    for train_index, test_index in KFold(len(array), nfolds): 
     correlation = analyze(array[train_index]) 

     # Retrieved tests is calculated outside the build loop in kfold2 
     if mode==2: 
      retrieved_tests = _sum_tests(correlation) 

     for build in array[test_index]: # <- All functions have this loop 
      # Retrieved tests is calculated inside the build loop in kfold1 
      if mode==1: 
       retrieved_tests = get_tests(set(build['modules']), correlation) 

      relevant_tests = set(build['tests']) 
      fval = calc_f(relevant_tests, retrieved_tests) 
      if fval is not None: 
       ret.append(fval) 
0

Один из способов заключается в написании внутренние циклы друг как функцию, а затем иметь внешнюю петлю в качестве отдельной функции, которая принимает другие в качестве аргумента. Это нечто близкое к тому, что делается при сортировке функций (которые получают функцию, которая должна использоваться для сравнения двух элементов).

Конечно, сложная часть заключается в том, чтобы найти то, что является общей частью между всеми функциями, что не всегда просто.

0

Типичное решение состоит в том, чтобы идентифицировать части алгоритма и использовать Template method design pattern, где в подклассах будут реализованы различные этапы. Я вообще не понимаю ваш код, но я предполагаю, что будут такие методы, как makeGlobalRetrievedTests() и makeIndividualRetrievedTests()?

0

Я бы подошел к проблеме наизнанку: за счет исключения самой внутренней петли. Это хорошо работает с «функциональным» стилем (а также «функциональным программированием»). Мне кажется, что если вы обобщите fmeasure_all, вы можете реализовать все три функции в терминах этого.Что-то вроде

def fmeasure(builds, calcFn, retrieveFn): 
    ret = [] 
    for build in array: 
     relevant = set(build['tests']) 
     fval = calcFn(relevant, retrieveFn(build)) 
     if fval is not None: 
      ret.append(fval) 

    return ret 

Это позволяет определить:

def fmeasure_kfold1(array, nfolds): 
    ret = [] 

    # Kfold1 and kfold2 both have this outer loop 
    for train_index, test_index in KFold(len(array), nfolds): 
     correlation = analyze(array[train_index]) 

     ret += fmeasure(array[test_index], calc_f, 
         lambda build: get_tests(set(build['modules']), correlation)) 

    return ret 


def fmeasure_kfold2(array, nfolds): 
    ret = [] 

    # Kfold1 and kfold2 both have this outer loop 
    for train_index, test_index in KFold(len(array), nfolds): 
     correlation = analyze(array[train_index]) 

     # Retrieved tests is calculated outside the build loop in kfold2 
     retrieved_tests = _sum_tests(correlation) 

     ret += fmeasure(array[test_index], calc_f, lambda _: retrieved_tests) 

    return ret 


def fmeasure_all(array): 
    return fmeasure(array, 
        lambda relevant, _: calc_f2(relevant), 
        lambda x: x) 

К настоящему времени, fmeasure_kfold1 и fmeasure_kfold2 выглядят ужасно похожи. Они отличаются, в основном, как fmeasure называется, так что мы можем реализовать общую fmeasure_kfoldn функцию, которая централизует итерации и сбора результатов:

def fmeasure_kfoldn(array, nfolds, callable): 
    ret = [] 
    for train_index, test_index in KFold(len(array), nfolds): 
     correlation = analyze(array[train_index]) 
     ret += callable(array[test_index], correlation) 
    return ret 

Это позволяет определить fmeasure_kfold1 и fmeasure_kfold2 очень легко:

def fmeasure_kfold1(array, nfolds): 
    def measure(builds, correlation): 
     return fmeasure(builds, calc_f, lambda build: get_tests(set(build['modules']), correlation)) 
    return fmeasure_kfoldn(array, nfolds, measure) 


def fmeasure_kfold2(array, nfolds): 
    def measure(builds, correlation): 
     retrieved_tests = _sum_tests(correlation) 
     return fmeasure(builds, calc_f, lambda _: retrieved_tests) 
    return fmeasure_kfoldn(array, nfolds, measure) 
+0

Спасибо, это выглядит очень близко к тому, что я искал. Я дам ему вихрь и посмотрю, будет ли это работать для меня. – imolit

 Смежные вопросы

  • Нет связанных вопросов^_^