10
\$\begingroup\$

Here is my functional approach to split a list into sub lists, each having non-decreasing numbers, so the input

\$[1; 2; 3; 2; 4; 1; 5;]\$

computes to

\$[[1; 2; 3]; [2; 4]; [1; 5]]\$

The code works, but I need a while to understand it. I would prefer a solution that is more clear. Is there a way to make this more elegant or readable? Everything is allowed, F# mutables as well. Or do I just get used more to functional code reading?

The subsets are called "run"s in the code below.

// returns a single run and the remainder of the input
let rec takerun input =
 match input with
 | [] -> [], []
 | [x] -> [x], []
 | x :: xr -> if x < xr.Head then 
 let run, remainder = takerun xr
 x :: run, remainder
 else [x], xr
// returns the list of all runs
let rec takeruns input =
 match takerun input with
 | run, [] -> [run]
 | run, rem -> run :: takeruns rem
let runs = takeruns [1; 2; 3; 2; 4; 1; 5;]
> val runs : int list list = [[1; 2; 3]; [2; 4]; [1; 5]]

Edit:

Considering the helpful feedback I ended up with this reusable code. And got more used to functional programming, comparing imperative alternatives I meanwhile find the pure functional approach more readable. This version is good readable, although not tail recursive. For the small lists I had to deal with, readability was preferred.

// enhance List module
module List = 
 // splits list xs into 2 lists, based on f(xn, xn+1)
 let rec Split f xs =
 match xs with
 | [] -> [], []
 | [x] -> [x], []
 | x1 :: x2 :: xr when f x1 x2 -> [x1], x2 :: xr // split on first f(xn, xn+1)
 | x :: xr -> let xs1, xs2 = Split f xr
 x :: xs1, xs2
// Now takruns becomes quite simple
let rec takeruns input =
 match List.Split (>) input with
 | run, [] -> [run]
 | run, rem -> run :: takeruns rem
let runs = takeruns [1; 2; 3; 2; 4; 1; 5;]
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jan 25, 2014 at 0:44
\$\endgroup\$
2
  • 1
    \$\begingroup\$ "Consecutive" would mean two integers (a, b) such that b = a + 1. It doesn't change the essence of the problem, though. \$\endgroup\$ Commented Jan 25, 2014 at 2:00
  • \$\begingroup\$ thx for the heads up \$\endgroup\$ Commented Jan 25, 2014 at 10:06

4 Answers 4

3
\$\begingroup\$

A version that uses List.foldBack and List.pairwise

let split lst =
 let folder (a, b) (cur, acc) = 
 match a with
 | _ when a < b -> a::cur, acc
 | _ -> [a], cur::acc
 let result = List.foldBack folder (List.pairwise lst) ([List.last lst], []) 
 (fst result)::(snd result)
printfn "%A" (split [1; 2; 3; 2; 2; 4; 1; 5;])
answered Sep 18, 2018 at 14:53
\$\endgroup\$
4
\$\begingroup\$

Haskell:

import Data.List.HT -- or Data.List.Grouping
takeruns xs@(x:xss) = map (map fst) pss
 where pss = segmentAfter (uncurry (>)) $ zip xs xss

EDIT
Example:

xs = [1, 2, 3, 2, 4, 1, 5]
xss = [2, 3, 2, 4, 1, 5]
zip xs xss = [(1, 2), (2, 3), (3, 2), (2, 4), (4, 1)]
uncurry (>) (1, 2) == 1 > 2 = False
segmentAfter ... = [
 [(1, 2) /* False */, (2, 3) /* False */, (3, 2) /* True */],
 [(2, 4) /* False */, (4, 1) /* True */],
 []
]
map (map fst) (segmentAfter ...) = [[1, 2, 3], [2, 4], []]

And, it turns out that my function is wrong :) Correct version:

takeruns xs = map (map snd) pss
 where pss = segmentAfter (uncurry (>)) $ zip (minBound:xs) xs
answered Jan 25, 2014 at 6:27
\$\endgroup\$
6
  • 3
    \$\begingroup\$ This is not exactly a helpful code review. At least you should try and explain the solution in haskell and in how far it could be applied to other functional language as well. \$\endgroup\$ Commented Jan 25, 2014 at 8:45
  • 1
    \$\begingroup\$ @ChrisWue I really don't know what to add to my answer. TS asked for "more readable or elegant version" -- my amost one-liner is not very readable, but probably the shortest. Initially TS mentioned Haskell \$\endgroup\$ Commented Jan 25, 2014 at 8:56
  • \$\begingroup\$ thx for the helpful input. i realize that plain f# simply lacks some reusable functional basics, like Haskell's segmentAfter which can be found at hackage.haskell.org/package/utility-ht-0.0.1/docs/src/… \$\endgroup\$ Commented Jan 25, 2014 at 9:19
  • \$\begingroup\$ leventov, perhaps you could explain your code a bit more? How does it do what it does? \$\endgroup\$ Commented Jan 25, 2014 at 19:23
  • \$\begingroup\$ @SimonAndréForsberg see edit \$\endgroup\$ Commented Jan 25, 2014 at 19:40
4
\$\begingroup\$

Since there is only one solution using sequences and one in Haskell, I thought that I still might post my code:

let partition list =
 let rec aux =
 function
 | trg,acc,[] -> acc::trg
 | trg,a::acc,x::xs when x<a 
 -> aux ((a::acc)::trg,[x],xs)
 | trg,acc,x::xs -> aux (trg,x::acc,xs)
 aux ([],[],list)|> List.map (List.rev) |> List.rev

Of course, running through the list twice as in the last line is bad (performance wise), however this could be easily solved by a custom revMap function that reverses and maps at the same time.

Marc-Andre
6,7795 gold badges39 silver badges65 bronze badges
answered Apr 9, 2014 at 19:57
\$\endgroup\$
4
\$\begingroup\$

Using List.foldBack

let insert e state = 
 match state with
 | cur::rest ->
 match cur with
 | h::_ when e < h -> (e::cur)::rest // add e to current list
 | _ -> [e]::state // start a new list 
 | _ -> [[e]]
List.foldBack insert [1;2;3;2;4;1;5;] []
val insert : e:'a -> state:'a list list -> 'a list list when 'a : comparison
val it : int list list = [[1; 2; 3]; [2; 4]; [1; 5]] 
answered Apr 24, 2014 at 15:42
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.