AutoHotkey - How to use 3 different possible combinations? - scroll

I want to use these three hotkeys:
- left shift and wheeldown to scroll down twice
- left control and wheel down to scroll down 4 times
- left shift and left control and wheeldown to scroll down 8 times
This is what I have so far but when I input the commands nothing happens
Lshift & wheeldown::
GetKeyState,state1,LShift
GetKeyState,state2,LControl
if (state1 = d) and (state2 = u)
send {wheeldown 2}
if (state2 = d) and (state1 = u)
send {wheeldown 4}
if (state1 = d) and (state2 = d)
send {wheeldown 8}
return

See my same answer in Super User
I would do it this way:
$+WheelDown::SendInput, {WheelDown 2}
$^WheelDown::SendInput, {WheelDown 4}
$+^WheelDown::SendInput, {WheelDown 8}
The $ sign is to prevent a loop where SendInput wheeldown would potentially trigger the same script over and over again since the user holds one of the modifier keys already.

Related

an algorithm for uniquely random patterns of colors for Christmas lights written in AppleScript

This post is ultimately about creating files of random patterns for smart Christmas lights.
I have 7 colors (Red, Green, Blue, Orange, Purple, White, Dark) that I have chosen that I want to semi-randomize.
I have 4 "frames" that is a 5 wide x 4 tall grid that I want to fill with "random" colors, with no color being side my side, or directly up and down from one another and no color repeating between frames in each grid location.
Here are examples of what the totally random frames might look like. Completely random is easy, it is the "no duplicates" random that I am looking for. Again, these frames are NOT what I am ultimately looking for, but they are close. Also the following is purely for visual reference, in AppleScript (or probably any other programming language), the following would be a list of lists.
P G O R D D O G D O P W D O G W O G B W
W B P D O P G R G B R G B O P D R B P G
R G B D O R O G O W P O W D R O G R W D
B G W D B D R G P R R W D O B B O G D W
So here is some code that gets me to the first column of the first frame:
set colorList to {"R", "G", "B", "D", "O", "W", "P"}
set newColumn to {{}, {}, {}, {}, {}}
set previousColor to {}
set previousColumn to {}
repeat with i from 1 to 4 -- for the 4 frames that I need
repeat with j from 1 to 5 -- for the 5 columns that each frame needs
repeat with k from 1 to 4 -- for the 4 values I need in each column
set isRandom to false
set theColor to some item of colorList
if i is not greater than 1 then -- if i is 1 then it is the first frame of the series
if j is not greater than 1 then -- if J is 1 then it is the first column
if k is not greater than 1 then -- if K is 1 then its the first value of the column
set end of (item j of newColumn) to theColor
else
if theColor is not previousColor then -- if k is greater than 1 then ¬
--check the color against the previous color, to make sure they are not the same
set end of (item j of newColumn) to theColor
set previousColor to theColor
else
repeat until isRandom is true
set theColor to some item of colorList
if theColor is not previousColor then
set end of (item j of newColumn) to theColor
set isRandom to true
end if
end repeat
end if
end if
end if
end if
end repeat
end repeat
end repeat
I have tried to go further than, but every time I do, it becomes an incoherent mess of if-then-else statements that I get lost in.
So the script needs to do 3 things before it adds a color to the column:
Make sure that the previous color of the column is not the same
Make sure (if the column we are dealing with is not column 1) that the color is not the same as the previous columns item,
Make sure (if the frame we are dealing with is beyond the first frame) that the color is not the same as it was in the same position as the frame before.
So the question that I am asking is, is their a handler or algorithm here that I am missing (I am no CS Major) that would make this task more straightforward?
That is all you probably NEED to know about my issue, but for reference sake, I am trying to create the patterns for Christmas lights. The colorList in my script actually looks like this:
set colorList to {{255, 0, 0}, {0, 255, 0}, {0, 0, 255}, {255, 255, 255}, {255, 128, 0}, {255, 255, 0},{0, 0, 0}}
So in the end I need a text file that is formatted exactly like this:
255, 0, 0
0, 0, 255
0, 0, 0
255, 255, 0
(there is a space character in front of each line).
I take the text file, convert it over to binary, and then use some Python to send the file to the REST API that the lights are using.
The lights are from a company called Twinkly.
I am just trying to make the process of creating some scenes easier for me (and learn something in the process, hopefully).
Ok, I went ahead and did this without objC. The approach I used was to first build an empty array of an appropriate size and shape, and then run through the array adding colors, blocking the colors from being added to farther-on adjacent cells. It has some tricky aspects — recursions to drill down through the nested lists, using references for speed optimization and list processing, mocking up a version of an indexPath to locate list elements — but you ought to get the idea of it fairly quickly.
I've left the finalization of the script to you; this produces a list of text elements in the property fullList that you can convert into the form you need. If you have any questions, ask in the comments.
property colorList : {"Red", "Green", "Blue", "Orange", "Purple", "White", "Dark"}
property fullList : missing value
property columns : 5
property rows : 4
property blocks : 4
my buildEmptyNestedList()
my setColorsRecursivelyInNestedList:(a reference to fullList) withIndexList:{}
return get fullList
on buildEmptyNestedList()
set fullList to {}
set fullListRef to a reference to fullList
repeat blocks times
set blocksList to {}
repeat rows times
set rowsList to {}
repeat columns times
set end of rowsList to {}
end repeat
copy rowsList to end of blocksList
end repeat
copy blocksList to end of fullListRef
end repeat
end buildEmptyNestedList
on setColorsRecursivelyInNestedList:nestedListElement withIndexList:idxList
local idx
if lists of nestedListElement is equal to {} then -- bottom level: empty list or list of strings
set localColors to my filterColorsByList:nestedListElement
set chosenColor to some item of localColors
set excludePathsList to my processPathList:idxList
repeat with aPathList in excludePathsList
set target to (my getSublistByIndexes:aPathList)
copy chosenColor to end of target
end repeat
set contents of nestedListElement to chosenColor
else
set idx to 1
repeat with aSublist in nestedListElement
copy idxList to nextIdxList
set nextIdxList to nextIdxList & idx
(my setColorsRecursivelyInNestedList:(a reference to (item idx of nestedListElement)) withIndexList:nextIdxList)
set idx to idx + 1
end repeat
end if
end setColorsRecursivelyInNestedList:withIndexList:
on getSublistByIndexes:idxList
set foundList to item (item 1 of idxList) of fullList
repeat with idx in (rest of idxList)
set foundList to item idx of foundList
end repeat
return foundList
end getSublistByIndexes:
on getElementFromList:aNestedList byIndexes:idxArray
set currIdx to item 1 of idxArray
if (count of idxArray) = 1 then
return item currIdx of aNestedList
else
return my getElementFromList:(item currIdx of aNestedList) byIndexes:(rest of idxArray)
end if
end getElementFromList:byIndexes:
on processPathList:aPathList
set pathCheckList to {}
repeat with i from 1 to count of aPathList
copy aPathList to tempList
set item i of tempList to (item i of tempList) + 1
if item i of tempList ≤ item i of {blocks, rows, columns} then
copy tempList to end of pathCheckList
end if
end repeat
return pathCheckList
end processPathList:
on filterColorsByList:aList
set filteredList to {}
set colorListRef to a reference to colorList
repeat with aColor in colorListRef
if aColor is not in aList then
copy aColor as text to end of filteredList
end if
end repeat
return filteredList
end filterColorsByList:

