Подтвердить что ты не робот

Заменить отдельные элементы списка в Haskell?

У меня есть список элементов, и я хочу их обновить:

из этого: ["Off","Off","Off","Off"]

к этому: ["Off","Off","On","Off"]

Поскольку я несколько новичок в Haskell, я использовал (x:xs)!!y для извлечения и обновления отдельных компонентов с помощью функции:

replace y z [] = []
replace y z (x:xs)
  | x==y           = z:replace y z xs
  | otherwise      = x:replace y z xs

а затем введите следующее в ghci: (replace "Off" "On" ["Off",'Off","Off","Off"]) !! 2

Получаю следующее: "On"

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

Любая помощь по этому вопросу была бы оценена.

4b9b3361

Ответ 1

Я не уверен, что вы пытаетесь сделать. Если вам нужно только создать [ "Выкл." , "Выкл." , "On", "Выкл." ], Вы можете сделать это явно. Вообще говоря, следует избегать изменения состояния в haskell.

Возможно, вам нужна функция "изменить" (сгенерировать новый элемент с другим значением) n-й элемент списка? Дон дает очень общий подход к этой проблеме. Вы также можете использовать явную рекурсию:

 replaceNth n newVal (x:xs)
     | n == 0 = newVal:xs
     | otherwise = x:replaceNth (n-1) newVal xs

Haskell предоставляет отличные возможности для манипулирования списками. Если вы уже не знаете их уже filter, map и foldr/foldl, все стоит посмотреть, как и списки.

Ответ 2

Как правило, вы изменяете элементы списка, разбивая список, заменяя элемент и соединяя его вместе.

Чтобы разбить список по индексу, мы имеем:

 splitAt :: Int -> [a] -> ([a], [a]) 

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

 > splitAt 2 ["Off","Off","Off","Off"] 
 (["Off","Off"],["Off","Off"])

теперь вам просто нужно поместить элемент заголовка компонента snd в списке. Это легко сделать с помощью сопоставления с образцом:

 > let (x,_:ys) = splitAt 2 ["Off","Off","Off","Off"]
 > x
 ["Off","Off"]
 > ys
 ["Off"]

теперь вы можете присоединиться к списку обратно вместе с "On":

 > x ++ "On" : ys
 ["Off","Off","On","Off"]

Я оставлю это вам, чтобы собрать эти фрагменты в одну функцию.


В качестве примечания стиля я бы предложил использовать новый настраиваемый тип данных вместо String для ваших переключателей:

 data Toggle = On | Off deriving Show

Ответ 3

Изменение n-го элемента

Общей операцией на многих языках является присвоение индексированной позиции в массиве. В python вы можете:

>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]

lens предоставляет эту функцию с помощью оператора (.~). Хотя в отличие от python исходный список не мутирован, скорее возвращается новый список.

> let a = [1,2,3,4,5]
> a & element 3 .~ 9
[1,2,3,9,5]
> a
[1,2,3,4,5]

element 3 .~ 9 - это просто функция и оператор (&), часть lens, это просто приложение с обратной функцией. Здесь это с более распространенным приложением функции.

> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]

Назначение снова отлично работает с произвольным вложением Traversable s.

> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]

или

> set (element 3) 9 [1,2,3,4,5,6,7]

Или, если вы хотите использовать несколько элементов, которые вы можете использовать:

> over (elements (>3)) (const 99) [1,2,3,4,5,6,7]
> [1,2,3,4,99,99,99]

Работа с другими типами списков

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

Возьмем, например, ту же технику, что и на деревья, образуют стандарт containers.

 > import Data.Tree
 > :{
 let
  tree = Node 1 [
       Node 2 [Node 4[], Node 5 []]
     , Node 3 [Node 6 [], Node 7 []]
     ]
 :}
> putStrLn . drawTree . fmap show $ tree
1
|
+- 2
|  |
|  +- 4
|  |
|  `- 5
|
`- 3
   |
   +- 6
   |
   `- 7
> putStrLn . drawTree . fmap show $ tree & element 1 .~ 99
1
|
+- 99
|  |
|  +- 4
|  |
|  `- 5
|
`- 3
   |
   +- 6
   |
   `- 7
