patternMinor
Compute a value (and update a CSV record), based on prior value
Viewed 0 times
updatecomputecsvvaluerecordbasedpriorand
Problem
The F# code below embodies the two specific questions I have. It is part of a working transformation utility, the entirety of which, including sample data, is in this gist:
http://bit.ly/2i3kUwF.
Generically, the task is: given a file of discrete records, compute a value for a subsequent record that depends on a value of the previous record. Since I want a transformed stream and not just a computed summary value, this seems a perfect use of Seq.scan. Here is the relevant portion of main:
But I have some questions concerning proper usage of Seq.scan.
Since, by definition, there is no initial state prior to reading the first record, and because I want to return an entire record to the stream, I initialize state to an empty string (ignore the "target" for a bit - that is question 2). This creates the need to distinguish between this special case of the of the state being the empty string that first time through, vs all other invocations:
```
let updateBarValue (state:string, target:int32) (line:string) =
// This is the "folder" function to maintain state for the scan function in main.
// Since the first time through there will be no accumulated state, the inital state is set to "".
// The match below handles the decision for first time through, or all other cases.
//
// From doc, usage for Seq.scan:
// Seq.scan folder state source
//
// Because scan accepts a single parameter for state AND we need the target passed from the command line
// invocation, we bundle state and target into a tuple.
// However, target remains unchanged, while the true state is continuall
http://bit.ly/2i3kUwF.
Generically, the task is: given a file of discrete records, compute a value for a subsequent record that depends on a value of the previous record. Since I want a transformed stream and not just a computed summary value, this seems a perfect use of Seq.scan. Here is the relevant portion of main:
File.ReadLines(inFile)
|> Seq.map appendEmptyColumnForBarValue // add the col to hold the new barValue
|> Seq.scan updateBarValue ("", target) // compute the barValue, based on prev state and target
|> Seq.skip 1 // exclude initial blank state above from output
|> Seq.iter outFile.WriteLineBut I have some questions concerning proper usage of Seq.scan.
Since, by definition, there is no initial state prior to reading the first record, and because I want to return an entire record to the stream, I initialize state to an empty string (ignore the "target" for a bit - that is question 2). This creates the need to distinguish between this special case of the of the state being the empty string that first time through, vs all other invocations:
```
let updateBarValue (state:string, target:int32) (line:string) =
// This is the "folder" function to maintain state for the scan function in main.
// Since the first time through there will be no accumulated state, the inital state is set to "".
// The match below handles the decision for first time through, or all other cases.
//
// From doc, usage for Seq.scan:
// Seq.scan folder state source
//
// Because scan accepts a single parameter for state AND we need the target passed from the command line
// invocation, we bundle state and target into a tuple.
// However, target remains unchanged, while the true state is continuall
Solution
First question - no, there is no way to get rid of this "initial" value completely. There is, however, a way to make it more intuitive that the values is "missing" - the
Second question - easy: just make
As an aside, I noticed your tendency to tuple function arguments. That is generally considered bad form, unless arguments inherently "belong together" - e.g. point coordinates, - because curried arguments give more freedom. To this end, I would make
Option type. Pass None as initial state, and return Some from then on. This will also allow you to elegantly get rid of that initial value by using Seq.choose.Second question - easy: just make
target first parameter of the folder function and pass it in via partial application:let updateBarValue target state (line:string) =
let effectiveState =
match state with
| None -> line
| Some s -> s
Some (buildLineWithBarValue (effectiveState, target) line)
File.ReadLines(inFile)
|> Seq.map appendEmptyColumnForBarValue // add the col to hold the new barValue
|> Seq.scan (updateBarValue target) None // compute the barValue, based on prev state and target
|> Seq.choose id // get rid of the initial input
|> Seq.iter outFile.WriteLineAs an aside, I noticed your tendency to tuple function arguments. That is generally considered bad form, unless arguments inherently "belong together" - e.g. point coordinates, - because curried arguments give more freedom. To this end, I would make
buildLineWithBarValue's arguments curried as well.Code Snippets
let updateBarValue target state (line:string) =
let effectiveState =
match state with
| None -> line
| Some s -> s
Some (buildLineWithBarValue (effectiveState, target) line)
File.ReadLines(inFile)
|> Seq.map appendEmptyColumnForBarValue // add the col to hold the new barValue
|> Seq.scan (updateBarValue target) None // compute the barValue, based on prev state and target
|> Seq.choose id // get rid of the initial input
|> Seq.iter outFile.WriteLineContext
StackExchange Code Review Q#150755, answer score: 6
Revisions (0)
No revisions yet.