Сортировка строк Excel по алфавиту в F# (Office.Interop)
Я использую взаимодействие Excel в Visual Studio 2010, чтобы попытаться отсортировать все эти строки данных в алфавитном порядке. Некоторые уже в алфавитном порядке.
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();;
Но есть проблема с типами. Последняя строка перед командой quit
(outSheet.Cells.Rows.[i] :?> Range).Value2 <- row.Value2;;
Красная линия подчеркнута intellisense, и ошибка, которую я получаю,
Msgstr "Это выражение, как ожидается, будет иметь тип seq -> 'a, но здесь имеет тип unit".
Я понимаю, что VS пытается сказать мне, но я сделал несколько попыток исправить это сейчас, и я не могу обойти проблему с типом.
Кто-нибудь может посоветовать, пожалуйста, как я могу получить конвейер правильного типа, чтобы выходные данные записывались в мой выходной лист?
РЕДАКТИРОВАТЬ 1: Это полное сообщение об ошибке, которое я получаю с отсортированной переменной, закомментированной следующим образом
let sorted = row |> toSeq //|> Seq.map (fun x -> x.Value2.ToString()) |> Seq.sort
Сообщение об ошибке:
System.Runtime.InteropServices.COMException (0x800A03EC): Исключение из HRESULT: 0x800A03EC в System.RuntimeType.ForwardCallToInvokeMember(строковые члены memberName, BindingFlags, объектная цель, Int32[] aWrapperTypes, msD.ExD.Gence.InDataGateInInter_D_G_D_D_G_D_G_D_G_D_G_P_D_G_P_G_P_P_P_P_P_P_P_S_S_P_S_S_S_S_S_S_S_S_S_S_S_S2.get_Item(Object RowIndex, Object ColumnIndex) в FSI_0122.toSeq@34-47.Invoke(Int32 c) в C:\Users\Simon Hayward\Dropbox\F# Скрипты \TotalJobsSort\sortExcelScript.fsx: строка 36 в Microsoft.FSharp.Collections.IEnumerator.map@109.DoMoveNext(b&) в 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] (ConcatEnumerator2 x, Unit unitVar0) at <StartupCode$FSharp-Core>.$Seq.MoveNextImpl@751.GenerateNext(IEnumerable
1 и далее) в Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase1.MoveNextImpl() at Microsoft.FSharp.Core.CompilerServices.GeneratedSequenceBase
1.System-Collections-IEnumerator-MoveNext () в Microsoft.FSharp.Collections.SeqModule.ToArray [T] (IEnumerable1 source) at Microsoft.FSharp.Collections.ArrayModule.OfSeq[T](IEnumerable
1 источник) at .$FSI_0122.main@() в C:\Users\Simon Hayward\Dropbox\F# Scripts\TotalJobsSort\sortExcelScript.fsx: строка 42 Остановлена из-за ошибки
РЕДАКТИРОВАТЬ 2: Может ли эта проблема быть из-за функции toSeq, предназначенной для превращения целого листа в последовательность? Где я применяю это, я только хочу, чтобы это применялось к одному ряду.
Я попытался ограничить переменную r в toSeq до 1, но это не помогло.
Имеет ли значение тот факт, что мои фактические данные являются зубчатыми массивами? Он не всегда имеет 3 записи в каждой строке, он варьируется от 1 до 4.
РЕДАКТИРОВАТЬ 3:
Вот текущая итерация моего кода, основанная на предложениях Томаса
#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 ответ
Похоже, у вас есть дополнительный |>
оператор в конце строки с 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
При выборе свойства диапазона размер диапазона должен совпадать с размером назначаемого ему массива. Кроме того, в зависимости от диапазона, который вы хотите установить, вам может понадобиться двумерный массив.
РЕДАКТИРОВАТЬ Что касается ошибок во время выполнения, я не совсем уверен, что не так с вашим кодом, но вот как я бы сделал сортировку (при условии, что у вас есть только один столбец со строковыми значениями, и вы хотите отсортировать строки):
// 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