> putStrLn . drawTree . fmap show $ tree & element 3 .~ 99
1
|
+- 2
|  |
|  +- 4
|  |
|  `- 99
|
`- 3
   |
   +- 6
   |
   `- 7
> putStrLn . drawTree . fmap show $ over (elements (>3)) (const 99) tree
1
|
+- 2
|  |
|  +- 4
|  |
|  `- 5
|
`- 99
   |
   +- 99
   |
   `- 99

Ответ 4

Вот код, который я использовал:

-- | Replaces an element in a list with a new element, if that element exists.
safeReplaceElement
  -- | The list
  :: [a]
  -- | Index of the element to replace.
  -> Int
  -- | The new element.
  -> a
  -- | The updated list.
  -> [a]
safeReplaceElement xs i x =
  if i >= 0 && i < length xs
    then replaceElement xs i x
    else xs


-- | Replaces an element in a list with a new element.
replaceElement
  -- | The list
  :: [a]
  -- | Index of the element to replace.
  -> Int
  -- | The new element.
  -> a
  -- | The updated list.
  -> [a]
replaceElement xs i x = fore ++ (x : aft)
  where fore = take i xs
        aft = drop (i+1) xs

Ответ 5

Вот один лайнер, который отлично работает

replace pos newVal list = take pos list ++ newVal : drop (pos+1) list

Мне кажется, что неэффективно это делать в haskell.

Ответ 6

Я думаю, вам следует рассмотреть возможность использования структуры данных, отличной от List. Например, если вы просто хотите иметь состояние из четырех переключателей вкл/выкл, тогда:

data State = St { sw1, sw2, sw3, sw4 :: Bool }

Для динамического числа коммутаторов рассмотрим отображение от switch name до Bool.

Ответ 7

На самом деле, во многих случаях (не всегда), где вы бы использовали List, лучше использовать Data.Vector.

Он поставляется с функцией обновления, см. Hackage, что делает именно то, что вам нужно.

Ответ 8

Я считаю, что это более элегантный способ замены отдельного элемента:

setelt:: Int -> [a] -> a -> [a]

setelt i list newValue = 
  let (ys,zs) = splitAt i-1 list in  ys ++ newValue ++ tail zs

Имеется обработка ошибок ввода. Поэтому, если индекс я выходит за границы, haskell будет показывать неправильный вывод. (Примечание: индексирование Haskell начинается с 1 года)

Объятия будут вести себя следующим образом:

Main> setelt 1 [1,2,3] 9
[9,2,3]
Main> setelt 3 [1,2,3] 9
[1,2,9]
Main> setelt 0 [1,2,3] 9
[9,2,3]
Main> setelt 4 [1,2,3] 9
[1,2,3,9]
Program error: pattern match failure: tail []

Обработка ошибок в вашей службе:

setelt i y newValue = 
    if and [i>0, i<= length y]
    then let (ys,zs) = splitAt (i-1) y in  ys ++ [newValue] ++ tail zs
    else y

Если указатель, указанный вами, неверен, он возвращает исходный список.

Ответ 9

Этот ответ приходит довольно поздно, но я решил поделиться тем, что, по моему мнению, является эффективным способом замены элемента nth в списке в Haskell. Я новичок в Haskell и думал, что смогу войти.

Функция set устанавливает n-й элемент в списке в заданное значение:

set' :: Int -> Int -> a -> [a] -> [a]
set' _ _ _ [] = []
set' ind curr new [email protected](x:xs)
    | curr > ind = arr
    | ind == curr = new:(set' ind (curr+1) new xs)
    | otherwise = x:(set' ind (curr+1) new xs)

set :: Int -> a -> [a] -> [a]
set ind new arr = (set' ind 0 new arr)

Поскольку set перемещает список, он разбивает список отдельно, и если текущий индекс является данным n, он объединяет предыдущий элемент с данным новым значением, в противном случае он объединяет предыдущий элемент со старым значением в список для этого индекса.