Заменить отдельные элементы списка в 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
Я не уверен, что вы пытаетесь сделать. Если вам нужно только создать [ "Выкл." , "Выкл." , "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
, он объединяет предыдущий элемент с данным новым значением, в противном случае он объединяет предыдущий элемент со старым значением в список для этого индекса.