Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I find out the size of the group a given element exists in?

Tags:

list

haskell

If I have a list like such:

lst = [4,4,4,5,3,3,9]

I can use group to create this: [[4,4,4],[5],[3,3],[9]].

If I have an index pointing into the list, e.g., i = 4, which points at the first 3 element, how can I best write a function taking a list and index, and returning the size of the group to which the index points (in this case, 2, the length of the group of 3s)?

I know I can write it pretty imperatively, and that I could also probably knock something together with map length . group and count how many groups I pass through when locating my index, but it seems to me that there must be a nicer way to do this. All suggestions welcome.

like image 738
Allan Avatar asked Dec 05 '25 14:12

Allan


2 Answers

Here's a solution in rather explicit stages so I can explain each step.

Make the grouped list:

lst = [4,4,4,5,3,3,9]
lst1 = group lst

Next, replace the groups with lengths:

lst2 = map length lst1

This gives us lst2 = [3,1,2,1]

Now replace the lengths with lists of that length that also contain the length in each position:

lst3 = map (\l -> replicate l l) lst2

Now we have lst3 = [[3,3,3],[1],[2,2],[1]], which is nearly what we need but we don't want the inner structure:

lst4 = concat lst3

Now we have something the same length as the original list, but with group lengths in each position instead of the original values. So lst4!!4 = 2.

This solution is a simplification of my original solution using an idea from the comments.

As also pointed out in that comment, a further couple of tweaks are possible - elide the two maps and the concat as concatMap, and use join in the (->) r instance of Monad to "simplify" \l -> replicate l l, giving this for the whole pipeline:

concatMap (join replicate . length) . group $ lst
like image 172
GS - Apologise to Monica Avatar answered Dec 08 '25 22:12

GS - Apologise to Monica


If you have a function to compute the cumulative sum, then you can make it fairly straightforward:

cumsum :: Num a => [a] -> [a]
cumsum xs = go xs 0
    where
        go [] _ = []
        go (x:xs) i = let n = x + i in n : go xs n

groupSizeAt :: [[a]] -> Int -> Int
groupSizeAt xs i =
    let groups = zip xs $ cumsum $ map length xs
        targetGroup = head $ dropWhile ((<= i) . snd) groups
    in length $ fst targetGroup

The logic behind this is to compute the cumulative sum of the lengths of the groups, then drop groups until this cumulative sum is greater than the index you're searching for, then grab the first one, drop the cumulative sum, and compute the length again. There's a bit of redundancy, but nothing that would be too expensive for small groups (if your group size is on the order of 100s or 1000s, you might want to modify it to keep from having to compute the length of the target group twice)

like image 43
bheklilr Avatar answered Dec 09 '25 00:12

bheklilr



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!