Swift:将二维数组转换为自定义对象数组的简洁实现方案
Got it, let's fix this up properly. The goal is to turn your row-based sheetValues into an array of Language objects where each language has its ISO code and matching SheetData entries. Here's a clean, efficient approach that avoids messy loops and leverages Swift's collection methods:
Step 1: Add a Transpose Helper Function
First, we need to convert the row-oriented sheetValues into column-oriented data. This makes it way easier to handle each key's associated values, descriptions, and sections all in one place:
func transpose<T>(_ matrix: [[T]]) -> [[T]] { guard !matrix.isEmpty, let columnCount = matrix.first?.count else { return [] } return (0..<columnCount).map { columnIndex in matrix.map { $0[columnIndex] } } }
Step 2: Process the Data into Language Objects
Now we can transform the transposed data into our desired model structure. We'll use a dictionary to group SheetData entries by language ISO code, then convert that dictionary into our final [Language] array:
let sheetValues = [["key", "general_ok", "connection_nointernet_message"], ["en", "Ok", "No internet connection"], ["ru", "russianOk", "No internet connection"], ["pt", "portugueseOk", "No internet connection"], ["description", "Ok", "No internet connection"], ["section", "General", "Connection"]] // Extract languages directly from the sheet (no hardcoding!) let languages = sheetValues[1..<4].map { $0[0] } // Transpose to work with columns instead of rows let transposedColumns = transpose(sheetValues) // Skip the first column (it's just headers like "key", "en", etc.) let keyColumns = transposedColumns.dropFirst() // Group SheetData entries by language ISO code var languageDataMap: [String: [SheetData]] = [:] for column in keyColumns { guard column.count >= 6 else { continue } // Ensure we have all required fields let key = column[0] let section = column[5] let description = column[4] // For each language, create a SheetData entry and add it to the map for (index, isoCode) in languages.enumerated() { let value = column[index + 1] // +1 skips the key in the column let sheetData = SheetData(key: key, value: value, description: description, section: section) languageDataMap[isoCode, default: []].append(sheetData) } } // Convert the map to our final [Language] array let finalLanguages: [Language] = languages.compactMap { isoCode in guard let data = languageDataMap[isoCode] else { return nil } return Language(isoCode: isoCode, data: data) }
How This Works
- Transpose: Converts rows to columns so each column represents all data for a single key (e.g., one column for "general_ok" with its en/ru/pt values, description, and section).
- Dynamic Language Extraction: Instead of relying on a hardcoded
languagesarray, we pull ISO codes directly from the sheet rows 2-4 (indexes 1-3). This makes the code flexible if you add more languages later. - Dictionary Grouping: Using a dictionary to group
SheetDataby ISO code ensures we efficiently collect all entries for each language without redundant checks. - CompactMap: Converts the dictionary into a properly ordered array of
Languageobjects, matching the order of languages from the sheet.
Bonus: Make It Even Cleaner with Functional Programming
If you prefer a more concise style, you can combine some steps using reduce:
let finalLanguages: [Language] = languages.map { isoCode in let data = keyColumns.compactMap { column in guard column.count >= 6, let langIndex = languages.firstIndex(of: isoCode) else { return nil } return SheetData( key: column[0], value: column[langIndex + 1], description: column[4], section: column[5] ) } return Language(isoCode: isoCode, data: data) }
This skips the dictionary step and builds each Language directly by filtering the key columns for the current ISO code.
内容的提问来源于stack exchange,提问作者user9449189

