티스토리 뷰

시리즈/OCaml

99 problems in OCaml🐪 1

빅또리 2021. 11. 7. 16:34

working with lists

 

1. 리스트 마지막 원소

let rec last = function
    |[] -> None
    |[x] -> Some x
    |h::t -> last t

2. 마지막 2개 원소

let rec last_two = function
    |[] |[_] -> None
    |[a; b] -> Some (a,b)
    |h::t -> last_two t

3. 리스트 n번째 원소

let rec at n = function
    |[] -> None
    |h::t -> if n>1 then at (n-1) t else Some h

4. 리스트 길이

let rec length = function
    |[] -> 0
    |h::t -> 1 + length t    

(*tail recursive*)
let length lst = 
    let rec aux acc = function
        |[] -> acc
        |h::t -> aux (acc+1) t
    in aux 0 lst

5. 리스트 뒤집기

let rec rev = function
    |[] -> []
    |h::t -> (rev t)@[h]

(*tail recursive*)
let rev lst =
    let rec aux res = function
        |[] -> res
        |h::t -> aux (h::res) t
    in aux [] lst

6. 리스트가 palindrome인지 판단하기

let is_palindrome l = l = List.rev l

(*List.rev 직접구현*)
let is_palindrome l =
    let rec rev acc = function
        |[] -> acc
        |h::t -> rev (h::acc) t
    in l = rev [] l

7. flatten

(* flatten [One "a"; Many [One "b"; Many [One "c" ;One "d"]; One "e"]];; *)
type 'a node =
    | One of 'a 
    | Many of 'a node list;;

let rec flatten = function
    |[] -> []
    |One x :: t -> x::(flatten t)
    |Many l :: t -> (flatten l) @ (flatten t)

(*tail recursive*)
let flatten lst = 
    let rec aux acc = function
        |[] -> acc
        |One x :: t -> aux (x::acc) t
        |Many l :: t -> aux (aux acc l) t    
    in List.rev (aux [] lst)

8. compress

(*
# compress ["a"; "a"; "a"; "a"; "b"; "c"; "c"; "a"; "a"; "d"; "e"; "e"; "e"; "e"];;
- : string list = ["a"; "b"; "c"; "a"; "d"; "e"]
*)    
let rec compress = function
    |[] | [_] as t -> t
    |h1::(h2::_ as t) -> if h1=h2 then compress t else h1::(compress t)

(*tail recursive*)
let compress lst = 
    let rec aux acc = function
        |[] -> acc
        |[x] -> x::acc
        |h1::(h2::_ as t) -> if h1=h2 then aux acc t else aux (h1::acc) t
    in List.rev (aux [] lst)

9. pack

(*
# compress ["a"; "a"; "a"; "a"; "b"; "c"; "c"; "a"; "a"; "d"; "e"; "e"; "e"; "e"];;
- : string list = ["a"; "b"; "c"; "a"; "d"; "e"]
*)

let pack lst = 
    let rec aux l1 l2 = function
        |[] -> []         (* lst가 []이었을 경우만 이 case에 매칭됨 *)
        |[x] -> (x::l1)::l2
        |h1::(h2::_ as t) -> if h1=h2 then aux (h1::l1) l2 t else aux [] ((h1::l1)::l2) t
    in List.rev (aux [] [] lst)

10. RLE (run length encoding)

(*
# encode ["a"; "a"; "a"; "a"; "b"; "c"; "c"; "a"; "a"; "d"; "e"; "e"; "e"; "e"];;
- : (int * string) list =
[(4, "a"); (1, "b"); (2, "c"); (2, "a"); (1, "d"); (4, "e")]
*)

let encode lst =
    let rec aux n acc = function
        |[] -> []              (* lst가 []이었을 경우만 이 case에 매칭됨 *)
        |[x] -> (n+1, x) :: acc
        |h1::(h2::_ as t) -> if h1=h2 then aux (n+1) acc t else aux 0 ((n+1, h1) :: acc) t
    in List.rev (aux 0 [] lst)    

(* 9번 pack 함수 이용하는 버전 *)
let encode lst = 
    List.map (fun l -> List.length l, List.hd l) (pack lst)

11. modified rle. 정의한 rle 타입 적용하는 버전

(*
encode ["a"; "a"; "a"; "a"; "b"; "c"; "c"; "a"; "a"; "d"; "e"; "e"; "e"; "e"];;
- : string rle list =
[Many (4, "a"); One "b"; Many (2, "c"); Many (2, "a"); One "d";
 Many (4, "e")]
 *)

type 'a rle =
    | One of 'a
    | Many of int * 'a;;

let encode lst = 
    let f l = 
        let len = List.length l in 
        if len=1 then One (List.hd l)
        else Many (len, List.hd l)  
    in List.map f (pack lst)

12. rle decode

(*
# decode [Many (4, "a"); One "b"; Many (2, "c"); Many (2, "a"); One "d"; Many (4, "e")];;
- : string list =
["a"; "a"; "a"; "a"; "b"; "c"; "c"; "a"; "a"; "d"; "e"; "e"; "e"; "e"]
*)