How to stop a reduce operation mid way based on some condition?

How to stop a reduce operation mid way based on some condition?
For example, how can I find an index of maximum value in a list of integers before hitting 0. So in code below, processing list1 should return 4 (5th element), while processing list2 should return 1 (2nd element, because 8 it is the max value in 5, 8, 3 which are the values before 0).
List<Integer> list1 = Arrays.asList(5, 8, 3, 2, 10, 7);
List<Integer> list2 = Arrays.asList(5, 8, 3, 0, 2, 10, 7);
// This will work for list1 but not for list2
IntStream.range(0, list1.size())
.reduce((a, b) -> list1.get(a) < list1.get(b) ? b : a)
.ifPresent(ix -> System.out.println("Index: " + ix));
Reduction is meant to work on an entire set of values without specifying in which order the actual processing is going to happen. In this regard, there is no “stopping at point x” possible as that would imply an order of processing.
So the simple answer is, reduce does not support it, thus, if you want to limit the search range, do the limiting first:
List<Integer> list2 = Arrays.asList(5, 8, 3, 0, 2, 10, 7);
int limit=list2.indexOf(0);
IntStream.range(0, limit>=0? limit: list2.size())
.reduce((a, b) -> list2.get(a) < list2.get(b) ? b : a)
.ifPresent(ix -> System.out.println("Index: " + ix));
Note that you can implement a new kind of Stream that ends on a certain condition using the lowlevel Spliterator interface as described in this answer but I don’t think that this effort will pay off.
Starting with Java 9, you can use:
IntStream.range(0, list2.size())
.takeWhile(ix -> list2.get(ix) != 0)
.reduce((a, b) -> list2.get(a) < list2.get(b) ? b : a)
.ifPresent(ix -> System.out.println("Index: " + ix));
takeWhile depends on the encounter order of the preceding stream. Since IntStream.range produces an ordered stream, it is guaranteed that only the elements before the first mismatching element in encounter order will be used by the subsequent reduction.

