提案されたコードを機能させることができましたが、コードとデータを微調整する必要があります。
students
の列rooms.csv
をオブジェクトのコレクションに逆シリアル化する追加の手順が必要です。ScriptBlock
の配列に評価される のように見えますがHashTable
、CSV 入力へのいくつかの変更がまだ必要です。
- およびプロパティを引用符で囲み、 にキャストする必要があり
StartDate
ます。EndDate
[DateTime]
- 少なくとも複数の学生がいる部屋では、値を引用符で囲む必要があるため、分離された配列要素が追加の列として
Import-Csv
解釈されません。,
- CSV を中間形式として使用することの欠点は、元のプロパティ タイプが失われることです。すべてが
[String]
インポート時になります。効率化のために元の型にキャストし直すことが望ましい場合もあれば、特定の操作を機能させるために絶対に必要な場合もあります。これらのプロパティを使用するたびにキャストすることもできますが、インポートの直後に一度キャストすることをお勧めします。
それらの変更によりrooms.csv
...
roomName, roomNo, students
room1, 1, "{@{StudentNo=111; StudentName='john smith'; StartDate=[DateTime] '2018-01-01T00:00:00'; EndDate=[DateTime] '2018-07-06T00:00:00'}}"
room2, 2, "{@{StudentNo=222; StudentName='jane doe'; StartDate=[DateTime] '2018-01-01T00:00:00'; EndDate=[DateTime] '2018-07-06T00:00:00'}}"
...そしてスクリプトは...
# Replace the [String] property "students" with an array of [HashTable] property "Students"
$rooms = Import-Csv rooms.csv `
| Select-Object `
-ExcludeProperty 'students' `
-Property '*', @{
Name = 'Students'
Expression = {
$studentsText = $_.students
$studentsScriptBlock = Invoke-Expression -Command $studentsText
$studentsArray = @(& $studentsScriptBlock)
return $studentsArray
}
}
# Replace the [String] property "StudentNo" with an [Int32] property of the same name
$students = Import-Csv students.csv `
| Select-Object `
-ExcludeProperty 'StudentNo' `
-Property '*', @{
Name = 'StudentNo'
Expression = { [Int32] $_.StudentNo }
}
$combined = $students `
| Select-Object -Property `
'StudentNo', `
'PreferredSurname', `
'PreferredFirstnames', `
'UPN', `
@{
Name = "roomName";
Expression = {
foreach ($r in $rooms)
{
if ($r.Students.StudentNo -contains $_.StudentNo)
{
return $r.roomName
}
}
#TODO: Return text indicating room not found?
}
}
これが遅くなる理由は、すべての学生オブジェクトに対して線形検索を実行しているためです。実際にはそのうちの 2 つです。最初に部屋のコレクション ( foreach
)、次に各部屋の生徒のコレクション( ) を調べ-contains
ます。現在の生徒が割り当てられていないすべての部屋で、その生徒の部屋が見つかるまで、その部屋の生徒のコレクション全体を何度も繰り返しているため、これはすぐに多くの反復と等価比較に変わります。
線形検索を実行するときに実行できる簡単な最適化の 1 つは、検索する項目を並べ替えることです (この場合、プロパティは各学生Students
のプロパティによって順序付けられます)...StudentNo
# Replace the [String] property "students" with an array of [HashTable] property "Students"
$rooms = Import-Csv rooms.csv `
| Select-Object `
-ExcludeProperty 'students' `
-Property '*', @{
Name = 'Students'
Expression = {
$studentsText = $_.students
$studentsScriptBlock = Invoke-Expression -Command $studentsText
$studentsArray = @(& $studentsScriptBlock) `
| Sort-Object -Property @{ Expression = { $_.StudentNo } }
return $studentsArray
}
}
...そして、同じコレクションを検索しているときに、検索しているアイテムよりも大きなアイテムに出くわした場合、コレクションの残りの部分に検索しているものが含まれていない可能性があることがわかります。検索をすぐに中止できます...
@{
Name = "roomName";
Expression = {
foreach ($r in $rooms)
{
# Requires $room.Students to be sorted by StudentNo
foreach ($roomStudentNo in $r.Students.StudentNo)
{
if ($roomStudentNo -eq $_.StudentNo)
{
# Return the matched room name and stop searching this and further rooms
return $r.roomName
}
elseif ($roomStudentNo -gt $_.StudentNo)
{
# Stop searching this room
break
}
# $roomStudentNo is less than $_.StudentNo; keep searching this room
}
}
#TODO: Return text indicating room not found?
}
}
さらに良いことに、並べ替えられたコレクションを使用すると、線形検索よりも高速なバイナリ検索を実行することもできます*。Array
クラスはすでにBinarySearch
静的メソッドを提供しているため、これも少ないコードで実現できます...
@{
Name = "roomName";
Expression = {
foreach ($r in $rooms)
{
# Requires $room.Students to be sorted by StudentNo
if ([Array]::BinarySearch($r.Students.StudentNo, $_.StudentNo) -ge 0)
{
return $r.roomName
}
}
#TODO: Return text indicating room not found?
}
}
ただし、この問題にアプローチする方法は、部屋への[HashTable]
マッピングを使用することです。StudentNo
を構築するには少し前処理が必要です[HashTable]
が、これにより、学生の部屋を取得するときに一定時間のルックアップが提供されます。
function GetRoomsByStudentNoTable()
{
$table = @{ }
foreach ($room in $rooms)
{
foreach ($student in $room.Students)
{
#NOTE: It is assumed each student belongs to at most one room
$table[$student.StudentNo] = $room
}
}
return $table
}
# Replace the [String] property "students" with an array of [HashTable] property "Students"
$rooms = Import-Csv rooms.csv `
| Select-Object `
-ExcludeProperty 'students' `
-Property '*', @{
Name = 'Students'
Expression = {
$studentsText = $_.students
$studentsScriptBlock = Invoke-Expression -Command $studentsText
$studentsArray = @(& $studentsScriptBlock)
return $studentsArray
}
}
# Replace the [String] property "StudentNo" with an [Int32] property of the same name
$students = Import-Csv students.csv `
| Select-Object `
-ExcludeProperty 'StudentNo' `
-Property '*', @{
Name = 'StudentNo'
Expression = { [Int32] $_.StudentNo }
}
$roomsByStudentNo = GetRoomsByStudentNoTable
$combined = $students `
| Select-Object -Property `
'StudentNo', `
'PreferredSurname', `
'PreferredFirstnames', `
'UPN', `
@{
Name = "roomName";
Expression = {
$room = $roomsByStudentNo[$_.StudentNo]
if ($room -ne $null)
{
return $room.roomName
}
#TODO: Return text indicating room not found?
}
}
$roomsByStudentNo
搬入と同時に行うことで建物の当たりを改善することができrooms.csv
ます...
# Replace the [String] property "students" with an array of [HashTable] property "Students"
$rooms = Import-Csv rooms.csv `
| Select-Object `
-ExcludeProperty 'students' `
-Property '*', @{
Name = 'Students'
Expression = {
$studentsText = $_.students
$studentsScriptBlock = Invoke-Expression -Command $studentsText
$studentsArray = @(& $studentsScriptBlock)
return $studentsArray
}
} `
| ForEach-Object -Begin {
$roomsByStudentNo = @{ }
} -Process {
foreach ($student in $_.Students)
{
#NOTE: It is assumed each student belongs to at most one room
$roomsByStudentNo[$student.StudentNo] = $_
}
return $_
}
*小さいアレイを除く