(* 풀이 1*)
let decode = function
    | [] -> []
    | Many (n,x) :: t -> if n > 1 then x :: decode (Many (n-1,x) :: t) else x :: decode t
    | One x :: t-> x :: decode t

(* 풀이 2*)
let decode lst =
    let rec len n x = if n=0 then [] else x :: len (n-1) x in
    let rec aux el=
        match el with
        | One x -> [x]
        | Many (n,x) -> len n x 
    in List.flatten (List.map aux lst)

13. encode

let encode lst =
    let rle count x = if count = 1 then One x else Many (count, x) in   (* count와 원소를 받아 rle 타입으로 리턴*)
    let rec aux count list = 
        match list with
        |[] -> []
        |[x] -> [rle (count+1) x]
        |h1::(h2::_ as t) -> if h1=h2 then aux (count+1) t else rle (count+1) h1 :: aux 0 t
    in aux 0 lst

14. 리스트 원소 중복한 리스트 만들기

let rec duplicate = function   
    |[] -> []
    |h::t -> h::h::(duplicate t)

15. 리스트 원소 n번 반복하는 리스트 만들기

(*
# replicate ["a"; "b"; "c"] 3;;
- : string list = ["a"; "a"; "a"; "b"; "b"; "b"; "c"; "c"; "c"]
*)

let rec replicate lst n = 
    let rec aux l m acc =
        match l with
        |[]->acc
        |h::t as l-> if m=1 then aux t n (h::acc) else aux l (m-1) (h::acc)
    in List.rev (aux lst n [])

16. n번째 원소 삭제하기

let rec drop lst n = 
    match lst with
    |[] -> []
    | h::t -> if n=1 then t else h :: drop t (n-1)

17. 리스트 2개로 나누기. 나눠야할 첫번째 리스트 길이 주어짐

let split lst n = 
    let rec aux l m acc = 
        match l with
        |[] -> List.rev acc,[]
        |h::t as l-> if m=0 then List.rev acc, l else aux t (m-1) (h::acc)
    in aux lst n []

18. 리스트에서 a번째부터 b번째까지로 자른 리스트 반환하기

(* 내 풀이 - 17번의 split 함수 활용, aux는 list에서 앞의 n개 원소까지 자르기 *)
let slice list a b = 
    let rec aux l n = 
        match l with 
        |h::t as l-> if n=0 then l else aux t (n-1)
        |[] -> [] 
    in fst (split (aux list a) b-a+1)
    
(* 다른 사람 풀이1 *)
let slice lst a b =
    (* 앞에서 n개 원소 자른 것 리턴 *)
    let rec drop n = function
        |[]->[]
        |h::t as l-> if n=0 then l else drop (n-1) t
    in
    (* 앞에서 n개 원소까지 리턴 *)
    let rec take n acc = function
        |[] -> []
        |h::t-> if n=0 then List.rev acc else take (n-1) (h::acc) t   
    in take (b-a+1) [] (drop a lst)   

(* 다른 사람 풀이2 *)
(* (n번째 원소 까지만 f를 적용한 fold 결과물, 뒷부분 리스트)의 튜플을 리턴하는 함수 *)
let rec fold_until f acc n = function
    |[] -> acc,[]
    |h::t as l-> if n=0 then acc,l else fold_until f (f h acc) (n-1) t 

let slice list a b = 
    (* 앞 부분은 뭐가 되든 상관 없기 때문에 그냥 빈 배열 리턴하는 함수 f로 정의 *)
    let _, rest = fold_until (fun _ _ -> []) [] a list in
    let taken, _ = fold_until (fun h acc -> h::acc) [] (b-a+1) rest in
    List.rev taken

 

19. 리스트를 왼쪽으로 n번 rotate 하기

(*
# rotate ["a"; "b"; "c"; "d"; "e"; "f"; "g"; "h"] 3;;
- : string list = ["d"; "e"; "f"; "g"; "h"; "a"; "b"; "c"]
# rotate ["a"; "b"; "c"; "d"; "e"; "f"; "g"; "h"] (-2);;
- : string list = ["g"; "h"; "a"; "b"; "c"; "d"; "e"; "f"]
*)

let rotate list n = 
    let len = List.length list in
    let n = if n = 0 then 0 else (n mod len + len) mod len in
    let rec aux n = function
        |[] -> []
        |h::t as l-> if n=0 then l else aux (n-1) (t@[h]) 
    in aux pn list

20. n번째 원소 삭제하기. (0부터 인덱싱)

let rec remove_at n = function
    |[] -> []
    |h::t -> if n=0 then t else h :: remove_at (n-1) t

21. 주어진 원소 n번째 위치에 집어넣기

let rec insert_at x n = function
    |[] -> [x]
    |h::t as l-> if n = 0 then x::l else h :: insert_at x (n-1) t

22. a 부터 b까지 순차적으로 1씩 증가하는 (혹은 감소하는) 리스트 리턴

let range a b = 
    let rec aux a b acc = if a>b then List.rev acc else aux (a+1) b (a::acc) in
    if a<b then aux a b [] else List.rev (aux b a [])