How to write register machine code for Fibonacci

I am not sure whether this is the right place to ask this question, but since it involves programming and code, hopefully this is the right place.
I have tried to post on Programmers and Computer Science SE, but they redirected me to this site.
The question is about how can I write a register machine code/program that computes the Fibonacci number. The syntax of the code is actually really simple.
(Below are just for reference only, sorry for the long post)
(For more explanation see the book on Formal Logic: Its Scope And Limits by Richard Carl Jeffrey)
According to Wikipedia a register machine is a generic class of abstract machines used in a manner similar to a Turing machine. A (processor) register is a small amount of storage available as part of a CPU or other digital processor.
We can simplify the problem by modelling the registers as empty buckets, and we can let marbles or rocks to be put into the register ("bucket"). The rule is to add or remove marbles from the bucket to perform computations.
The rules are:
1. A register machine uses of a finite number of buckets, and an unending supply of marbles.
2. Each bucket can be individually identified. The marbles need not be distinguishable.
3. A register machine program is a finite set of instructions:
- To add marble to a bucket (and then to go on to the next instruction).
- Or to take a marble away from a bucket (and to go on to one next
instruction if you can, or another one, if you can’t).
4. The program can be written in a flowchart or in a list of instructions.
Here is an example of a register machine program that performs addition.
Let A, B, C be buckets.
1. (-B; 2,4) means take away one marble from bucket B, go to instruction 2 if can, or 4 if cannot
2. (+A; 3) means add one marble to bucket A, then go to instruction 3
3. (+C; 1) means add one marble to bucket C, then go to instruction 1
4. (-C; 5,-) means take away one marble from bucket C, go to instruction 2 if can, or exit if cannot
5. (+B; 4) means add one marble to bucket B, then go to instruction 4
It is easily shown that suppose we have 3 marbles in bucket A and 2 marbles in bucket B, and 4 marbles in bucket C. After performing this algorithm, there will be |A|+|B| = 3+2 = 5 marbles in bucket A and |B|+|C| = 2+4 = 6 marbles in bucket B.
(I hope the example above is clear enough for illustration purpose)
(Now here comes the question)
Now, I would like to write a register machine code which when given an input of n in bucket A, returns (also in bucket A) the nth Fibonacci number. The Fibonacci numbers are 0 (the 0th), 1 (the 1st), 1 = 0 + 1 (the 2nd), etc. We can use any number of buckets, the goal is to write the code as simple as possible (i.e. with fewest instructions). Fibonacci recurrence relation is F(n)=F(n-1)+F(n-2) given F(0)=0 and F(1)=1.
Here is my attempt and the code:
My idea is to use bucket A as input and finally as output F(n) (since the question requires the output in bucket A), bucket B as the "counter", bucket C as the F(n-1) and bucket D as F(n-2).
1. (+C; 2)
2. (-A; 3,4)
3. (+B; 2)
4. (-D; 5,7)
5. (+A; 4)
6. (-C; 5,7)
7. (-B; 6,-)
But my code only works for up to n=2 sadly, I am struggling to make the code works for any n>2.
I have been thinking for this days and nights, I would appreciate if anyone could help me on this. Please do not hesitate to ask me for clarification if anything is unclear.
Many many thanks in advance!
Here is some example Python code to simulate a register machine that does the task in 11 instructions:
# Store loop counter in 4
# Store F(n) in 1
# Store F(n+1) in 2
# Redistribute 2 to 1 and 3 to get F(n+1)+F(n),0,F(n+1)
# then move back to the order F(n+1),F(n+1)+F(n),0
code={1:(-1,2,3), # Move n to
2:(+4,1), # bin 4 to act as loop counter
3:(+2,4), # Initialise bin 2 with F(1)=1
4:(-4,5,0), # Check for completion
5:(-2,6,8), # redistribute F(n+1)
6:(+1,7), # to bin 1
7:(+3,5), # and bin 3
8:(-1,9,10), # Move bin 1 to
9:(+2,8), # bin 2
10:(-3,11,4), # and bin 3
11:(+1,10)} # to bin 1
def fib(n):
buckets=[0,n,0,0,0]
instruction_pointer = 1
while instruction_pointer:
ins = code[instruction_pointer]
x = ins[0]
if x>0:
buckets[x]+=1
instruction_pointer = ins[1]
else:
x=-x
if buckets[x]>0:
buckets[x]-=1
instruction_pointer = ins[1]
else:
instruction_pointer = ins[2]
return buckets[1]
for n in xrange(10):
print n,fib(n)
I'll take a crack at this. Since you want machine code, the easiest thing to do is write relatively low-level pseudo code, and work from there to the machine code. The major things you need to think about are how to make an if statement, how to set one register equal to another and how to do a loop.
I can practically guarantee there are more efficient ways to do this, but this should be a start.
Here's the pseudocode I would do, assuming you already have your intended number of iterations, >2, in register 'n':
A = 0
B = 1
C = 1
DO
A = B + C // Note, this is really A = 0, A += B, A += C
C = B // Similarly, C = 0, C += B
B = A // B = 0, B += A
WHILE N- > 0
This shouldn't be hard to turn into register code:
1. B+, 2 // B = 1
2. C+, 3 // C = 1
3. A-, 3, 4 // Get A down to 0 - not necessary, just here for clarity
4. D-, 4, 5 // Get temporary variable D down to 0. - not necessary, just here for clarity
5. B-, 6, 8 // Copy B into A (part of adding) and temporary variable D
6. A+, 7 // A = B
7. D+, 5 // D = B
8. C-, 9, 10 // Add C into A = B
9. A+, 8 // A += C
10. N-, 11, -// Check your N count
11. D-, 12, 13 // Copy D (the old B) into C
12. C+, 11 // C = D
13. A-, 14, 5 // Copy A into B
14. B+, 13 // B = A
You can see I kept two lines I didn't need, where I initialized A and D. Since they start at 0 and get read down to 0 each loop, those lines are unnecessary, making the end result this:
1. B+, 2 // B = 1
2. C+, 3 // C = 1
3. B-, 4, 6 // Copy B into A (part of adding) and temporary variable D
4. A+, 5 // A = B
5. D+, 3 // D = B
6. C-, 7, 8 // Add C into A = B
7. A+, 6 // A += C
8. N-, 9, -// Check your N count, or exit
9. D-, 10, 11 // Copy D (the old B) into C
10. C+, 9 // C = D
11. A-, 12, 3 // Copy A into B
12. B+, 12 // B = A
Again, I'm sure it's not perfect, but it should get you close. Hope this helps!
Edit
Re-reading the question, I see that "N" starts out in A. My algorithm doesn't work with that, so I would need to prepend two lines to move it. Also, I see my formatting doesn't match the original OP, so I modified mine to match (give or take spacing and comments):
1. (-A; 2, 3) // Move the "N" value out of A into N
2. (+N; 1) // N = A
3. (+B; 4) // B = 1
4. (+C; 5) // C = 1
5. (-B; 6, 8) // Copy B into A (part of adding) and temporary variable D
6. (+A; 7) // A = B
7. (+D; 5) // D = B
8. (-C; 9, 10) // Add C into A = B
9. (+A; 8) // A += C
10. (-N; 11, -)// Check your N count, or exit
11. (-D; 11, 13) // Copy D (the old B) into C
12. (+C; 11) // C = D
13. (-A; 14, 5) // Copy A into B
14. (+B; 13) // B = A
Ok, I think this will work (if it doesn't let me know and I'll attempt to fix it.) After a function runs, just continue to the next line (so after F1 runs and returns, immediately commence with F2)
Buckets:
A: Initial Value and Final Answer
B: First Counter
C: Second Counter
D: Adding bucket
I: Copy of C for next iteration
J: temporary bucket used for copying
1: +B,3 //loads the first counter
2: +C,4 //loads the second counter
3: -A,3,L
4: F1, 4
5: F2, 5
6: F3, 3
F1:: //makes a copy of C
Fa: -C,Fb,Fd
Fb: +I,Fcv //I is now the copy of C
Fc: +J,Fa
Fd: -J,Ff,Fg
Ff: +C,fd
Fg: return
F2:: //Sums B and C
Fa: -B,Fc,Fb //adds up B
Fb: -C,Fd,Fe //adds up C
Fc: +D,Fa
Fd: +D,Fb
Fe: -D,Ff,Fg
Ff: +C,Fe //Copies result to C
Fg: return
F3:: //copys old C into B
Fa: -I,Fb,Fc
Fb: +B,Fa
Fc: //return
L:: //puts value of C into A for result
Fa: -C,Fb,DONE
Fb: +A,Fa
Write in terms of macro-instructions you can implement.
In fact you only need one macro-instruction for this task, and you already have it (addition). Zeroing out a bucket is trivial (remove marbles one by one), and so is copying bucket's contents to another bucket (zero then add).

