在F#中按字母顺序排序Excel行(Office.Interop)

时间:2013-03-05 12:29:35

标签: excel sorting f# office-interop

我在Visual Studio 2010中使用Excel互操作来尝试按字母顺序对所有这些数据行进行排序。有些已按字母顺序排列。

Accountancy Graduate, Trainees  Banking, Insurance, Finance
Accountancy Graduate, Trainees  Customer Services
Accountancy Graduate, Trainees  Education
Accountancy Graduate, Trainees  Health, Nursing
Accountancy Graduate, Trainees  Legal
Accountancy Graduate, Trainees  Management Consultancy
Accountancy Graduate, Trainees  Media, New Media, Creative
Accountancy Graduate, Trainees  Oil, Gas, Alternative Energy
Accountancy Graduate, Trainees  Public Sector & Services
Accountancy Graduate, Trainees  Recruitment Sales
Accountancy Graduate, Trainees  Secretarial, PAs, Administration
Accountancy Graduate, Trainees  Telecommunications
Accountancy Graduate, Trainees  Transport, Logistics

我的代码的当前版本如下(我将代码在交互式工作之前将其放入fs文件中)。

#r "office.dll"
#r "Microsoft.Office.Interop.Excel.dll"

open System;;
open System.IO;;
open Microsoft.Office.Interop.Excel;;




let app = new ApplicationClass(Visible = true)
let inputBook = app.Workbooks.Open @"C:\Users\simon.hayward\Dropbox\F# Scripts\TotalJobsSort\SortData.xlsx" //work
//let inputBook = app.Workbooks.Open @"C:\Users\Simon Hayward\Dropbox\F# Scripts\TotalJobsSort\SortData.xlsx"  //home
let outputBook = app.Workbooks.Add()
let inSheet = inputBook.Worksheets.[1] :?> _Worksheet
let outSheet = outputBook.Worksheets.[1] :?> _Worksheet

let rows = inSheet.UsedRange.Rows.Count;;

let toSeq (range : Range) =
      seq {
           for r in 1 .. range.Rows.Count do
                  for c in 1 .. range.Columns.Count do
                      let cell = range.Item(r, c) :?> Range
                      yield cell
              }
for i in 1 .. rows do 

      let mutable row = inSheet.Cells.Rows.[i] :?> Range

      row |> toSeq |> Seq.map (fun x -> x.Value2.ToString()) |> Seq.sort |> 

     (outSheet.Cells.Rows.[i] :?> Range).Value2 <- row.Value2;;



app.Quit();;

但是类型存在问题。退出命令之前的最后一行

(outSheet.Cells.Rows.[i] :?> Range).Value2 <- row.Value2;;

智能感应用红色下划线,我得到的错误是

“此表达式应具有seq - &gt;'a类型,但此处具有类型单位”。

我得到了VS试图告诉我的内容,但我已经多次尝试解决这个问题,而且我似乎无法解决类型问题。

有人可以建议我如何将管道设置为正确的类型,以便输出写入我的输出表吗?

编辑1:这是我通过如下注释的已排序变量得到的完整错误消息

let sorted = row |> toSeq //|> Seq.map (fun x -> x.Value2.ToString()) |> Seq.sort

错误信息是: -

  

System.Runtime.InteropServices.COMException(0x800A03EC):来自HRESULT的异常:0x800A03EC      at System.RuntimeType.ForwardCallToInvokeMember(String memberName,BindingFlags flags,Object target,Int32 [] aWrapperTypes,MessageData&amp; msgData)      在Microsoft.Office.Interop.Excel.Range.get_Item(Object RowIndex,Object ColumnIndex)      在FSI_0122.toSeq@34-47.Invoke(Int32 c)在C:\ Users \ Simon Hayward \ Dropbox \ F#Scripts \ TotalJobsSort \ sortExcelScript.fsx:第36行      在Microsoft.FSharp.Collections.IEnumerator.map@109.DoMoveNext(b&amp;)      在Microsoft.FSharp.Collections.IEnumerator.MapEnumerator 1.System-Collections-IEnumerator-MoveNext() at Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.takeOuter@651[T,TResult](ConcatEnumerator 2 x,Unit unitVar0)      在Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.takeInner@644 [T,TResult](ConcatEnumerator 2 x, Unit unitVar0) at <StartupCode$FSharp-Core>.$Seq.MoveNextImpl@751.GenerateNext(IEnumerable 1&amp; next)      在Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase 1.MoveNextImpl() at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase 1.System-Collections-IEnumerator-MoveNext()      在Microsoft.FSharp.Collections.SeqModule.ToArray [T](IEnumerable 1 source) at Microsoft.FSharp.Collections.ArrayModule.OfSeq[T](IEnumerable 1来源)      在。$ FSI_0122.main @()在C:\ Users \ Simon Hayward \ Dropbox \ F#Scripts \ TotalJobsSort \ sortExcelScript.fsx:第42行   因错误而停止

