как заполнить существующий список/массив

Я новичок в Reason/ocaml/функциональном программировании.

Я знаю о List.append и [] @ [], но эти функции создадут новый список, но как заполнить существующий список/массив?

  1. Как лучше заполнить список?
  2. Каков наилучший способ заполнения массива? Означает, если тип координат let coords: array point = [];
  3. Или это неправильный поток (алгоритм) для такого случая?

Код причины:

type point = {x: int, y: int};

let coords: list point = [];

let append raw =>
  Array.iter
    (
      fun data => {
        let p = {x: data.x, y: data.y};
        /* how to append p to coords */
        ()
      }
    )
    raw;

JS-аналог:

const coords = [];
const append = raw => raw.forEach({x, y} => {
  coords.push({
    x: process(x),
    y: process(y)
  });
});

person tuchk4    schedule 07.06.2017    source источник
comment
Досадно простой ответ: в FP вам не нужен отдельный coords, так как это точная копия raw. Просто продолжайте использовать сырье.   -  person Yawar    schedule 08.06.2017
comment
@Yawar У меня есть код обновления. Например - координаты содержат обработанные x y (означает, что не точная копия)   -  person tuchk4    schedule 08.06.2017
comment
Посмотрите ответ Ченг Лу, он исчерпывающий, но вкратце вы бы сделали что-то вроде List.map (fun {x, y} => {x: process x, y: process y}) raw   -  person Yawar    schedule 08.06.2017
comment
да, List.map поможет, но области (где список был создан и где список должен быть сопоставлен) разные   -  person tuchk4    schedule 08.06.2017
comment
Я не понимаю, можете ли вы привести пример проблемы с областью действия?   -  person Yawar    schedule 08.06.2017
comment
gist.github.com/tuchk4/7130405338a41a7a91b6de22356c072e   -  person tuchk4    schedule 08.06.2017


Ответы (2)


Добро пожаловать в Разум!

В Reason/OCaml списки неизменяемы. Под капотом они представляют собой простой односвязный список. Вы создаете новые каждый раз, когда «модифицируете» их. Вот пример:

let a = [1, 2, 3];
let b = [0, ...a];

Это похоже на «распространение» массива JavaScript, за исключением того, что здесь вы берете существующий a, связываете новый узел 0 впереди и называете его b. a по-прежнему указывает на [1, 2, 3] (таким образом, «неизменный»). b теперь [0, 1, 2, 3]. Это эффективно, поскольку часть [1, 2, 3] является общей.

Преимущество этого заключается в том, что вам не нужно беспокоиться о том, чтобы передать свой список и случайно изменить его неясной функцией. Неизменяемость списка позволяет вам рассуждать о вашем коде, просто глядя на значение, которое вы смотрите прямо сейчас (поскольку оно никогда не изменится!).

Недостаток списка в том, что добавлять что-то в конец неэффективно:

let c = a @ [4] 

Эта операция в основном берет список из одного элемента, [4], и последовательно прикрепляет к нему каждый элемент [1, 2, 3]. Так линейно с точки зрения производительности. Но, судя по простоте реализации списка, исторически он считался достойным компромисса.

Итак, 3. это неправильный поток, если вы пытаетесь установить элемент списка.

  1. Лучший способ заполнить список в вашем случае — это сопоставить его без изменений из старого списка: let newList = List.map (fun blabla => ...) raw
  2. То же самое для массива. Карта над ним. Есть Array.of_list и Array.to_list, если вы когда-нибудь застряли.

Подробнее о массиве: массив OCaml является изменяемым, и его размер неизменен. Думайте об этом как о блоке памяти. Вы должны выделить новый массив через Array.make newSize, а затем заполнить его через Array.set. Это не имеет смысла, если вы сильно изменяете размер массива, поэтому выберите правильную структуру данных.

Для компиляции JS BuckleScript компилирует массив ocaml в массив JS. Таким образом, он может изменять и размер. Знакомые операции с массивами JS вы найдете в разделе Js.Array.