Visual Basic Code Issue

Can somebody explain why this doesn't work? (meaning the variable colorChange stays the same throughout function)
Each array (leftRing and rightRing have integer values of numbers ranging from 1 through 4)
Private Sub Solve_Click(sender As System.Object, e As System.EventArgs) Handles Solve.Click
countColorChangesInRings(colorChanges)
RotateLeftRingClockwise()
countColorChangesInRings(colorChanges)
End Sub
Sub countColorChangesInRings(ByRef var As Integer)
Dim colorChangesLeft As Integer = 0
Dim colorChangesRight As Integer = 0
Dim totalChanges As Integer = 0
' Count Color Changes in Left Ring
For i = 1 To 20
If (i = 20) Then
If (leftRing(i) <> leftRing(1)) Then
colorChangesLeft += 1
End If
Else
If (leftRing(i) <> leftRing(i + 1)) Then
colorChangesLeft += 1
End If
End If
Next
' Count Color Changes in Right Ring
For i = 1 To 20
If (i = 20) Then
If (rightRing(i) <> rightRing(1)) Then
colorChangesRight += 1
End If
Else
If (rightRing(i) <> rightRing(i + 1)) Then
colorChangesRight += 1
End If
End If
Next
totalChanges = colorChangesLeft + colorChangesRight
var = totalChanges
End Sub
Sub RotateLeftRingClockwise()
Dim temp As Integer = leftRing(1)
' Rotates Rings clockwise direction
For i = 1 To 20
If (i = 20) Then
leftRing(i) = temp
Else
leftRing(i) = leftRing(i + 1)
End If
Next
End Sub
From what I can see, it will stay the same.
You basically have a circular buffer of values (due to your special handle when i = 20) and you count the number of times the value changes from index to index.
That number is not going to change if you simply rotate the values through the circular buffer.
Take for example, the array {1, 2, 2, 2, 3, 4}. There are four transitions there, 1-2, 2-3, 3-4 and 4-1.
If you then rotate the list clockwise, you get {4, 1, 2, 2, 2, 3} and the transitions are 4-1, 1-2, 2-3 and 3-4.
In other words, the order of the transitions may change, but the quantity does not.
Based on your addition of the RotateLeftRingClockwise code, this is where your problem lies.
Because it simply rotates the left ring, you're not handling the intersection points of the Hungarian rings correctly. Rotating the left ring would, as well as moving the values around the left ring, change two of the array elements in the right ring (and vice versa), as per the diagram below:
array element 1 (in a 32-element array)
|
+---+---+
| |
V V
LLL RRR
LL LL RR RR
L * R <- shared cell is left(29)/right(5)
L R L R
L R L R
L R L R
L R L R
L R L R
L R L R
L R L R
L * R <- shared cell is left(21)/right(13)
LL LL RR RR
\ LLL RRR /
\ /
\----->>>-----/
Direction of
increasing
array index
Because your rotate code does not do this, the transition count of both rings will remain identical and hence the sum of them will not change.
For example, say the top-middle cells are array element 1 (as shown) and it increases in a anti-clockwise direction (as your code seems to indicate from the fact that a clockwise rotation shifts from ring[i+1] to ring[i]).
What your code needs to do is first rotate the left ring and then force the new values for the * cells into the right ring, something like:
Sub RotateLeftRingClockwise()
Dim temp As Integer = leftRing(1)
For i = 1 To 19
leftRing(i) = leftRing(i + 1)
Next
leftRing(20) = temp
rightRing( 5) = leftRing(29)
rightRing(13) = leftRing(21)
End Sub
Those array indexes in the last two lines are specific to my diagram above (32 cells rather than 20), you'll need to adjust them for your own puzzle.
If you make those changes (and similar ones to your other three rotation functions), you should find that the function starts returning different values. Specifically, rotating the left ring will leave the left value unchanged but should change the right value as different balls come into the intersection point.

