Slice/Group a sequence of equal chars in F#

前端 未结 6 1798
难免孤独
难免孤独 2020-12-21 15:34

I need to extract the sequence of equal chars in a text.

For example: The string \"aaaBbbcccccccDaBBBzcc11211\" should be converted to a list of strings

相关标签:
6条回答
  • 2020-12-21 15:40

    When you're folding, you'll need to carry along both the previous value and the accumulator holding the temporary results. The previous value is wrapped as option to account for the first iteration. Afterwards, the final result is extracted and reversed.

    "aaaBbbcccccccDaBBBzcc11211"
    |> Seq.map string
    |> Seq.fold (fun state ca ->
        Some ca,
        match state with
        | Some cb, x::xs when ca = cb -> x + ca::xs
        | _, xss ->                      ca::xss )
        (None, [])
    |> snd 
    |> List.rev
    // val it : string list =
    //   ["aaa"; "B"; "bb"; "ccccccc"; "D"; "a"; "BBB"; "z"; "cc"; "11"; "2"; "11"]
    
    0 讨论(0)
  • 2020-12-21 15:41

    As someone other here:

    Know thy fold ;-)

    let someString = "aaaBbbcccccccDaBBBzcc11211"
    
    let addLists state elem = 
        let (p, ls) = state
        elem, 
        match p = elem, ls with
        | _, [] -> [ elem.ToString() ]
        | true, h :: t -> (elem.ToString() + h) :: t
        | false, h :: t -> elem.ToString() :: ls
    
    someString
    |> Seq.fold addLists ((char)0, [])
    |> snd
    |> List.rev
    
    0 讨论(0)
  • 2020-12-21 15:46

    How about with groupby

    "aaaBbbcccccccD"
    |> Seq.groupBy id
    |> Seq.map (snd >> Seq.toArray)
    |> Seq.map (fun t -> new string (t))
    

    If you input order matters, here is a method that works

    "aaaBbbcccccccDaBBBzcc11211"
    |> Seq.pairwise
    |> Seq.toArray
    |> Array.rev
    |> Array.fold (fun (accum::tail) (ca,cb) -> if ca=cb then System.String.Concat(accum,string ca)::tail else string(ca)::accum::tail)   (""::[])
    
    0 讨论(0)
  • 2020-12-21 15:48

    This one is also based on recursion though the matching gets away with smaller number of checks.

    let chop (txt:string) =
        let rec chopInner txtArr (word: char[]) (res: List<string>) =
            match txtArr with
            | h::t when word.[0] = h -> chopInner t (Array.append word [|h|]) res
            | h::t when word.[0] <> h -> 
                let newWord = word |> (fun s -> System.String s)
                chopInner t [|h|] (List.append res [newWord])
            | [] -> 
                let newWord = word |> (fun s -> System.String s)
                (List.append res [newWord])
    
        let lst = txt.ToCharArray() |> Array.toList 
        chopInner lst.Tail [|lst.Head|] []
    

    And the result is as expected:

    val text : string = "aaaBbbcccccccDaBBBzcc11211"
    > chop text;;
    val it : string list =
      ["aaa"; "B"; "bb"; "ccccccc"; "D"; "a"; "BBB"; "z"; "cc"; "11"; "2"; "11"]
    
    0 讨论(0)
  • 2020-12-21 15:52

    Here a completely generic implementation:

    let group xs =
        let folder x = function
            | [] -> [[x]]
            | (h::t)::ta when h = x -> (x::h::t)::ta
            | acc -> [x]::acc
        Seq.foldBack folder xs []
    

    This function has the type seq<'a> -> 'a list list when 'a : equality, so works not only on strings, but on any (finite) sequence of elements, as long as the element type supports equality comparison.

    Used with the input string in the OP, the return value isn't quite in the expected shape:

    > group "aaaBbbcccccccDaBBBzcc11211";;
    val it : char list list =
      [['a'; 'a'; 'a']; ['B']; ['b'; 'b']; ['c'; 'c'; 'c'; 'c'; 'c'; 'c'; 'c'];
       ['D']; ['a']; ['B'; 'B'; 'B']; ['z']; ['c'; 'c']; ['1'; '1']; ['2'];
       ['1'; '1']]
    

    Instead of a string list, the return value is a char list list. You can easily convert it to a list of strings using a map:

    > group "aaaBbbcccccccDaBBBzcc11211" |> List.map (List.toArray >> System.String);;
    val it : System.String list =
      ["aaa"; "B"; "bb"; "ccccccc"; "D"; "a"; "BBB"; "z"; "cc"; "11"; "2"; "11"]
    

    This takes advantage of the String constructor overload that takes a char[] as input.

    As initially stated, this implementation is generic, so can also be used with other types of lists; e.g. integers:

    > group [1;1;2;2;2;3;4;4;3;3;3;0];;
    val it : int list list = [[1; 1]; [2; 2; 2]; [3]; [4; 4]; [3; 3; 3]; [0]]
    
    0 讨论(0)
  • 2020-12-21 15:58

    Just interesting why everyone publishing solutions based on match-with? Why not go plain recursion?

    let rec groups i (s:string) =
      let rec next j = if j = s.Length || s.[i] <> s.[j] then j else next(j+1)
      if i = s.Length then []
      else let j = next i in s.Substring(i, j - i) :: (groups j s)
    
    "aaaBbbcccccccDaBBBzcc11211" |> groups 0
    val it : string list = ["aaa"; "B"; "bb"; "ccccccc"; "D"; "a"; "BBB"; "z"; "cc"; "11"; "2"; "11"]
    
    0 讨论(0)
提交回复
热议问题