как добавить число в 2D-список с указанием c индекса в haskell - PullRequest
0 голосов
/ 17 июня 2020

Я новичок в haskell, и я попытался добавить число в 2D-список с указанием c индекса в haskell, но я не знаю, как это сделать

Пример у меня есть это:

[[],[],[]]

и я хотел бы поместить число (3) в индекс 1 вот так

[[],[3],[]]

Я пробовал это

[array !! 1] ++ [[3]]

но не работает

Ответы [ 2 ]

1 голос
/ 17 июня 2020

Как 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 - очень простая задача. Попробуйте!

1 голос
/ 17 июня 2020

Как вы, возможно, заметили в своем набеге, Haskell не похож на многие другие языки тем, что он обычно неизменяемый , поэтому пытается изменить значение, особенно в глубоко вложенной структуре вот так, это не самое легкое. [array !! 1] даст вам вложенный список [[]], но он не изменяемый, поэтому любые манипуляции, которые вы делаете с этой структурой, не будут отражены в исходном array, это будет отдельная копия.

(Существуют специализированные среды, в которых вы можете выполнять локальную изменчивость, как, например, Vectors в монаде ST, но это исключение.)

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

Функция splitAt выглядит так, как будто она поможет вам в этом: она требует список и разделяет его на две части по указанному вами индексу.

let array = [[],[],[]]
splitAt 1 array

даст вам

([[]], [[],[]])

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

Давайте сделаем деструктурирующую привязку, чтобы иметь возможность позже восстановить ваш окончательный список:

let array = [[],[],[]]
    (beginning, end) = splitAt 1 array

Затем вам нужно будет перейти к нужному подсписку, который является первым предметом в списке end:

    desired = head end

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

    desired' = 3:desired

Теперь нам нужно вернуть это в список end. К сожалению, список end по-прежнему имеет исходное значение [[],[]], поэтому нам придется заменить его заголовок на наш desired', чтобы все было правильно:

    end' = desired' : (tail end)

Это отбрасывает пустой подсписок в начале и прикрепляет измененный список на его месте.

Теперь все, что осталось, это рекомбинировать измененный end' с исходным beginning:

in beginning ++ end'

создание всего фрагмента:

let array = [[],[],[]]
    (beginning, end) = splitAt 1 array
    desired = head end
    desired' = 3:desired
    end' = desired' : (tail end)
in beginning ++ end'

или, если вы вводите все это как команды в REPL:

let array = [[],[],[]]
let (beginning, end) = splitAt 1 array
let desired = head end
let desired' = 3:desired
let end' = desired' : (tail end)
beginning ++ end'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...