В качестве общей эвристики, если вы хотите изменить длину: попробуйте filter. Если вы хотите изменить длину и содержащиеся элементы, попробуйте fold_left. В противном случае map.

Совсем недавно мы начали реализовывать некоторый неизменяемый, изменяемый по размеру, опционально изменяемый массив. Быть в курсе!

person chenglou    schedule 07.06.2017
comment
List.map (fun blabla => ...) raw - но в этом случае результат должен быть присвоен новой переменной и в соответствии с приведенным примером - области действия (где список был создан и где список должен быть отображен) разные. Таким образом, такой поток не является правильным в соответствии с потоком FP? - person tuchk4; 08.06.2017
comment
Да, я присваиваю новую переменную. Я добавил уточнение. Кстати, если вы не используете возвращаемое значение, система типов предупредит вас. Так что все хорошо. - person chenglou; 08.06.2017
comment
С Js.Array все в порядке, но я хочу использовать как можно больше причин потока и структуры. Как быть с этим делом? что я не умею работать с ФП :) - person tuchk4; 08.06.2017
comment
@tuchk4 прокомментировал вашу суть - person Yawar; 08.06.2017
comment
Спасибо. Теперь мне стало намного понятнее. Не могли бы вы проверить этот случай: gist.github.com/tuchk4/42a98693fd0d664eea04bd54ad95549e - person tuchk4; 08.06.2017
comment
Я обновил ответ, чтобы включить информацию о filter и fold_left, двух вещах, которые вы, вероятно, использовали бы для сопоставления с результатом разной длины. - person chenglou; 08.06.2017

Вот несколько различных способов сделать это. Если вы хотите придерживаться Js.Array, как вы отметили в комментариях, вы можете сделать это следующим образом:

Использование Js.Array и Array.iter

    type point = {
      x: int,
      y: int,
    };

    let points: array(point) = [|{x: 2, y: 4}|];

    let rawArray = [|[|1, 2|], [|3, 4|], [|9, 4|]|];

    let append = raw => {
      Array.iter(
        a => {
          let data: point = {x: a[0], y: a[1]};
          Js.Array.push(data, points)->ignore;
        },
        raw,
      );
    };

    append(rawArray);

    Js.log2("new points", points);
...or with tuples with `Js.Array`
    let rawTuples = [|(3, 5), (4, 9), (9, 4)|];

    append(rawTuples);

    Js.log2("new points", points);
[ { x: 2, y: 4 }, { x: 3, y: 5 }, { x: 4, y: 9 }, { x: 9, y: 4 } ]

Если вы хотите сделать это с помощью Array.fold_left и Array.append, как предложил @chenglou, вы можете попробовать это с помощью

        rawArray|>Array.fold_left((a, b) =>
        Array.append(a,  [|{x: b[0], y: b[1]}|]), [||]);

Это может быть чище с некоторыми вспомогательными функциями, такими как:

    let concatMap = f =>
      Array.fold_left((a, b) => Array.append(a, f(b)), [||]);

    let newPoint = coord => [|{x: coord[0], y: coord[1]}|];

Затем вызов:

    let rawTuples = [|(3, 5), (4, 9), (9, 4)|];

    let arrayTuples = rawTuples |> concatMap(a => [|{x: fst(a), y: snd(a)}|]);

Использование помощников также помогает мне понять, что делает каждая часть функции.

Работает и с tuples, так как это просто массивы в ReasonML/Ocaml/Rescript.

    let rawTuples = [|(3, 5), (4, 9), (9, 4)|];

    let arrayTuples = rawTuples |> concatMap(a => [|{x: fst(a), y: snd(a)}|]);

Использование изменяемых записей

У вас есть возможность создать свой массив с помощью mutable records .

Это то, что обновляет массив изменяемых записей. Это не сильно отличается.

