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

#list #haskell #state

#Список #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"

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

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

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

1. Это причина, по которой я отказался от Haskell…

Ответ №1:

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

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

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

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

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

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

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

теперь вы можете снова объединить список вместе, нажав «Вкл.»:

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

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


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

  data Toggle = On | Off deriving Show
  

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

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

Ответ №2:

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

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

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

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

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

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

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

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

 > [[1,2,3],[4,5,6]] amp; 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]
  

Работа с типами, отличными от списков

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

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

  > 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 amp; element 1 .~ 99
1
|
 - 99
|  |
|   - 4
|  |
|  `- 5
|
`- 3
   |
    - 6
   |
   `- 7
> putStrLn . drawTree . fmap show $ tree amp; 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
  

Ответ №3:

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

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

  replaceNth :: Int -> a -> [a] -> [a]
 replaceNth _ _ [] = []
 replaceNth n newVal (x:xs)
   | n == 0 = newVal:xs
   | otherwise = x:replaceNth (n-1) newVal xs
  

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

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

1. Рассмотрим более общий вариант modifyNth :: Int -> (a -> a) -> [a] -> [a] , который использует функцию для изменения элемента вместо жестко запрограммированного нового значения. Теперь эта функция может быть реализована как replaceNth n newVal = modifyNth n (const newVal)

2. Вот почему Haskell не используется. Это действительно шутка — делать все это только для изменения одного элемента

Ответ №4:

Вот одна строка, которая отлично работает

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

Мне не кажется эффективным делать такого рода вещи в haskell.

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

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

Ответ №5:

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

 -- | 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 amp;amp; 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
  

Ответ №6:

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

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

Ответ №7:

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

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

Для динамического количества переключателей затем рассмотрите сопоставление от switch name к Bool .

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

1. Для динамического количества переключателей я бы предложил Vector или, возможно, IntMap .

2. @John Верно, в зависимости от ваших конкретных потребностей сопоставлением может быть Map, IntMap или HashMap (или даже что-то еще). Почему вы все же предложили вектор? Если это статично для каждого запуска, конечно, но если количество переключателей действительно должно увеличиваться или уменьшаться, это то, с чем Vector не может справиться хорошо.

Ответ №8:

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

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

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

Происходит обработка ошибок ввода.
Итак, если индекс i выходит за границы, 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 arr@(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 он объединяет предыдущий элемент с заданным новым значением, в противном случае он объединяет предыдущий элемент со старым значением в списке для этого индекса.