Algorithm for combining different age groups together based on their values

Let's say we have an array of age groups and an array of the number of people in each age group
For example:
Ages = ("1-13", "14-20", "21-30", "31-40", "41-50", "51+")
People = (1, 10, 21, 3, 2, 1)
I want to have an algorithm that combines these age groups with the following logic if there are fewer than 5 people in each group. The algorithm that I have so far does the following:
Start from the last element (e.g., "51+") can you combine it with the next group? (here "41-50") if yes add the numbers 1+2 and combine their labels. So we get the following
Ages = ("1-13", "14-20", "21-30", "31-40", "41+")
People = (1, 10, 21, 3, 3)
Take the last one again (here is "41+"). Can you combine it with the next group (31-40)? the answer is yes so we get:
Ages = ("1-13", "14-20", "21-30", "31+")
People = (1, 10, 21, 6)
since the group 31+ now has 6 members we cannot collapse it into the next group.
we cannot collapse "21-30" into the next one "14-20" either
"14-20" also has 10 people (>5) so we don't do anything on this either
for the first one ("1-13") since we have only one person and it is the last group we combine it with the next group "14-20" and get the following
Ages = ("1-20", "21-30", "31+")
People = (11, 21, 6)
I have an implementation of this algorithm that uses many flags to keep track of whether or not any data is changed and it makes a number of passes on the two arrays to finish this task.
My question is if you know any efficient way of doing the same thing? any data structure that can help? any algorithm that can help me do the same thing without doing too much bookkeeping would be great.
Update:
A radical example would be (5,1,5)
in the first pass it becomes (5,6) [collapsing the one on the right into the one in the middle]
then we have (5,6). We cannot touch 6 since it is larger than our threshold:5. so we go to the next one (which is element on the very left 5) since it is less than or equal to 5 and since it is the last one on the left we group it with the one on its right. so we finally get (11)
Here is an OCaml solution of a left-to-right merge algorithm:
let close_group acc cur_count cur_names =
(List.rev cur_names, cur_count) :: acc
let merge_small_groups mini l =
let acc, cur_count, cur_names =
List.fold_left (
fun (acc, cur_count, cur_names) (name, count) ->
if cur_count <= mini || count <= mini then
(acc, cur_count + count, name :: cur_names)
else
(close_group acc cur_count cur_names, count, [name])
) ([], 0, []) l
in
List.rev (close_group acc cur_count cur_names)
let input = [
"1-13", 1;
"14-20", 10;
"21-30", 21;
"31-40", 3;
"41-50", 2;
"51+", 1
]
let output = merge_small_groups 5 input
(* output = [(["1-13"; "14-20"], 11); (["21-30"; "31-40"; "41-50"; "51+"], 27)] *)
As you can see, the result of merging from left to right may not be what you want.
Depending on the goal, it may make more sense to merge the pair of consecutive elements whose sum is smallest and iterate until all counts are above the minimum of 5.
Here is my scala approach.
We start with two lists:
val people = List (1, 10, 21, 3, 2, 1)
val ages = List ("1-13", "14-20", "21-30", "31-40", "41-50", "51+")
and combine them to a kind of mapping:
val agegroup = ages.zip (people)
define a method to merge two Strings, describing an (open ended) interval. The first parameter is, if any, the one with the + in "51+".
/**
combine age-strings
a+ b-c => b+
a-b c-d => c-b
*/
def merge (xs: String, ys: String) = {
val xab = xs.split ("[+-]")
val yab = ys.split ("-")
if (xs.contains ("+")) yab(0) + "+" else
yab (0) + "-" + xab (1)
}
Here is the real work:
/**
reverse the list, combine groups < threshold.
*/
def remap (map: List [(String, Int)], threshold : Int) = {
def remap (mappings: List [(String, Int)]) : List [(String, Int)] = mappings match {
case Nil => Nil
case x :: Nil => x :: Nil
case x :: y :: xs => if (x._2 > threshold) x :: remap (y :: xs) else
remap ((merge (x._1, y._1), x._2 + y._2) :: xs) }
val nearly = (remap (map.reverse)).reverse
// check for first element
if (! nearly.isEmpty && nearly.length > 1 && nearly (0)._2 < threshold) {
val a = nearly (0)
val b = nearly (1)
val rest = nearly.tail.tail
(merge (b._1, a._1), a._2 + b._2) :: rest
} else nearly
}
and invocation
println (remap (agegroup, 5))
with result:
scala> println (remap (agegroup, 5))
List((1-20,11), (21-30,21), (31+,6))
The result is a list of pairs, age-group and membercount.
I guess the main part is easy to understand: There are 3 basic cases: an empty list, which can't be grouped, a list of one group, which is the solution itself, and more than one element.
If the first element (I reverse the list in the beginning, to start with the end) is bigger than 5 (6, whatever), yield it, and procede with the rest - if not, combine it with the second, and take this combined element and call it with the rest in a recursive way.
If 2 elements get combined, the merge-method for the strings is called.
The map is remapped, after reverting it, and the result reverted again. Now the first element has to be inspected and eventually combined.
We're done.
I think a good data structure would be a linked list of pairs, where each pair contains the age span and the count. Using that, you can easily walk the list, and join two pairs in O(1).

Resources