编辑2:这个问题可能是由于toSeq函数被设计为将整个表格转换为序列吗?在我申请的地方,我只希望它适用于一行。

我尝试将toSeq中的r变量限制为1,但这没有帮助。

我的实际数据是否是锯齿状阵列的事实?它并不总是每行有3个条目,它在1到4之间变化。

编辑3:

以下是我的代码的当前迭代,基于Tomas的建议

#r "office.dll"
#r "Microsoft.Office.Interop.Excel.dll"

open System;;
open System.IO;;

open Microsoft.Office.Interop.Excel;;



let app = new ApplicationClass(Visible = true);;
let inputBook = app.Workbooks.Open @"SortData.xlsx" //workbook
let outputBook = app.Workbooks.Add();;
let inSheet = inputBook.Worksheets.[1] :?> _Worksheet
let outSheet = outputBook.Worksheets.[1] :?> _Worksheet

let rows = inSheet.UsedRange.Rows.Count;;
let columns = inSheet.UsedRange.Columns.Count;;

// Get the row count and calculate the name of the last cell e.g. "A13"
let rangeEnd = sprintf "A%d" columns

// Get values in the range A1:A13 as 2D object array of size 13x1
let values = inSheet.Range("A1", rangeEnd).Value2 :?> obj[,]

// Read values from the first (and only) column into 1D string array
let data = [| for i in 1 .. columns -> values.[1, i] :?> string |]
// Sort the array and get a new sorted 1D array
let sorted1D = data |> Array.sort
// Turn the 1D array into 2D array (13x1), so that we can write it back
let sorted2D = Array2D.init 1 columns (fun i _ -> data.[i])

// Write the data to the output sheet in Excel
outSheet.Range("A1", rangeEnd).Value2 <- sorted2D

但是因为实际数据在每一行中都有可变数量的条目,所以我得到了标准范围异常错误(这是对最近几天HRESULT异常错误的改进)。

所以我需要为每一行定义列,或者只是将行的长度绑定到for循环中的变量。 (我猜)。

1 个答案:

答案 0 :(得分:4)

看起来你在|>行的末尾有一个额外的Seq.sort运算符 - 这意味着列表已经排序,然后编译器尝试将它传递给执行的表达式赋值(不带任何参数且类型为unit)。

这样的东西应该编译(尽管可能还有一些其他的运行时问题):

for i in 1 .. rows do 
  let row = inSheet.Cells.Rows.[i] :?> Range
  let sorted = row |> toSeq |> Seq.map (fun x -> x.Value2.ToString()) |> Seq.sort
  (outSheet.Cells.Rows.[i] :?> Range).Value2 <- Array.ofSeq sorted

请注意,您无需将row标记为可变,因为代码会创建副本(并且 - 在我的版本中 - 将其分配给新变量sorted)。

我还使用Array.ofSeq将已排序的序列转换为数组,因为我认为Excel互操作对数组的效果更好。

在范围上设置Value2属性时,范围的大小应与您为其分配的数组的大小相同。此外,根据您要设置的范围,您可能需要2D数组。

编辑关于运行时错误,我不完全确定你的代码出了什么问题,但是这里我将如何进行排序(假设你只有一列包含字符串值而你想要排序行:)

// Get the row count and calculate the name of the last cell e.g. "A13"
let rows = inSheet.UsedRange.Rows.Count
let rangeEnd = sprintf "A%d" rows

// Get values in the range A1:A13 as 2D object array of size 13x1
let values = inSheet.Range("A1", rangeEnd).Value2 :?> obj[,]
// Read values from the first (and only) column into 1D string array
let data = [| for i in 1 .. rows -> values.[i, 1] :?> string |]
// Sort the array and get a new sorted 1D array
let sorted1D = data |> Array.sort
// Turn the 1D array into 2D array (13x1), so that we can write it back
let sorted2D = Array2D.init rows 1 (fun i _ -> data.[i])

// Write the data to the output sheet in Excel
outSheet.Range("A1", rangeEnd).Value2 <- sorted