23. 리스트에서 n개 원소 랜덤으로 고르기. Random 모듈의 Random.int 함수 이용! 

let random_select list n =
    (* (리스트에서 n번째 원소, 나머지 리스트) 이렇게 튜플로 리턴 *)
    let rec extract nth acc = function
        |[] -> raise (Failure "no element to extract!")
        |h::t -> if nth = 0 then h, acc @ t else extract (nth -1) (h::acc) t in 
    (* n번 동안 acc에 랜덤원소 모으기 *)    
    let rec aux acc n list = 
        if n=0 then acc 
        else 
        let x, rest = extract (Random.int (List.length list)) [] list in aux (x::acc) (n-1) rest
    in let len = List.length list  
    in aux [] (min n len) list

24. 1..m 사이의 숫자 n개 고른 리스트

let lotto_select n m = random_select (range 1 m) n

25. random permutation

let permutation list = random_select list (List.length list)

26. 모든 n개 조합

(*
# extract 2 ["a"; "b"; "c"; "d"];;
- : string list list =
[["a"; "b"]; ["a"; "c"]; ["a"; "d"]; ["b"; "c"]; ["b"; "d"]; ["c"; "d"]]
*)

let rec extract n list = 
    if n <= 0 then [[]]
    else match list with
    |[] -> []
    |h::t -> let with_h = List.map ( fun l-> h::l ) ( extract (n-1) t ) in 
             let without_h = extract n t in 
             with_h @ without_h

27. 리스트 원소 disjoint subset으로 그루핑 하는 모든 조합

(* 예시
# group ["a"; "b"; "c"; "d"] [2; 1];;
- : string list list list =
[[["a"; "b"]; ["c"]]; [["a"; "c"]; ["b"]]; [["b"; "c"]; ["a"]];
 [["a"; "b"]; ["d"]]; [["a"; "c"]; ["d"]]; [["b"; "c"]; ["d"]];
 [["a"; "d"]; ["b"]]; [["b"; "d"]; ["a"]]; [["a"; "d"]; ["c"]];
 [["b"; "d"]; ["c"]]; [["c"; "d"]; ["a"]]; [["c"; "d"]; ["b"]]]
 *)
 
 (* 내 풀이 *)
 (* 26번 extract 변형 (n개 뽑은 것, 나머지) 튜플 리스트로 리턴 *)
let rec extract n list = 
    if n<=0 then [[],list]
    else match list with
    |[] -> []
    |h::t -> let with_h = List.map (fun (x,y) -> h::x,y ) (extract (n-1) t) in
             let without_h = List.map (fun (x,y) -> x,h::y) (extract n t) in
             with_h @ without_h

let rec group list = function
    |[] -> [[]]
    |h::t -> let h_list = extract h list in
             let result = List.map (fun (h_extracted,rest) -> List.map (fun l -> h_extracted::l) (group rest t)) h_list in 
             List.flatten result
 

 

(* 사이트 풀이 *)
(* This implementation is less streamlined than the one-extraction
    version, because more work is done on the lists after each
    transform to prepend the actual items. The end result is cleaner
    in terms of code, though. *)
  
let group list sizes =
    let initial = List.map (fun size -> size, []) sizes in

(* The core of the function. Prepend accepts a list of groups,
    each with the number of items that should be added, and
    prepends the item to every group that can support it, thus
    turning [1,a ; 2,b ; 0,c] into [ [0,x::a ; 2,b ; 0,c ];
    [1,a ; 1,x::b ; 0,c]; [ 1,a ; 2,b ; 0,c ]]

    Again, in the prolog language (for which these questions are
    originally intended), this function is a whole lot simpler.  *)
    let prepend p list =
      let emit l acc = l :: acc in
      let rec aux emit acc = function
        | [] -> emit [] acc
        | (n, l) as h :: t ->
           let acc = if n > 0 then emit ((n - 1, p :: l) :: t) acc
                     else acc in
           aux (fun l acc -> emit (h :: l) acc) acc t
      in
      aux emit [] list
    in
    let rec aux = function
      | [] -> [initial]
      | h :: t -> List.concat (List.map (prepend h) (aux t))
    in
    let all = aux list in
    (* Don't forget to eliminate all group sets that have non-full
       groups *)
    let complete = List.filter (List.for_all (fun (x, _) -> x = 0)) all in
      List.map (List.map snd) complete;;
prepend  => 원소가 어떤 각 set에 들어가는 ( 해당 set에 들어갈 수 있는 경우만.) + 아무 set에도 안들어가는 모든 경우의 수에 대한 리스트 리턴.
emit => acc 에 새로운 경우의 수 포함하기.
재귀함수 call 마다 emit 함수 재정의 하는데, 그 경우의 수가 지금 head도 포함하도록 하기 위해서 이다.
 ( (1,a) :: [(2,b), (3,c)] ) :: acc 
      ⬆️          ⬆️
  지금 h     다음 h

'시리즈 > OCaml' 카테고리의 다른 글

함수형으로 정렬 알고리즘 구현하기  (0) 2021.11.07
ocaml 문법  (0) 2021.03.18
댓글
공지사항
최근에 올라온 글