Удалить одно неуникальное значение из последовательности в F # - PullRequest
3 голосов
/ 25 апреля 2010

У меня есть последовательность целых чисел, представляющих кости в F #.

В рассматриваемой игре у игрока есть пул игральных костей, и он может выбрать один из них (руководствуясь определенными правилами) и оставить остальные.

Если, например, игрок выбрасывает 6, 6 и 4 и решает сыграть одну из шестерок, есть ли простой способ вернуть последовательность с удалением только одной 6?

Seq.filter (fun x -> x != 6) dice

удаляет все шестерки, а не только одну.

Ответы [ 5 ]

4 голосов
/ 25 апреля 2010

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

let filterFirst f s =
    seq {
        let filtered = ref false
        for a in s do
            if filtered.Value = false && f a then
                filtered := true
            else yield a
    }

Пока изменяемая реализация скрыта от клиента, это все еще функциональный стиль;)

2 голосов
/ 25 апреля 2010

Если вы собираетесь хранить данные, я бы использовал ResizeArray вместо Sequence. Он имеет множество встроенных функций, таких как функция, о которой вы спрашивали. Это просто называется Удалить. Примечание : ResizeArray - это сокращение для типа CLI Список .

let test = seq [1; 2; 6; 6; 1; 0]
let a = new ResizeArray<int>(test)
a.Remove 6 |> ignore
Seq.toList a |> printf "%A"

// output
> [1; 2; 6; 1; 0]

Другие параметры типа данных могут быть Array

let removeOneFromArray v a =
    let i = Array.findIndex ((=)v) a
    Array.append a.[..(i-1)] a.[(i+1)..]

или Список

let removeOneFromList v l = 
    let rec remove acc = function
        | x::xs when x = v -> List.rev acc @ xs
        | x::xs -> remove (x::acc) xs
        | [] -> acc
    remove [] l
1 голос
/ 10 апреля 2019

Вы можете попробовать это:

let rec removeFirstOccurrence item screened items =

    items |> function
    | h::tail -> if h = item
                 then screened @ tail
                 else tail |> removeFirstOccurrence item (screened @ [h])
    | _ -> []

Использование:

let updated = products |> removeFirstOccurrence product []
1 голос
/ 25 апреля 2010

Я не думаю, что есть какая-либо функция, которая позволила бы вам напрямую представить идею о том, что вы хотите удалить из списка только первый элемент, соответствующий заданным критериям (например, что-то вроде Seq.removeOne).

Вы можете реализовать функцию относительно читабельным способом, используя Seq.fold (если последовательность чисел конечна):

let removeOne f l = 
  Seq.fold (fun (removed, res) v ->
    if removed then true, v::res
    elif f v then true, res
    else false, v::res) (false, []) l 
  |> snd |> List.rev

> removeOne (fun x -> x = 6) [ 1; 2; 6; 6; 1 ];
val it : int list = [1; 2; 6; 1]

Функция fold сохраняет некоторое состояние - в данном случае типа bool * list<'a>. Логический флаг представляет, удалили ли мы уже какой-либо элемент, и этот список используется для накопления результата (который должен быть обращен в конце обработки).

Если вам нужно сделать это для (возможно) бесконечного seq<int>, то вам нужно будет использовать GetEnumerator напрямую и реализовать код как выражение рекурсивной последовательности. Это немного страшнее и будет выглядеть так:

let removeOne f (s:seq<_>) = 
  // Get enumerator of the input sequence
  let en = s.GetEnumerator()

  let rec loop() = seq {
    // Move to the next element
    if en.MoveNext() then
      // Is this the element to skip?
      if f en.Current then
        // Yes - return all remaining elements without filtering
        while en.MoveNext() do
          yield en.Current
      else
        // No - return this element and continue looping
        yield en.Current
        yield! loop() }
  loop()
1 голос
/ 25 апреля 2010

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

let rec removeOne value list = 
               match list with
               | head::tail when head = value -> tail
               | head::tail -> head::(removeOne value tail)
               | _ -> [] //you might wanna fail here since it didn't find value in
                                   //the list

РЕДАКТИРОВАТЬ: код обновлен на основе правильного комментария ниже. Спасибо P

РЕДАКТИРОВАТЬ: После прочтения другого ответа я подумал, что предупреждение будет в порядке. Не используйте приведенный выше код для последовательностей бесконечности, но, так как, я думаю, у ваших игроков нет кубиков бесконечности, это не должно быть проблемой, но для полноты здесь есть реализация, которая будет работать (почти) для любых конечная последовательность

 let rec removeOne value seq acc = 
                   match seq.Any() with
                   | true when s.First() = value -> seq.Skip(1)
                   | true -> seq.First()::(removeOne value seq.Skip(1))
                   | _ -> List.rev acc //you might wanna fail here since it didn't find value in
                                       //the list

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...