2014-11-11 2 views
2

Если данный список кортежей, представляющих диапазоны, как это:Если задан список кортежей, представляющих диапазоны, как вы можете объединить непрерывные диапазоны?

[(0,10),(10,100),(1000,5000)] 

Я хотел бы объединить кортежи, которые представляют собой смежные диапазоны, поэтому результат таков:

[(0,100),(1000,5000)] 

Любые элегантные решения?

Вот мой

mergeRanges :: [(Int, Int)] -> [(Int, Int)] 
mergeRanges xs = foldr f [] (sort xs) 
    where f [email protected](x,y) [email protected]((a,b):ys) = 
      if y == a 
      then (x,b):ys 
      else new:acc 
     f x acc = x:acc 

РЕДАКТИРОВАНИЕ: Диапазоны не перекрываются

+0

Я бы подумал, что mergeRanges [(0, 10), (10, 100), (10, 1000), (1000, 5000)] должен давать [(0, 5000)], но с вашим решением он дает [ (0, 100), (10, 5000)], поскольку каждый диапазон объединяется только с одним другим диапазоном. –

ответ

2

Если это не шаблон, который показывает вверх более часто в вашей программе, я бы просто пойти на прямой рекурсии (непроверенный код следует!):

mergeRanges (lo1,hi1) : (lo2,hi2) : rest) 
    | hi1 == lo2 = mergeRanges ((lo1,hi2) : rest) 
      -- or (lo1,hi2) : mergeRanges rest, to merge only adjacent ranges 
mergeRanges (interval:rest) = interval : mergeRanges rest 
mergeRanges [] = [] 

(где вы могли бы оптимизировать немного с помощью @ -patterns за счет помех).

Но если вы действительно хотите, вы можете использовать следующую вспомогательную функцию

merge :: (a -> a -> Maybe a) -> [a] -> [a] 
merge f [] = [] 
merge f [x] = [x] 
merge f (x:y:xs) = case f x y of 
    Nothing -> x : merge f (y:xs) 
    Just z -> merge (z:xs) -- or z : merge xs 

и дать в качестве первого аргумента

merge2Ranges (lo1, hi1) (lo2, hi2) 
    | hi1 == lo2 = Just (lo1, hi2) 
    | otherwise = Nothing 

Я сомневаюсь, что merge находится в библиотеке где-то, так как это довольно специфические для данной проблемы.

0

Ну, я думаю, что лучшие решения в этом пространстве, вероятно, будет включать в себя специализированные структуры данных, которые поддерживают инварианта в вопросе. В Java-land библиотека Guava имеет RangeSet, что делает именно это.

Это не решение вашей проблемы сразу, но когда-то я играл с этим простым (слишком простой) реализации «исторических ценностей» как своего рода бинарного дерева поиска:

-- | A value that changes over time at discrete moments. @[email protected] is the timeline type, 
-- @[email protected] is the value type. 
data RangeMap t a = Leaf a 
       -- Invariant: all @[email protected] values in the left branch must be less than 
       -- the one in the parent. 
       | Split t (RangeMap a) (RangeMap a) 

valueAt :: RangeMap t a -> t -> a 
valueAt _ (Leaf a) = a 
valueAt t (Split t' before since) 
    | t < t' = get t before 
    | otherwise = get t since 

Идея вот что Split t beforeT sinceT делит временную шкалу на две ветви, одну для значений, которые были проведены до t, и вторую для тех, которые удерживались с t.

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

example :: RangeMap Int Bool 
example = Split 1000 (Split 100 (Split 0 (Leaf False) (Leaf False)) 
           (Leaf False)) 
        (Split 5000 (Leaf True) (Leaf False)) 

Есть несколько аккуратных вещей об этом, по сравнению с [(since, until, value)] представления, которое я использовал в прошлое для аналогичных приложений:

  1. Древовидное представление не позволяет иметь конфликтующие a значения за тот же диапазон времени. RangeMap - это настоящая функция от t до a.
  2. Древовидное представление гарантирует, что для каждого присваивается a. Опять же, RangeMap является истинной функцией от t до a.
  3. Поскольку это дерево, а не список, оно поддерживает операции входа в журнал.

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

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