문제

플로트 목록을 Vector3 또는 Vector2 목록으로 변환하려는 두 개의 코드 스 니펫이 있습니다. 아이디어는 목록에서 한 번에 2/3 요소를 가져 와서 벡터로 결합하는 것입니다. 최종 결과는 일련의 벡터입니다.

    let rec vec3Seq floatList =
        seq {
            match floatList with
            | x::y::z::tail -> yield Vector3(x,y,z)
                               yield! vec3Seq tail
            | [] -> ()
            | _ -> failwith "float array not multiple of 3?"
            }

    let rec vec2Seq floatList =
        seq {
            match floatList with
            | x::y::tail -> yield Vector2(x,y)
                            yield! vec2Seq tail
            | [] -> ()
            | _ -> failwith "float array not multiple of 2?"
            }

코드는 매우 유사 해 보이지만 공통 부분을 추출 할 방법이없는 것 같습니다. 어떤 아이디어?

도움이 되었습니까?

해결책

여기에 하나의 접근법이 있습니다. 이것이 얼마나 단순한 지 잘 모르겠지만 반복되는 논리 중 일부를 추상화합니다.

let rec mkSeq (|P|_|) x =
  seq {
    match x with
    | P(p,tail) -> 
        yield p
        yield! mkSeq (|P|_|) tail
    | [] -> ()
    | _ -> failwith "List length mismatch" }

let vec3Seq =
  mkSeq (function
  | x::y::z::tail -> Some(Vector3(x,y,z), tail)
  | _ -> None)

다른 팁

Rex가 언급 한 바와 같이, 두 가지 경우에만 이것을 원한다면 코드를 그대로두면 아무런 문제가 없을 것입니다. 그러나 공통 패턴을 추출하려면 지정된 길이 (2 또는 3 또는 다른 숫자)의 하위 목록에 목록을 분할하는 함수를 작성할 수 있습니다. 일단 그렇게하면 만 사용할 수 있습니다 map 지정된 길이의 각 목록을 바꾸려면 Vector.

분할 목록의 기능은 F# 라이브러리에서 사용할 수 없으므로 (내가 알 수있는 한) 직접 구현해야합니다. 대략 다음과 같이 할 수 있습니다.

let divideList n list = 
  // 'acc' - accumulates the resulting sub-lists (reversed order)
  // 'tmp' - stores values of the current sub-list (reversed order)
  // 'c'   - the length of 'tmp' so far
  // 'list' - the remaining elements to process
  let rec divideListAux acc tmp c list = 
    match list with
    | x::xs when c = n - 1 -> 
      // we're adding last element to 'tmp', 
      // so we reverse it and add it to accumulator
      divideListAux ((List.rev (x::tmp))::acc) [] 0 xs
    | x::xs ->
      // add one more value to 'tmp'
      divideListAux acc (x::tmp) (c+1) xs
    | [] when c = 0 ->  List.rev acc // no more elements and empty 'tmp'
    | _ -> failwithf "not multiple of %d" n // non-empty 'tmp'
  divideListAux [] [] 0 list      

이제이 기능을 사용하여 다음과 같은 두 가지 변환을 구현할 수 있습니다.

seq { for [x; y] in floatList |> divideList 2 -> Vector2(x,y) }
seq { for [x; y; z] in floatList |> divideList 3 -> Vector3(x,y,z) }

반환 된 목록의 각각 길이 2 또는 3이 될 것으로 예상되는 불완전한 패턴을 사용하기 때문에 경고가 발생하지만 올바른 기대치이므로 코드가 잘 작동합니다. 나는 또한 간단한 버전을 사용하고 있습니다 시퀀스 표현 그만큼 -> 같은 일을합니다 do yield, 그러나 이와 같은 간단한 경우에만 사용할 수 있습니다.

이것은 KVB의 솔루션에 동시이지만 부분 활성 패턴을 사용하지 않습니다.

let rec listToSeq convert (list:list<_>) =
    seq {
        if not(List.isEmpty list) then
            let list, vec = convert list
            yield vec
            yield! listToSeq convert list
        }

let vec2Seq = listToSeq (function
    | x::y::tail -> tail, Vector2(x,y)
    | _ -> failwith "float array not multiple of 2?")

let vec3Seq = listToSeq (function
    | x::y::z::tail -> tail, Vector3(x,y,z)
    | _ -> failwith "float array not multiple of 3?")

솔직히, 당신이 가진 것은 이것을 사용하여 조금 더 컴팩트 할 수는 있지만, 당신이 가진 것만 큼 좋은 것입니다.

// take 3 [1 .. 5] returns ([1; 2; 3], [4; 5])
let rec take count l =
    match count, l with
    | 0, xs -> [], xs
    | n, x::xs -> let res, xs' = take (count - 1) xs in x::res, xs'
    | n, [] -> failwith "Index out of range"

// split 3 [1 .. 6] returns [[1;2;3]; [4;5;6]]
let rec split count l =
    seq { match take count l with
          | xs, ys -> yield xs; if ys <> [] then yield! split count ys }

let vec3Seq l = split 3 l |> Seq.map (fun [x;y;z] -> Vector3(x, y, z))
let vec2Seq l = split 2 l |> Seq.map (fun [x;y] -> Vector2(x, y))

이제 목록을 분해하는 프로세스는 자체 일반적인 "Take"및 "Split"기능으로 이동하여 원하는 유형에 훨씬 쉽게 매핑 할 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top