Первый, ваше представление об изменении первого списка вместо того, чтобы возвращать новый, ошибочно. Мутация (т. Е. Изменение данных на месте) является основной причиной ошибок сегодня (используется как goto
, но это уже давно запрещено). Сделать каждую операцию новой базой данных, а не модифицировать существующие, намного безопаснее. И в некоторых случаях это может быть еще более результативным, совершенно контринтуитивно (см. Ниже).
Second, как вы пытаетесь это сделать, вы не делаете то, что, по вашему мнению, делаете. Двойная двоеточие не означает «изменить первый элемент». Это означает «прикрепить элемент вперед».Например:
let a = [1; 2; 3]
let b = 4 :: a // b = [4; 1; 2; 3]
let c = 5 :: b // c = [5; 4; 1; 2; 3]
Вот как списки на самом деле построены: вы начинаете с пустым списком и предметами вставляться в него. Синтаксис [1; 2; 3]
, который вы используете, - это просто синтаксический сахар для этого. То есть, [1; 2; 3] === 1::2::3::[]
.
Итак, как мне изменить список, спросите вы? Ответ в том, что нет! Списки F # являются неизменяемыми структурами данных. Создав список, вы больше не сможете его модифицировать.
Эта непреложность позволяет провести интересную оптимизацию. Взгляните еще раз на приведенный выше пример, один с тремя списками a
, b
и c
. Сколько клеток памяти вы считаете этими тремя списками? Первый список состоит из 3-х предметов, второго - 4 и третьего - 5, поэтому общий объем памяти должен быть равен 12, верно? Неправильно! Общий объем памяти, занимаемой этими тремя списками, действителен всего 5 ячеек. Это связано с тем, что список b
не является блоком памяти длины 4, а просто номером 4
в паре с указателем на список a
. Число 4
называется «голова» списка, а указатель называется «хвостом». Аналогично, список c
состоит из одного номера 5
(его «голова») и указателя на список b
, который является его «хвостом».
Если списки не были неизменными, их не удалось организовать так: что, если кто-то изменяет мой хвост? Списки должны быть скопированы каждый раз (google «защитная копия»).
Таким образом, единственный способ сделать список - это вернуть новый. То, что вы пытаетесь сделать, может быть описано примерно так: если входные списки пустые, результатом будет пустой список; в противном случае результатом будет сумма хвостов, добавленных с суммой головок. Вы можете записать это в F # почти дословно:
let rec add a b =
match a, b with
| [], [] -> [] // sum of two empty list is an empty list
| a::atail, b::btail -> (a + b) :: (add atail btail) // sum of non-empty lists is sum of their tails prepended with sum of their heads
Обратите внимание, что эта программа является неполной: она не определяет, какой результат должен быть, когда один вход пуст, а другой нет. Компилятор генерирует предупреждение об этом. Я оставлю решение в качестве упражнения для читателя.
_Why_ Вы не хотите возвращать новый список? –
Использование [Seq.zip] (https://msdn.microsoft.com/en-us/visualfsharpdocs/conceptual/seq.zip%5B't1,'t2%5D-function-%5Bfsharp%5D) и возврат нового коллекция кажется самым идиоматическим подходом ко мне – DaveShaw
@FyodorSoikin В задании не указано, должен ли я или не должен возвращать новый список. Обычно я иду безопасным способом и не делаю этого, но в этом случае, я думаю, это не имело бы значения. Прошу прощения за путаницу. – user999999