如何填充现有列表/数组

时间:2017-06-07 22:13:06

标签: ocaml reason

我是理性/ ocaml /函数式编程的新手。

我知道List.append[] @ []但是这些函数会创建新的列表但是如何填充现有的列表/数组?

  1. 填充列表的最佳方法是什么?
  2. 填充数组的最佳方法是什么?表示coords类型是let coords: array point = [];
  3. 对于这种情况,这是错误的流程(算法)吗?
  4. 原因代码:

    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)
      });
    });
    

2 个答案:

答案 0 :(得分:8)

欢迎来到理智!

在Reason / OCaml中,列表是不可变的。在引擎盖下,它们是简单的单链表。每次“修改”它们时都会创建新的。这是一个例子:

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

这类似于JavaScript的数组“spread”,除了这里你正在使用现有的a,在前面链接一个新节点0并将其称为b。 a仍然指向[1, 2, 3](因此“不可变”)。 b现在是[0, 1, 2, 3]。这是有效的,因为[1, 2, 3]部分是共享的。

这样做的好处是你不必担心传递你的列表而不小心有一个模糊的功能修改它。列表的不变性允许您纯粹通过查看您正在盯着的值来推理您的代码(因为它永远不会改变!)。

列表的缺点是最后添加一些东西是低效的:

let c = a @ [4] 

该操作基本上是列出一个项目[4],并连续将[1, 2, 3]的每个项目附加到其中。因此在perf方面是线性的。但从列表实现的简单性来看,它在历史上被认为值得权衡。

所以3.如果你想设置一个列表项,这是错误的流程。

  1. 在您的案例中填充列表的最佳方法是从旧列表中非变异地映射它:let newList = List.map (fun blabla => ...) raw
  2. 数组相同。映射它。如果您遇到困难,可以Array.of_listArray.to_list
  3. 更多关于数组:OCaml数组是可变的,其大小是不可更改的。把它想象成一块记忆。您将通过Array.make newSize分配一个新数组,然后通过Array.set填充它。如果你要大量调整数组大小,那就没有意义,所以选择正确的数据结构。

    对于JS编译,BuckleScript将ocaml数组编译为JS数组。因此可变的可调整大小。您将在Js.Array

    下找到熟悉的JS数组操作

    作为一般启发式,如果您想更改长度:请尝试filter。如果您想更改长度和所包含的项目,请尝试fold_left。否则,map

    最近,我们开始实现一些不可变的,可调整大小的,可选的可变数组。请继续关注!

答案 1 :(得分:0)

这里有几种不同的方法来做到这一点。如果你想像评论中提到的那样坚持使用 Js.Array,你可以这样做:

使用 Js.ArrayArray.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 } ]

如果你想按照@chenglou的建议用Array.fold_leftArray.append来做,你可以用

        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

您可以选择使用 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"]) */