Как paul упоминает , вещи в Haskell неизменяемы. То, что вы хотите сделать, должно быть сделано не путем изменения списка на месте, а путем деструктурирования списка, преобразования одной из его частей и реструктуризации списка с этой измененной частью. Здесь предлагается один способ деструктуризации (через splitAt
); Я хотел бы предложить другой.
Списки в Haskell определены следующим образом:
data [] a = [] | a : [a]
Это гласит: «Список a
либо пуст, либо a
за которым следует список a
". (:)
произносится как «cons» для «конструктора», и с его помощью вы можете создавать непустые списки.
1 : [] -> [1]
1 : [2,3] -> [1,2,3]
1 : 2 : 3 : [] -> [1,2,3]
Это работает в обе стороны, благодаря сопоставлению с образцом. Если у вас есть список [1,2,3]
, сопоставление его с x : xs
привяжет его голову 1
к имени x
, а хвост [2,3]
к xs
. Как видите, мы разложили список на две части, которые изначально использовались для его создания. Затем мы можем работать с этими частями, прежде чем снова собрать список:
λ> let x : xs = [1,2,3]
λ> let y = x - 5
λ> y : xs
[-4,2,3]
Итак, в вашем случае мы можем сопоставить исходный список с x : y : z : []
, вычислить w = y ++ [3]
и построить наш новый список:
λ> let x : y : z : [] = [[],[],[]]
λ> let w = y ++ [3]
λ> [x,w,z]
[[],[3],[]]
Но это не очень расширяемое решение и не решает поставленную вами проблему («с указанием c индекса»). Что, если позже мы захотим изменить тысячный элемент списка? Я не очень хочу сочетать так много деталей. К счастью, мы кое-что знаем о списках: индекс n
в списке xs
это индекс n+1
в списке x:xs
. Таким образом, мы можем выполнять рекурсию, перемещая один шаг по списку и уменьшая наш индекс на каждом шаге:
foo :: Int -> [[Int]] -> [[Int]]
foo 0 (x:xs) = TODO -- Index 0 is x. We have arrived; here, we concatenate with [3] before restructuring the list.
foo n (x:xs) = x : foo (n-1) xs
foo n [] = TODO -- Up to you how you would like to handle invalid indices. Consider the function error.
Реализуйте первый из этих трех самостоятельно, предполагая, что вы работаете с нулевым индексом. Убедитесь, что вы понимаете рекурсивный вызов во втором. Тогда читайте дальше.
Теперь это работает. Однако это не так уж и полезно - он выполняет заранее определенное вычисление для указанного элемента в списке одного конкретного типа. Пришло время обобщить. Нам нужна функция следующей сигнатуры типа:
bar :: (a -> a) -> Int -> [a] -> [a]
, где bar f n xs
применяет преобразование f
к значению с индексом n
в списке xs
. С этим мы можем реализовать функцию, описанную ранее:
foo n xs = bar (++[3]) n xs
foo = bar (++[3]) -- Alternatively, with partial application
И хотите верьте, хотите нет, изменение foo
, которое вы уже написали, на гораздо более полезный bar
- очень простая задача. Попробуйте!