Алгоритм для реализации SQL-подобной группы с функциями агрегирования над коллекцией?

#php #javascript #python #algorithm #collections

#php #javascript #python #алгоритм #Коллекции

Вопрос:

Допустим, у вас есть массив, подобный этому:

 [
  {'id' : 1, 'closed' : 1 },
  {'id' : 2, 'closed' : 1 },
  {'id' : 5, 'closed' : 1 },
  {'id' : 7, 'closed' : 0 },
  {'id' : 8, 'closed' : 0 },
  {'id' : 9, 'closed' : 1 }
]
  

Я хотел бы обобщить этот набор данных (не используя SQL!) и получить min и max id для каждой группы, определяемой изменением строки 'closed' . В результате получается такой результат:

 [
  {'id__min' : 1, 'id__max' : 5, 'closed' : 1},
  {'id__min' : 7, 'id__max' : 8, 'closed' : 0},
  {'id__min' : 9, 'id__max' : 9, 'closed' : 1}
]
  

Это всего лишь пример того, что я хотел бы сделать. Я хочу реализовать что-то похожее на то, что itertools.groupby предоставляет python, но немного более всеобъемлющее. (Хотел бы определить свои собственные функции агрегирования).

Я ищу указатели, псевдокод и даже любой код PHP, Python или Javascript, если это возможно.

Спасибо!

Ответ №1:

key Аргумент в itertools.groupby() позволяет вам передать вашу собственную функцию агрегирования.

Комментарии:

1. Я знаю, я ищу общий способ реализовать это на другом языке (PHP прямо сейчас).

2. Документация предоставляет эквивалент функции в коде более низкого уровня. Не стесняйтесь конвертировать.

Ответ №2:

Код Ruby:

 def summarise array_of_hashes
    #first sort the list by id
    arr = array_of_hashes.sort {|a, b| a['id'] <=> b['id'] }
    #create a hash with id_min and id_max set to the id of the first
    #array element and closed to the closed of the first array element
    hash = {}
    hash['id_min'] = hash['id_max'] = arr[0]['id']
    hash['closed'] = arr[0]['closed']
    #prepare an output array
    output = []
    #iterate over the array elements
    arr.each do |el|
        if el['closed'] == hash['closed']
            #update id_max while the id value is the same
            hash['id_max'] = el['id']
        else #once it is different
            output.push hash #add the hash to the output array
            hash = {} #create a new hash in place of the old one
            #and initiate its keys to the appropriate values
            hash['id_min'] = hash['id_max'] = el['id']
            hash['closed'] = el['closed']
        end
    end
    output.push hash #make sure the final hash is added to the output array
    #return the output array
    output
end
  

Обобщенная версия:

 def summarise data, condition, group_func
    #store the first hash in a variable to compare t
    pivot = data[0]
    to_group = []
    output = []
    #iterate through array
    data.each do |datum|
        #if the comparison of this datum to the pivot datum fits the condition
        if condition.call(pivot, datum)
            #add this datum to the to_group list
            to_group.push datum
        else #once the condition no longer matches
            #apply the aggregating function to the list to group and add it to the output array
            output.push group_func.call(to_group)
            #reset the to_group list and add this element to it
            to_group = [datum]
            #set the pivot to this element
            pivot = datum
        end
    end
    #make sure the final list to group are grouped and added to the output list
    output.push group_func.call(to_group)
    #return the output list
    output
end
  

Затем следующий код будет работать для вашего примера:

 my_condition = lambda do |a, b|
    b['closed'] == a['closed']
end

my_group_func = lambda do |to_group|
    {
        'id_min' => to_group[0]['id'],
        'id_max' => to_group[to_group.length-1]['id'],
        'closed' => to_group[0]['closed']
    }
end

summarise(my_array.sort {|a, b| a['id'] <=> b['id']}, my_condition, my_group_func)
  

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

Комментарии:

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

2. Я отредактировал свой ответ, чтобы дать обобщенную версию. Я надеюсь, что кода комментариев достаточно, чтобы разрешить перенос на другие языки.

Ответ №3:

PHP-версия кода Ruby с немного более общим наименованием и порядком обработки идентификаторов:

 $input = array(
    array('id' => 3, 'closed' => 1),
    array('id' => 2, 'closed' => 1),
    array('id' => 5, 'closed' => 1),
    array('id' => 7, 'closed' => 0),
    array('id' => 8, 'closed' => 0),
    array('id' => 9, 'closed' => 1)
);

$output = min_max_group($input, 'id', 'closed');
echo '<pre>'; print_r($output); echo '</pre>';

function min_max_group($array, $name, $group_by)
{
    $output = array();

    $tmp[$name.'__max'] = $tmp[$name.'__min'] =  $array[0][$name];
    $tmp[$group_by] = $array[0][$group_by];

    foreach($array as $value)
    {
        if($value[$group_by] == $tmp[$group_by])
        {
            if($value[$name] < $tmp[$name.'__min']) { $tmp[$name.'__min'] = $value[$name]; }
            if($value[$name] > $tmp[$name.'__max']) { $tmp[$name.'__max'] = $value[$name]; }
        }
        else
        {
            $output[] = $tmp;

            $tmp[$name.'__max'] = $tmp[$name.'__min'] = $value[$name];
            $tmp[$group_by] = $value[$group_by];

            if($value[$name] < $tmp[$name.'__min']) { $tmp[$name.'__min'] = $value[$name]; }
            if($value[$name] > $tmp[$name.'__max']) { $tmp[$name.'__max'] = $value[$name]; }
        }
    }

    $output[] = $tmp;

    return $output;
}
  

Комментарии:

1. Да, я знаю, как делать это с конкретными данными, я ищу способы обобщить это.

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

3. Точно, мне нужен общий способ реализации обратных вызовов, который я мог бы передать функции для извлечения min or max или чего угодно по своему желанию из значений группы. Кроме того, способ определения функции сравнения для определения новой группы (возможно, указание более одного поля и т.д.). 🙂

4. Определяемые пользователем обратные вызовы просты в PHP: php.net/manual/en/function.call-user-func-array.php . Затем вы могли бы просто передать имена функций / параметры и т.д., а другие функции обрабатывать тесты группировки и т.д. По моему собственному мнению, хотя, как только вы определили несколько из них, вы могли бы с таким же успехом просто вызвать другие функции напрямую, а не пихать их через вышеупомянутое (что в основном является просто циклом массива)…

Ответ №4:

Возможно, я неправильно понимаю проблему, но разве это не просто стандартная проблема сопоставления / уменьшения?

Комментарии:

1. С таким же успехом вы могли бы сказать: «Почему бы просто не использовать обработку данных?»