Самый идиоматический c способ разобрать список с дырой посередине в F # - PullRequest
1 голос
/ 04 августа 2020

Я разбираю файл Excel, который организован таким образом:

header1
header2
data
data
[...]
data
one blank line
data
data
[...]
data
one blank line

Итак, у нас есть заголовок, который нужно пропустить, некоторые данные переменной длины, пустая строка, некоторые данные переменной длины и пустая строка, обозначающая конец интересующей зоны.

Два блока данных анализируются идентичным образом, и они должны оказаться в одном списке, но важно то, что я знаю индекс пустой строки посередине (это список, который обрабатывается от середины, вверх или вниз).

Есть два предостережения:

  • верхняя и нижняя части не имеют одинаковой длины.
  • Одна из сторон может быть пустой.

Прямо сейчас у меня есть не очень чистая реализация, поскольку она дублирует код :

let gridRowsUp =
    gridExcel
    |> List.skip 2
    |> List.takeWhile (fun rowData       -> rowData |> Seq.exists(fun x -> not (String.IsNullOrEmpty(x))))
    |> List.mapi      (fun index rowData -> parseGridLayer (index + 2) rowData)

// get the index of the middle row
let middleRow = 2 + gridRowsUp.Length

// get the bottom part of the grid
let gridRowsDown =
    gridExcel
    |> List.skip      (1 + middleRow)
    |> List.takeWhile (fun rowData       -> rowData |> Seq.exists(fun x -> not (String.IsNullOrEmpty(x))))
    |> List.mapi      (fun index rowData -> parseGridLayer (index + 1 + middleRow) rowData)

let gridData = gridRowsUp @ gridRowsDown

В идеале, я хотел бы обрабатывать строки данных за один go, но пропустить и записать местоположение пустой строки посередине.

Я подумал о найти первую пустую строку и перестроить список с извлеките его (так как теперь я знаю индекс), но он запутан, так как мне нужно найти первый, затем найти второй, чтобы знать, где остановиться (внизу может быть много неиспользуемых дополнительных строк), затем снова построить один список . А удаление элемента в середине списка в F # не идеально.

Как я могу сделать это более упорядоченным?

Ответы [ 3 ]

1 голос
/ 04 августа 2020

Использование @ mike67 testdata. Может быть, что-то вроде этого?

    let gridExcel = [
    "header1";  "header2";   "data1";    "data2";    "data3";    "data4";    "";
    "data5";    "data6";     "";
    "data7";    "data8";     "data9";    "data10";    "";
    "data11";   "data12";    "data13";   "data14";   "data15";    "";
    "data16";   "data17"
    ]

let (|IsNullOrEmpty|Data|) cell = 
    if String.IsNullOrEmpty cell
    then IsNullOrEmpty 
    else Data

let parseRow rCell = "P" + rCell    

let rec parseOrIndexEmpty currentIndex emptyIndexes processed grid = 
    match grid with
    | [] -> (List.rev processed, List.rev emptyIndexes) 
    | row::rows -> 
        match row with 
        //parse Row and continue
        | Data -> 
            parseOrIndexEmpty (currentIndex + 1) emptyIndexes ((parseRow row)::processed) rows 
        //Collect index and continue if it's not beyond second blank line
        | IsNullOrEmpty -> 
            let currentEmptyIndexes = (currentIndex::emptyIndexes)
            if currentEmptyIndexes.Length < 2
            then parseOrIndexEmpty (currentIndex + 1) currentEmptyIndexes processed rows 
            else (List.rev processed, List.rev currentEmptyIndexes)

let numOfHeaders = 2 
let (processed,emptyIndexes) =    
    gridExcel 
    |> List.skip numOfHeaders 
    |> parseOrIndexEmpty numOfHeaders List.empty List.empty

printfn "MiddleRow Index: %d" emptyIndexes.[0]


printfn "Processsed:\n%A" processed

Output:           

MiddleRow Index: 6
Processsed:
["Pdata1"; "Pdata2"; "Pdata3"; "Pdata4"; "Pdata5"; "Pdata6"]

Если вам не нужен индекс, его можно значительно упростить.

1 голос
/ 04 августа 2020

Я подумал, что al oop будет лучше, чем повторение кода. Этот код должен уточнить лог c.

// main dataset
let gridExcel = [
"header1";  "header2";   "data1";    "data2";    "data3";    "data4";    "";
"data5";    "data6";     "";
"data7";    "data8";     "data9";    "data10";    "";
"data11";   "data12";    "data13";   "data14";   "data15";    "";
"data16";   "data17"
]

let mutable blanks = []  // index of blank lines
let mutable allrows = []  // all non-blanks rows (no headers)

let mutable ctr = 2  // row index of main data
let mutable run = true

while (run) do
   let gridRows = 
       gridExcel
       |> List.skip ctr  // skip previous data
       |> List.takeWhile (fun rowData       -> not (String.IsNullOrEmpty(rowData)))  // while not empty line
       |> List.mapi      (fun index rowData -> "xx" + rowData)   // process entry
   allrows <- allrows@gridRows    // add to list total
   ctr <- ctr + gridRows.Length   // add to skip ctr
   if (ctr < gridExcel.Length) then blanks <- blanks@[ctr]   // store blank line index
   ctr <- ctr + 1  // skip blank line
   if (ctr >= gridExcel.Length) then run <- false  // done main list
   
printfn "Blanks: %A\n" blanks   // indexes of blanks lines
printfn "Processed Data:\n%A" allrows  // all processed rows

Вывод

Blanks: [6; 9; 14; 20]

Processed Data:
["xxdata1"; "xxdata2"; "xxdata3"; "xxdata4"; "xxdata5"; "xxdata6"; "xxdata7";
 "xxdata8"; "xxdata9"; "xxdata10"; "xxdata11"; "xxdata12"; "xxdata13";
 "xxdata14"; "xxdata15"; "xxdata16"; "xxdata17"]
0 голосов
/ 06 августа 2020

А как насчет рекурсивной функции вроде:

let parseGridLayer index rowData = sprintf "%d: %s" index rowData    
let isValidRow row = not (String.IsNullOrEmpty(row))

let extractData excelData = 
    let rec extracter index data result blankCount = 
        match blankCount with
        | 2 -> result 
        | _ ->
            match data with
            | [] -> result
            | row::remaingRows ->
                match isValidRow row with
                | true -> extracter (index + 1) remaingRows ((parseGridLayer index row)::result) blankCount
                | false -> extracter (index + 1) remaingRows result (blankCount + 1)

    extracter 2 (excelData |> List.skip 2) [] 0 |> List.rev


let gridExcel = [ 
    "header1";  "header2";   "data1";    "data2";    "data3";    "data4"; 
    "data5";    "data6"; 
    "data7";    "data8";     "data9";    "data10";    
    "";
    "data11";   "data12";    "data13";   "data14";   "data15";  
    "data16";   "data17"; 
    ""
]
printfn "%A" (extractData gridExcel)
...