2015-08-25 6 views
8

У меня есть класс Task, который выглядит следующим образом (с использованием Java 8 Time API).Сочетание диапазонов дат перекрытия - Java

class Task { 
    LocalDateTime start; 
    LocalDateTime end; 
    Set<String> actionItems; 
} 

Я два отсортирован (сначала начала, а затем к концу) списки, содержащие такие экземпляры задач, позволяет говорить List<Task> tasksList1 и List<Task> tasksList2. Я хочу объединить перекрывающиеся задачи (путем разбиения задач, если это необходимо, и добавления элементов action из других задач, которые перекрываются в один новый объект задачи).

Например, предположим, что у меня есть задание T1, которое начинается с 01/01/2015 и заканчивается 01/31/2015, которое содержит элементы A и B. Действия пользователя. Затем пользователь создает новую задачу T2, которая начинается с 01/15/2015 и заканчивается 02/15/2015 и добавляет в него элемент действия C. Когда я объединяюсь, я должен получить три объекта Task следующим образом.

  • Задача Х - от 01/01/2015 до 01/15/2015, содержит элементы действий A, B
  • Задача Y - от 01/15/2015 до 01/31/2015, содержит элементы A , B и C
  • задача Z - от 01/31/2015 до 02/15/2015, содержат пункт с

для визуализации, если мой объект задачи из двух списков выглядит следующим образом в сроках :

> [-----]  [-----]   [----]   [-----------------] 
>  [-----]   [---------------]   [------] 

Затем результирующий список задач будет содержать задачи следующим образом.

> [--][-][--] [-----] [-----][----][--]  [-][------][-----]` 

Перекрытие задачи должны иметь actionItems в сочетании с обеих задач, которые перекрывают друг друга на период, в котором они перекрывают друг друга.

Каков наиболее эффективный способ справиться с этим? На данный момент я тестирую различные варианты с помощью PeekableIterator, но пока не повезло. Любые решения, использующие JodaTime вместо API Java 8, также приветствуются.

ответ

10

Прежде всего, если вам нужны только даты (не важно время), вместо этого лучше использовать LocalDate. Во-вторых, я предполагаю, что у вас есть конструктор задач. Таким образом, я использовал следующий Task объект:

static class Task { 
    LocalDate start; 
    LocalDate end; 
    Set<String> actionItems; 

    public Task(LocalDate start, LocalDate end, 
      Collection<String> actionItems) { 
     this.start = start; 
     this.end = end; 
     this.actionItems = new HashSet<>(actionItems); 
    } 

    @Override 
    public String toString() { 
     return start + ".." + end + ": "+actionItems; 
    } 
} 

Вот решение более общей задачи, которая просто объединяет все задачи в данной коллекции в соответствии с вашими правилами (сбор ввода не обязательно отсортирован):

public static List<Task> convert(Collection<Task> input) { 
    NavigableMap<LocalDate, Set<String>> map = new TreeMap<>(); 
    map.put(LocalDate.MIN, new HashSet<>()); 

    for (Task task : input) { 
     if (!map.containsKey(task.start)) { 
      map.put(task.start, new HashSet<>(map.lowerEntry(task.start).getValue())); 
     } 
     if (!map.containsKey(task.end)) { 
      map.put(task.end, new HashSet<>(map.lowerEntry(task.end).getValue())); 
     } 
     for (Set<String> set : map.subMap(task.start, task.end).values()) { 
      set.addAll(task.actionItems); 
     } 
    } 
    List<Task> result = new ArrayList<>(); 
    LocalDate prev = null; 
    Set<String> prevValues = Collections.emptySet(); 
    for (Entry<LocalDate, Set<String>> entry : map.entrySet()) { 
     if (!prevValues.isEmpty()) { 
      result.add(new Task(prev, entry.getKey(), prevValues)); 
     } 
     prev = entry.getKey(); 
     prevValues = entry.getValue(); 
    } 
    return result; 
} 

Основная задача - NavigableMap, где каждая клавиша является началом следующего периода времени, а значение представляет собой совокупность действий за период от заданного старта до следующего ключа (пустые значения соответствуют периодам без действий). После добавления новой задачи существующие записи обновляются соответствующим образом. Пример использования:

List<Task> res = convert(Arrays.asList(
    new Task(LocalDate.parse("2015-01-01"), LocalDate.parse("2015-01-31"), 
     Arrays.asList("A", "B")), 
    new Task(LocalDate.parse("2014-01-01"), LocalDate.parse("2014-01-31"), 
     Arrays.asList("A", "B")), 
    new Task(LocalDate.parse("2015-01-15"), LocalDate.parse("2015-02-15"), 
     Arrays.asList("C")))); 
res.stream().forEach(System.out::println); 

Выход:

2014-01-01..2014-01-31: [A, B] 
2015-01-01..2015-01-15: [A, B] 
2015-01-15..2015-01-31: [A, B, C] 
2015-01-31..2015-02-15: [C] 
+0

Спасибо Тагир! Вы рок :) Извините, я не был ясно в своем вопросе, что время тоже важно для меня. Для простоты в этом примере я оставил это. Я считаю, что решение должно работать и для LocalDateTime? Еще одна вещь, которую следует отметить, заключается в том, что результат вашего алгоритма несколько отличается от того, что я ожидаю.Как слушайте в трех точках, я ожидаю, что три Задачи будут выводиться сначала с 01/01 по 01/15 с A, B, с 01/15 до 01/31 с A, B, C и третьим с 01/31 до 02/15 с C. На выходе не будет перекрытий. Я пытаюсь изменить ваше решение, чтобы справиться с этим ... –

+0

@YohanLiyanage, да, для 'LocalDateTime' он будет работать одинаково, просто замените имя класса везде. Я добавил в примере использования одну непрозрачную задачу (2014 года), чтобы проверить, правильно ли она обрабатывается. Удалите его, и выход будет таким же. –

+0

О, я пропустил, что это был 2014 год :). Еще раз спасибо, и это работает как шарм –