#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
он объединяет предыдущий элемент с заданным новым значением, в противном случае он объединяет предыдущий элемент со старым значением в списке для этого индекса.