Обрабатывать список Haskell справа налево, сохраняя исходный порядок

Необходимо увеличивать каждый второй элемент, начиная справа в списке Haskell, но сохраняя исходный порядок (например, reverse не является случаем). Например:

 f [1, 2, 3] --  [1, 3, 3]

 f [1, 2, 3, 4] -- [2, 2, 4, 4]

Я пробовал что-то вроде следующего:

fc ([]) = []
fc (x:[]) = [x]
fc (x:[y]) = [x+1,y]
fc( x:xs ) = fc [x] : ( fc xs ) -- this line is wrong

p.s. Очевидно, я мог бы изменить (но предпочел бы понять исходную задачу) список дважды и применить что-то вроде:

helper (x:y:tail) = [x, y+1] ++ tail
fc x = reverse (helper (reverse x) )

person Dewfy    schedule 27.09.2015    source источник
comment
Ваш второй пример меняет 1-й и 3-й элемент. Что ты хочешь делать?   -  person MASL    schedule 27.09.2015
comment
@MASL задача увеличивается на каждую 2-ю справа. Итак, посмотрите на список справа: второй справа 3->(3+1)=4, теперь пропустите 2, после этого 4-й 1->(1+1)=2. Итак, результат [2, 2, 4, 4]   -  person Dewfy    schedule 27.09.2015
comment
В порядке. Следил за этой деталью.   -  person MASL    schedule 27.09.2015
comment
Переверните список, добавьте 1 к каждому второму элементу и переверните, чтобы вернуть исходный порядок.   -  person raymonad    schedule 27.09.2015
comment
@raymonad да, это возможно, но я хотел бы понять, как обрабатывать список справа налево, см. мой постскриптум. Обновить   -  person Dewfy    schedule 27.09.2015
comment
Вы имеете в виду, что хотите увеличивать каждый второй элемент, начиная справа?   -  person jub0bs    schedule 27.09.2015
comment
@Dewfy Пожалуйста, отредактируйте свой вопрос, чтобы сделать его более понятным.   -  person AJF    schedule 27.09.2015


Ответы (4)


Это можно эффективно сделать с помощью сгиба влево:

inc :: Num a => [a] -> [a]
inc xs = foldl go (\_ _ acc -> acc) xs id (+ 1) []
    where go run x f g acc = run g f (f x: acc)

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

\> inc [1, 2, 3]
[1,3,3]
\> inc [1, 2, 3, 4]
[2,2,4,4]

Его также можно обобщить на чередование функций, отличных от id и (+ 1).

person behzad.nouri    schedule 27.09.2015

Типичным способом обработки списка Haskell справа налево было бы перевернуть его. Поскольку вы хотите получить первоначальный порядок результата, вы просто снова отмените его:

f1 = reverse . zipWith (+) (cycle [0,1]) . reverse

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

f2 = snd . g
  where
  g []     = (False, [])
  g (x:xs) = let (addOne, xs') = g xs
                 x' = if addOne then x + 1 else x
             in (not addOne, x':xs')

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

import Data.List (mapAccumR)
f2' = snd . mapAccumR g False
  where
  g addOne x = (not addOne, if addOne then x + 1 else x)
person raymonad    schedule 27.09.2015
comment
Мне очень нравится ваш второй ответ. Обратите внимание, что эти два подхода могут работать очень по-разному. Двойной реверс обычно выделяет немного больше памяти, но только тесты могут сказать, что действительно будет лучше. - person dfeuer; 27.09.2015

Я думаю, что более точная спецификация того, что вы хотите, заключается в том, что вы увеличиваете четные индексы, если длина четная, и нечетные индексы, если длина нечетная. Например, при индексации с нуля список длины 3 привел к увеличению индекса 1. Один из способов сделать это — использовать очевидное двухпроходное решение:

f xs = zipWith (+) (cycle sol) xs
 where sol = map fromEnum [even len, odd len] 
       len = length xs

Это можно сделать за один проход (не полагаясь на правила слияния компилятора), "завязав узел". Например (использование ручного рекурсивного стиля в качестве средства связи).

f2 xs = let (isEven, result) = go isEven xs in result
 where
  go _ []     = (True,     [])
  go e (x:xs) = let (ne,rest) = go (not e) xs
                in (not ne, x+fromEnum e : rest)
person Thomas M. DuBuisson    schedule 27.09.2015
comment
Хорошая попытка! Действительно, это дает правильный результат. Мой вопрос о происхождении был вдохновлен попыткой найти общие части в Prolog и Haskell. Пролог дает вам 2 способа «итерации» и «рекурсии» для обработки списка в любом направлении. Итак, глядя на Haskell, я вижу только f (x:xs) - рекурсивный способ - person Dewfy; 27.09.2015
comment
Я не понимаю, как ответ Раймонада завязывает узел сам по себе. - person dfeuer; 27.09.2015
comment
@dfeuer Ой, я слишком быстро взглянул. Виноват. - person Thomas M. DuBuisson; 27.09.2015

Мне нравится решение Томаса. Однако я думаю, что здесь достаточно простого foldr.

process = snd . foldr (\x (b,xs) -> (not b, x + fromEnum b:xs)) (False,[])
person is7s    schedule 01.10.2015
comment
Это то же самое, что и второй ответ raymonad, за исключением того, что вы встроили mapAccumR. - person dfeuer; 01.10.2015
comment
Это не должно быть концептуально другим, чтобы считаться ответом. Кроме того, я считаю, что foldr намного легче понять новичкам, чем mapAccumR, потому что он гораздо более широко используется в функциональных языках. - person is7s; 01.10.2015
comment
Я соглашусь, но вся ситуация немного необычная. mapAccumL встречается гораздо чаще, и немногие новички поймут его перевод на foldr. - person dfeuer; 01.10.2015