Смущен поведением List.mapi в F # - PullRequest
2 голосов
/ 31 мая 2010

Я строю некоторые уравнения в F #, и, работая над своим полиномиальным классом, я обнаружил странное поведение, используя List.mapi

По сути, каждый полином имеет массив, поэтому 3*x^2 + 5*x + 6 будет [|6, 5, 3|] в массиве, поэтому при добавлении полиномов, если один массив длиннее другого, мне просто нужно добавить дополнительные элементы к результат, и вот где я столкнулся с проблемой.

Позже я хочу обобщить его, чтобы не всегда использовать float, но это будет после того, как я начну работать.

Итак, проблема в том, что я ожидал, что List.mapi вернет List не отдельные элементы, но, чтобы собрать списки, мне пришлось поместить [] вокруг моего использования mapi, и я мне интересно, почему это так.

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

type Polynomial() =
    let mutable coefficients:float [] = Array.empty
    member self.Coefficients with get() = coefficients
    static member (+) (v1:Polynomial, v2:Polynomial) =
        let ret = List.map2(fun c p -> c + p) (List.ofArray v1.Coefficients) (List.ofArray v2.Coefficients)
        let a = List.mapi(fun i x -> x)
        match v1.Coefficients.Length - v2.Coefficients.Length with
            | x when x < 0 ->
                ret :: [((List.ofArray v1.Coefficients) |> a)]
            | x when x > 0 ->
                ret :: [((List.ofArray v2.Coefficients) |> a)]
            | _ -> [ret]

Ответы [ 2 ]

5 голосов
/ 31 мая 2010

Я думаю, что простая реализация с использованием списков и рекурсии была бы проще в этом случае. Альтернативная реализация класса Polynomial может выглядеть примерно так:

// The type is immutable and takes initial list as constructor argument
type Polynomial(coeffs:float list) = 
  // Local recursive function implementing the addition using lists
  let rec add l1 l2 = 
    match l1, l2 with
    | x::xs, y::ys -> (x+y) :: (add xs ys)
    | rest, [] | [], rest -> rest

  member self.Coefficients = coeffs

  static member (+) (v1:Polynomial, v2:Polynomial) = 
    // Add lists using local function
    let newList = add v1.Coefficients v2.Coefficients
    // Wrap result into new polynomial
    Polynomial(newList)

Стоит отметить, что вам действительно не нужно изменяемое поле в классе, поскольку оператор + создает и возвращает новый экземпляр типа, поэтому тип является полностью неизменяемым (как обычно требуется в F #).

Приятной особенностью функции add является то, что после обработки всех элементов, доступных в обоих списках , вы можете просто вернуть хвост непустого списка в качестве остальных.


Если вы хотите реализовать ту же функциональность с использованием массивов, то может быть лучше использовать простой цикл for (поскольку массивы, в принципе, являются императивными, обычные императивные шаблоны обычно являются лучшим вариантом для работы с ними ). Однако я не думаю, что есть какая-то конкретная причина для предпочтения массивов (возможно, производительности, но это должно быть оценено позже во время разработки).

Как указывает Павел, оператор :: добавляет один элемент в начало списка (см. Выше функцию add, которая демонстрирует это). Вы можете написать то, что хотели, используя @, который объединяет списки, или Array.concat (который объединяет последовательность массивов).

Возможна также реализация с использованием функций и массивов высшего порядка - лучшая версия, которую я могу придумать, будет выглядеть так:

let add (a1:_[]) (a2:_[]) =
  // Add parts where both arrays have elements
  let l = min a1.Length a2.Length
  let both = Array.map2 (+) a1.[0 .. l-1] a2.[0 .. l-1]  
  // Take the rest of the longer array
  let rest = 
    if a1.Length > a2.Length 
    then a1.[l .. a1.Length - 1] 
    else a2.[l .. a2.Length - 1]
  // Concatenate them
  Array.concat [ both; rest ]

add [| 6; 5; 3 |] [| 7 |]  

При этом используются слайсы (например, a.[0 .. l]), которые дают вам часть массива - вы можете использовать их, чтобы взять части, где оба массива имеют элементы, а оставшуюся часть длинного массива. 1029 *

2 голосов
/ 31 мая 2010

Я думаю, вы не понимаете, что делает оператор ::. Он не используется для объединения двух списков. Используется для добавления одного элемента к списку. Следовательно, это тип:

'a -> 'a list -> 'a list

В вашем случае вы указываете ret в качестве первого аргумента, а ret само по себе float list. Следовательно, он ожидает, что второй аргумент будет иметь тип float list list, поэтому вам нужно добавить дополнительный [] ко второму аргументу, чтобы он компилировался, и это также будет тип результата вашего оператора + что, вероятно, не то, что вы хотите.

Вы можете использовать List.concat для объединения двух (или более) списков, но это неэффективно. В вашем примере я не вижу смысла использовать списки вообще - все это преобразование туда-сюда обойдется дорого. Для массивов вы можете использовать Array.append, что лучше.

Кстати, не совсем понятно, для чего вообще нужен mapi в вашем коде. Это точно так же, как map, за исключением аргумента index, но вы его не используете, и ваше отображение - это функция тождества, так что по сути это неоперация. О чем это?

...