Функция Haskell replaceValues

#haskell

#haskell

Вопрос:

Я хочу написать функцию, которая принимает список элементов l, список индексов i и список замещающих значений v. Функция заменит значения в l, соответствующие индексам в i, на соответствующее значение в v.

Пример: Если l = [1,2,3,4,5,6], i = [0,2] и v = [166,667], то replaceValues l i v == [166,2,667,4,5,6]

Моя функция:

 --Replace the values in list l at indices in i with the
--  corresponding value in v
replaceValues :: [a] -> [Int] -> [a] -> [a]
replaceValues l [] [] = l
replaceValues l i v = x    [head v]    (replaceValues (tail y) shiftedIndices (tail v))
    where
        (x,y) = splitAt (head i) l
        --The indices must be shifted because we are changing the list
        shiftedIndices = map ((-)((head i)   1)) (tail i)
  

Этой функции удается корректно заменить значение в первом индексе в i, но она неправильно помещает все следующие значения. В приведенном выше примере это дало бы результат [166,667,3,4,5,6].

Ответ №1:

Проблема с вашей реализацией заключается в том, что вы не отслеживаете, в каком индексе вы находитесь в данный момент.

Прежде всего, вам лучше рассмотреть возможность использования раздельных аргументов [(Int,a)] вместо [Int] и [a] , чтобы гарантировать, что «списки» равны по длине.

Альтернативная реализация заключается в следующем:

 import Data.Maybe(fromMaybe)
import qualified Data.IntMap as M

replaceValues :: [a] -> [(Int,a)] -> [a]
replaceValues as rs = map rep $ zip [0..] as
  where
    rsM = M.fromList rs

    rep (i,a) = fromMaybe a $ M.lookup i rsM
  

Что здесь происходит:

  • Помечайте каждое значение его индексом

  • Посмотрите, есть ли заменяющее значение для этого индекса: если есть, используйте его; в противном случае используйте исходное значение.

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

1. Это очень краткая реализация. Однако я получаю ошибку типа в rs функции rep (i, a)

2. @kienjakenobi: упс, забыл дополнительный импорт! (т. Е. из maybe) и он должен использовать rsM , а не rs :s Исправил это сейчас.

3. Большое вам спасибо. Это действительно работает. Я подумаю об этом больше, чтобы убедиться, что я полностью это понимаю.

4. Это приведет к сбою, если вы будете склонны использовать замену для больших наборов данных, это также будет потреблять больше памяти. На 1 МЛН элементов она, вероятно, умрет 🙂

5. @jdevelop: Верно, но я подумал, что здесь это будет не так 😉 В этом случае barsoap реализация будет работать, предполагая, что оба списка созданы лениво (хотя мне было бы интересно, что вы делали, что вы хотели выполнить замену на основе индекса с таким большим количеством элементов, чтобы заменить эту память, является проблемой).

Ответ №2:

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

 l = [1,2,3,4,5,6]
r = [(0,166),(2,667)]
  

… вы можете использовать zip для преобразования ваших двух списков в этот формат. Затем я собираюсь оговорить, что этот список отсортирован по первому элементу кортежа ( sortBy ), и что в нем нет повторяющихся индексов ( nubBy ). Остальное — простая рекурсия с заменой по мере выполнения, с линейной сложностью и максимальной ленивостью:

 replaceValues :: [a] -> [(Int, a)] -> [a]
replaceValues xs rs = f 0 xs rs
  where
    f _ xs [] = xs
    f _ [] _  = []
    f n (x:xs) is@((i,r):is')
        | n < i  = x:f (n 1) xs is
        | n == i = r:f (n 1) xs is'
        | otherwise = error "Can't happen"
  

Однако остерегайтесь кода, я только доказал его правильность, на самом деле не пробовал его.

Использование карты, конечно, тоже работает, но тогда вы имеете дело со сложностью O (m log m n log m) (построение карты n-кратный поиск) вместо O (n), или, принимая во внимание сортировку, O (n m log m), а также теряете возможность лениться, если ваш список уже отсортирован, и во время обхода вы постепенно собираете мусор для замен.

nubBy из библиотеки имеет квадратичную сложность, но поскольку список отсортирован, с ним также можно справиться за линейное время, просто замените вызов error рекурсивным вызовом, отбрасывающим лишние (i,r) s.

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

1. Большое вам спасибо за это альтернативное решение. Сейчас должен спать, но я оценю это и тоже подумаю об этом.

Ответ №3:

как говорилось ранее, используйте кортежи, но не забывайте о сопоставлении с шаблоном. Или используйте Map, если вы не имеете дело с большими коллекциями

 replaceValues :: [a] -> [Int] -> [a] ->[a]
replaceValues a b i = map fst $ f (zip a [0..]) (zip b i)
    where
        f [] _ = []
        f xs [] = xs
        f ((x,i):xs) s2@((j,y):ys) | i == j = (y,i) : f xs ys
                                   | otherwise = (x,i) : f xs s2