Здесь мы используем функцию для изменения данных в массиве.

    let printCoords = coords => Array.iter(Js.log, coords);

    type mutablePoint('a, 'b) = {
      mutable x: 'a,
      mutable y: 'b,
    };

    let data1: mutablePoint(int, int) = {x: 2, y: 4};

    let data2: mutablePoint(int, int) = {x: 3, y: 4};

    let data3: mutablePoint(int, int) = {x: 4, y: 4};

    let isEven = n => {
      n mod 2 == 0;
    };

    let multiplyByY = data => data.x = data.x * data.y;

    let makeItOdd = data => data.x = data.x + 1;

    let updateData = data => data.x->isEven
       ? data->makeItOdd :  multiplyByY(data);

    let points: array(mutablePoint('x, 'y)) = [|data1, data2, data3|];

    let append = (fn, data) => {
      Array.iter(x => {fn(x)}, data);

      data;
    };

    points |> append(updateData);

    Js.log("points after");

    printCoords(points);

    // points after { x: 3, y: 4 } { x: 12, y: 4 }{ x: 5, y: 4 }

Ваш вопрос касался обновления некоторых необработанных данных, поэтому вот пример того, где мы берем необработанные данные по индексу i и используем их для изменения значения x в изменяемом массиве:

    let points2: array(mutablePoint('x, 'y)) = [|data1, data2, data3|];

    let printCoords = coords => Array.iter(Js.log, coords);

    printCoords(points2);

    let rawData = [|[|1, 2|], [|3, 4|], [|9, 4|]|];

    let update_x_on_point_i = (i, x) => points2[i].x = x;

    let append = raw =>
      Array.iteri(
        (i, d) => {
          let x: int = d[0];

          update_x_on_point_i(i, x);

        },
        raw,
      );

    append(rawData);

    Js.log2("points2 after: ", points2);

    printCoords(points2);

    // points2 after:  [ { x: 1, y: 4 }, { x: 3, y: 4 }, { x: 9, y: 4 } ]

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

Аналог JS с Belt.Array

Просто потому, что я сделал это сегодня по тем же причинам, которые привели меня к этому сообщению, вот версия JS Analogue.

    type point = {
      x: int,
      y: int,
    };
    let coords = [|{x: 9, y: 7}, {x: 2, y: 4}, {x: 3, y: 8}|];

    Js.log("coords before");

    Js.log("-------");

    let append = raw =>
      raw->Belt.Array.mapWithIndex(
             _,
             (i, r) => {
               let new_point_i = {x: r[0], y: r[1]};
               coords[i] = new_point_i;
             },
           );

    let rawData = [|[|1, 2|], [|3, 4|], [|9, 4|]|];

    append(rawData);

    Js.log("coords after");

    Js.log(coords);
    coords before
    [ { x: 9, y: 7 }, { x: 2, y: 4 }, { x: 3, y: 8 } ]
     -------
    coords after
    [ { x: 1, y: 2 }, { x: 3, y: 4 }, { x: 9, y: 4 } ]

Обновлять

Следующий код является обновленным синтаксисом для кода @yawar в сути в комментариях выше. Его объяснение стоит прочесть.

type data_item = {
  symbol: string,
  next: bool,
};

/* Convenience function for making data items. */
let make_data_item = (symbol, next) => {symbol, next};

let process = (data: list(data_item)) => {
  /*
   We track the current symbol as well as the result list in the folding function.
   */
  let fold_func = ((current, result), {symbol, next}) =>
    if (next) {
      (symbol, [current, ...result]);
    } else {
      (current ++ symbol, result);
    };

  let (current, result) = List.fold_left(fold_func, ("", []), data);
  /*
   We need to reverse the result list because `[el, ...els]` above actually
   builds it up in the _opposite_ order to the original input list.
   */
  (current, List.rev(result));
};

let result =
  process([
    make_data_item("a", false),
    make_data_item("b", false),
    make_data_item("c", true),
    make_data_item("d", false),
  ]);
/* result = ("cd", ["ab"]) */
person armand    schedule 09.06.2021