I have an array of Strings returned from TimeZone.knownTimeZoneIdentifiers that I would like to parse into a nested dictionary (or another data structure).
The format of the [String] is
["Africa/Abidjan", "Africa/Accra",..,"America/Araguaina","America/Argentina/Buenos_Aires", ...,"Pacific/Marquesas", "Pacific/Midway", "Pacific/Nauru"]
The end result would be something like
["Africa":["Abidjan":[], "Accra":[]], "America": ["Araguaina": [], "Argentina": ["Buenos_Aires"]], "Pacific": ["Marquesas":[], "Midway":[], "Nauru":[]]]
I will need to query all of the keys at each level of the dictionary.
There must be another way to do this functionally or with recursion, rather than split(seperator: "/") and several for loops to build the data structure manually.
Don't use a dictionary for this. Make a custom type. In particular, you are describing a tree. Let's make one (largely based on https://github.com/raywenderlich/swift-algorithm-club/tree/master/Tree, and if there is more that you need to do, modify the Node class itself to meet your needs):
class Node<T> {
var value: T
weak var parent: Node?
var children = [Node<T>]()
init(_ value: T) {
self.value = value
}
func add(_ node: Node<T>) {
children.append(node)
node.parent = self
}
}
Now it's trivial:
let ids = TimeZone.knownTimeZoneIdentifiers
let root = Node("")
for id in ids {
var node = root
let splits = id.split(separator: "/").map(String.init)
for split in splits {
if let child = node.children.first(where:{$0.value == split}) {
node = child
} else {
let newnode = Node(split)
node.add(newnode)
node = newnode
}
}
}
Okay, let's see what we've got. It will be useful to have a nice way of printing a node:
extension Node: CustomStringConvertible {
private func display(level:Int) -> String {
let offset = String(repeating: " ", count: level * 4)
var s = offset + String(describing: value)
if children.isEmpty { return s }
s += " {\n"
s += children.map { $0.display(level:level+1) }.joined(separator: ",\n")
s += "\n\(offset)}"
return s
}
var description: String { return display(level:0) }
}
Now:
root.children.forEach {print($0)}
Result:
Africa {
Abidjan,
Accra,
Addis_Ababa,
Algiers,
Asmara,
Bamako,
Bangui,
Banjul,
Bissau,
Blantyre,
Brazzaville,
Bujumbura,
Cairo,
Casablanca,
Ceuta,
Conakry,
Dakar,
Dar_es_Salaam,
Djibouti,
Douala,
El_Aaiun,
Freetown,
Gaborone,
Harare,
Johannesburg,
Juba,
Kampala,
Khartoum,
Kigali,
Kinshasa,
Lagos,
Libreville,
Lome,
Luanda,
Lubumbashi,
Lusaka,
Malabo,
Maputo,
Maseru,
Mbabane,
Mogadishu,
Monrovia,
Nairobi,
Ndjamena,
Niamey,
Nouakchott,
Ouagadougou,
Porto-Novo,
Sao_Tome,
Tripoli,
Tunis,
Windhoek
}
America {
Adak,
Anchorage,
Anguilla,
Antigua,
Araguaina,
Argentina {
Buenos_Aires,
Catamarca,
Cordoba,
Jujuy,
La_Rioja,
Mendoza,
Rio_Gallegos,
Salta,
San_Juan,
San_Luis,
Tucuman,
Ushuaia
},
Aruba,
Asuncion,
Atikokan,
Bahia,
Bahia_Banderas,
Barbados,
Belem,
Belize,
Blanc-Sablon,
Boa_Vista,
Bogota,
Boise,
Cambridge_Bay,
Campo_Grande,
Cancun,
Caracas,
Cayenne,
Cayman,
Chicago,
Chihuahua,
Costa_Rica,
Creston,
Cuiaba,
Curacao,
Danmarkshavn,
Dawson,
Dawson_Creek,
Denver,
Detroit,
Dominica,
Edmonton,
Eirunepe,
El_Salvador,
Fort_Nelson,
Fortaleza,
Glace_Bay,
Godthab,
Goose_Bay,
Grand_Turk,
Grenada,
Guadeloupe,
Guatemala,
Guayaquil,
Guyana,
Halifax,
Havana,
Hermosillo,
Indiana {
Indianapolis,
Knox,
Marengo,
Petersburg,
Tell_City,
Vevay,
Vincennes,
Winamac
},
Inuvik,
Iqaluit,
Jamaica,
Juneau,
Kentucky {
Louisville,
Monticello
},
Kralendijk,
La_Paz,
Lima,
Los_Angeles,
Lower_Princes,
Maceio,
Managua,
Manaus,
Marigot,
Martinique,
Matamoros,
Mazatlan,
Menominee,
Merida,
Metlakatla,
Mexico_City,
Miquelon,
Moncton,
Monterrey,
Montevideo,
Montreal,
Montserrat,
Nassau,
New_York,
Nipigon,
Nome,
Noronha,
North_Dakota {
Beulah,
Center,
New_Salem
},
Nuuk,
Ojinaga,
Panama,
Pangnirtung,
Paramaribo,
Phoenix,
Port-au-Prince,
Port_of_Spain,
Porto_Velho,
Puerto_Rico,
Punta_Arenas,
Rainy_River,
Rankin_Inlet,
Recife,
Regina,
Resolute,
Rio_Branco,
Santa_Isabel,
Santarem,
Santiago,
Santo_Domingo,
Sao_Paulo,
Scoresbysund,
Shiprock,
Sitka,
St_Barthelemy,
St_Johns,
St_Kitts,
St_Lucia,
St_Thomas,
St_Vincent,
Swift_Current,
Tegucigalpa,
Thule,
Thunder_Bay,
Tijuana,
Toronto,
Tortola,
Vancouver,
Whitehorse,
Winnipeg,
Yakutat,
Yellowknife
}
Antarctica {
Casey,
Davis,
DumontDUrville,
Macquarie,
Mawson,
McMurdo,
Palmer,
Rothera,
South_Pole,
Syowa,
Troll,
Vostok
}
Arctic {
Longyearbyen
}
Asia {
Aden,
Almaty,
Amman,
Anadyr,
Aqtau,
Aqtobe,
Ashgabat,
Atyrau,
Baghdad,
Bahrain,
Baku,
Bangkok,
Barnaul,
Beirut,
Bishkek,
Brunei,
Calcutta,
Chita,
Choibalsan,
Chongqing,
Colombo,
Damascus,
Dhaka,
Dili,
Dubai,
Dushanbe,
Famagusta,
Gaza,
Harbin,
Hebron,
Ho_Chi_Minh,
Hong_Kong,
Hovd,
Irkutsk,
Jakarta,
Jayapura,
Jerusalem,
Kabul,
Kamchatka,
Karachi,
Kashgar,
Kathmandu,
Katmandu,
Khandyga,
Krasnoyarsk,
Kuala_Lumpur,
Kuching,
Kuwait,
Macau,
Magadan,
Makassar,
Manila,
Muscat,
Nicosia,
Novokuznetsk,
Novosibirsk,
Omsk,
Oral,
Phnom_Penh,
Pontianak,
Pyongyang,
Qatar,
Qostanay,
Qyzylorda,
Rangoon,
Riyadh,
Sakhalin,
Samarkand,
Seoul,
Shanghai,
Singapore,
Srednekolymsk,
Taipei,
Tashkent,
Tbilisi,
Tehran,
Thimphu,
Tokyo,
Tomsk,
Ulaanbaatar,
Urumqi,
Ust-Nera,
Vientiane,
Vladivostok,
Yakutsk,
Yangon,
Yekaterinburg,
Yerevan
}
Atlantic {
Azores,
Bermuda,
Canary,
Cape_Verde,
Faroe,
Madeira,
Reykjavik,
South_Georgia,
St_Helena,
Stanley
}
Australia {
Adelaide,
Brisbane,
Broken_Hill,
Currie,
Darwin,
Eucla,
Hobart,
Lindeman,
Lord_Howe,
Melbourne,
Perth,
Sydney
}
Europe {
Amsterdam,
Andorra,
Astrakhan,
Athens,
Belgrade,
Berlin,
Bratislava,
Brussels,
Bucharest,
Budapest,
Busingen,
Chisinau,
Copenhagen,
Dublin,
Gibraltar,
Guernsey,
Helsinki,
Isle_of_Man,
Istanbul,
Jersey,
Kaliningrad,
Kiev,
Kirov,
Lisbon,
Ljubljana,
London,
Luxembourg,
Madrid,
Malta,
Mariehamn,
Minsk,
Monaco,
Moscow,
Oslo,
Paris,
Podgorica,
Prague,
Riga,
Rome,
Samara,
San_Marino,
Sarajevo,
Saratov,
Simferopol,
Skopje,
Sofia,
Stockholm,
Tallinn,
Tirane,
Ulyanovsk,
Uzhgorod,
Vaduz,
Vatican,
Vienna,
Vilnius,
Volgograd,
Warsaw,
Zagreb,
Zaporozhye,
Zurich
}
GMT
Indian {
Antananarivo,
Chagos,
Christmas,
Cocos,
Comoro,
Kerguelen,
Mahe,
Maldives,
Mauritius,
Mayotte,
Reunion
}
Pacific {
Apia,
Auckland,
Bougainville,
Chatham,
Chuuk,
Easter,
Efate,
Enderbury,
Fakaofo,
Fiji,
Funafuti,
Galapagos,
Gambier,
Guadalcanal,
Guam,
Honolulu,
Johnston,
Kiritimati,
Kosrae,
Kwajalein,
Majuro,
Marquesas,
Midway,
Nauru,
Niue,
Norfolk,
Noumea,
Pago_Pago,
Palau,
Pitcairn,
Pohnpei,
Ponape,
Port_Moresby,
Rarotonga,
Saipan,
Tahiti,
Tarawa,
Tongatapu,
Truk,
Wake,
Wallis
}
You can use reduce(into:) and populate your dictionary using Key-based subscript default value:
let dictionary: [String: [String]] = TimeZone.knownTimeZoneIdentifiers.reduce(into: [:]) {
if let index = $1.firstIndex(of: "/") {
$0[.init($1[..<index]), default: []].append(.init($1[$1.index(after: index)...]))
}
}
Then you can map your dictionary values. If you find the slash append to the array otherwise set an empty collection to the key.
let result = dictionary.mapValues { string -> [String: [String]] in
string.reduce(into: [:]) {
if let index = $1.firstIndex(of: "/") {
$0[.init($1[..<index]), default: []].append(.init($1[$1.index(after: index)...]))
} else {
$0[$1] = []
}
}
}
If you would like to do that in a single pass:
let result: [String: [String:[String]]] = TimeZone.knownTimeZoneIdentifiers.reduce(into: [:]) {
if let index = $1.firstIndex(of: "/") {
let key = String($1[..<index])
let value = String($1[$1.index(after: index)...])
if let index = value.firstIndex(of: "/") {
let country = String(value[..<index])
let city = String(value[value.index(after: index)...])
$0[key, default: [:]][country, default: []].append(city)
} else {
$0[key, default: [:]][value] = []
}
}
}
result.forEach({print("key:",$0.key, "values:", $0.value)})
I am building a money budgeting app where the user can split their monthly salary in 5 areas: food, social, education, travel, savings. I have a register and log in screen. This is in Flutter/dart and I'm using sqlite.
Some of the data to store:
id, username, password, email, monthly_salary, food_initial, food_final, social_initial, social_final, education_initial, education_final, travel_initial, travel_final, savings_initial, savings_final
The 'initial' refers to a value that the user will input for themselves based on the division of the monthly salary. (ie. $5000 a month/5 containers = 1000 for each container. but, the user can edit this value of 1000 as their initial budgeted money). The 'final' refers to the user's remaining money (under the assumption they don't go over) for that container.
The initial and final values get reset at the end of the month. However, I would like to save their previous month's final values so the user can see a backlog of what they did.
I initially had 2 tables for my database. One was for the USER_DATA and the other was BUDGETS table. However, after struggling to figure out how to do CRUD operations for two tables, I decided to make it all into one table. But, after experiencing trouble with one table, I want to move back to more than one table. I'm confused now on:
1. How to use CRUD operations when having more than one table on DB?
2. Do I have to build a new model class in order to have more than one table?
3. How to save some of the user's data on database, but have to wait for the rest of the information to come in (later on while they're using the app)?
When making the constructor for the model class, and then referring to the model class in the UI, it requires me to bring in all of the parameters I have, but I don't have all of those values ready yet. The user still needs to register before they can input their salary and etc. Would I have to use the Future class to get over this hurdle?
I've watched a lot of videos on others building their databases in sqlite and with Flutter but all of them usually do a simple To-Do list app where they will need to fill in every column of the table and I haven't seen one yet that includes several tables. (ie. columns: id, description, priority, date)
--
I think have a good basis now on the UI side and using TextFormFields to access the information, but connecting the database/creating it properly is confusing me.
I am also considering writing the database in a .sql file now before proceeding. I have been teaching myself Flutter through tutorials (as background knowledge).
I know this is a bit long and all over the place, but I would greatly appreciate anyone who has experience with the sqlite environment in Flutter to please help me understand how to do this correctly.
Thank you. Also, if there is any more clarification/code needed, let me know.
Model class:
import 'dart:core';
class UserData {
//user table
int _userid;
String _username;
String _password;
String _email;
String _first_name;
String _last_name;
String _date;
int _month_id;
bool _subtract; //did the user subtract money from their funds? true/false
double _month_salary;
String _log_details;
/////////////budget table
double _edu_budget_initial;
double _edu_budget_final;
double _travel_budget_initial;
double _travel_budget_final;
double _living_budget_initial;
double _living_budget_final;
double _savings_budget_initial;
double _savings_budget_final;
double _social_budget_initial;
double _social_budget_final;
// using [___] around your user's data entry makes it optional in the table
// otherwise, these are all required fields
UserData(
this._username,
this._email,
this._first_name,
this._last_name,
this._password,
this._date,
this._month_id,
this._subtract,
this._month_salary,
this._log_details,
this._edu_budget_final,
this._edu_budget_initial,
this._living_budget_final,
this._living_budget_initial,
this._savings_budget_final,
this._savings_budget_initial,
this._social_budget_final,
this._social_budget_initial,
this._travel_budget_final,
this._travel_budget_initial);
//created a Named Constructor for the ability to create multiple constructors in one class
UserData.withId(
this._userid,
this._username,
this._password,
this._first_name,
this._last_name,
this._email,
this._date,
this._month_id,
this._subtract,
this._month_salary,
this._log_details,
this._edu_budget_final,
this._edu_budget_initial,
this._living_budget_final,
this._living_budget_initial,
this._savings_budget_final,
this._savings_budget_initial,
this._social_budget_final,
this._social_budget_initial,
this._travel_budget_final,
this._travel_budget_initial);
//UserData.budgetTable(this.date, this.month_id, this.log_details, this.edu_budget_final, this.edu_budget_initial, this.living_budget_final, this.living_budget_initial,
//this.month_salary, this.savings_budget_final, this.savings_budget_initial, this.social_budget_final,
//this.social_budget_initial, this.travel_budget_final, this.travel_budget_initial);
//getters:
//userdata table
int get userid => _userid;
String get username => _username;
String get password => _password;
String get email => _email;
String get firstname => _first_name;
String get lastname => _last_name;
String get date => _date;
//budget table
int get month_id => _month_id;
bool get subtract => _subtract;
double get month_salary => _month_salary;
String get log_details => _log_details;
double get living_budget_initial => _living_budget_initial;
double get living_budget_final => _living_budget_final;
double get social_budget_initial => _social_budget_initial;
double get social_budget_final => _social_budget_final;
double get edu_budget_initial => _edu_budget_initial;
double get edu_budget_final => _edu_budget_final;
double get travel_budget_initial => _travel_budget_initial;
double get travel_budget_final => _travel_budget_final;
double get savings_budget_initial => _savings_budget_initial;
double get savings_budget_final => _savings_budget_final;
//setters:
/*
set userid(int newUserID) {
//adding condition before storing what user put in
if (newUserID <= 12) {
this._userid = newUserID;
}
}
*/
set username(String newUsername) {
if (newUsername.length <= 30) {
this._username = newUsername;
}
}
set password(String newPassword) {
if (newPassword.length <= 30)
this._password = newPassword;
}
set email(String newEmail) {
if (newEmail.length <= 50)
this._email = newEmail;
}
set first_name(String newFirstName) {
if (newFirstName.length <= 30)
this._first_name = newFirstName;
}
set last_name(String newLastName) {
if (newLastName.length <= 30)
this._last_name = newLastName;
}
set month_id(int newMonthId) {
if (newMonthId <= 12) {
this._month_id = newMonthId;
}
}
set month_salary(double newMonthSalary) {
this._month_salary = newMonthSalary;
}
set date(String newDate) {
this._date = newDate;
}
set log_details(String newLogDetails) {
if (newLogDetails.length <= 255)
this._log_details = newLogDetails;
}
set subtract(bool newSubtract) {
this._subtract = newSubtract;
}
set living_budget_initial(double newLBI) {
if (newLBI <= 10) this._living_budget_initial = newLBI;
}
set living_budget_final(double newLBF) {
if (newLBF <= 10) this._living_budget_final = newLBF;
}
set social_budget_initial(double newSOBI) {
if (newSOBI <= 10) this._social_budget_initial = newSOBI;
}
set social_budget_final(double newSOBF) {
if (newSOBF <= 10) this._social_budget_final = newSOBF;
}
set edu_budget_initial(double newEBI) {
if (newEBI <= 10) this._edu_budget_initial = newEBI;
}
set edu_budget_final(double newEBF) {
if (newEBF <= 10) this._edu_budget_final = newEBF;
}
set travel_budget_initial(double newTBI) {
if (newTBI <= 10) this._travel_budget_initial = newTBI;
}
set travel_budget_final(double newTBF) {
if (newTBF <= 10) this._travel_budget_final = newTBF;
}
set savings_budget_initial(double newSBI) {
if (newSBI <= 10) this._savings_budget_initial = newSBI;
}
set savings_budget_final(double newSBF) {
if (newSBF <= 10) this._savings_budget_final = newSBF;
}
// converting object into Map objects
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
var userid;
if (userid != null) {
map['userid'] = _userid;
}
map['userid'] = _userid;
map['username'] = _username;
map['password'] = _password;
map['email'] = _email;
map['first_name'] = _first_name;
map['last_name'] = _last_name;
//
map['month_id'] = _month_id;
map['month_salary'] = _month_salary;
map['date'] = _date;
map['log_details'] = _log_details;
map['subtract'] = _subtract;
map['living_budget_initial'] = _living_budget_initial;
map['living_budget_final'] = _living_budget_final;
map['social_budget_initial'] = _social_budget_initial;
map['social_budget_final'] = _social_budget_final;
map['edu_budget_initial'] = _edu_budget_initial;
map['edu_budget_final'] = _edu_budget_final;
map['travel_budget_initial'] = _travel_budget_initial;
map['travel_budget_final'] = _travel_budget_final;
map['savings_budget_initial'] = _savings_budget_initial;
map['savings_budget_final'] = _savings_budget_final;
return map;
}
//converts map objects into objects
UserData.fromMapObject(Map<String, dynamic> map) {
this._userid = map['userid'];
this._username = map['username'];
this._password = map['password'];
this._email = map['email'];
this.first_name = map['first_name'];
this.last_name = map['last_name'];
//
this._month_id = map['month_id'];
this._month_salary = map['month_salary'];
this._date = map['date'];
this._log_details = map['log_details'];
this._subtract = map['subtract'];
this._living_budget_initial = map['living_budget_initial'];
this._living_budget_final = map['living_budget_final'];
this._social_budget_initial = map['social_budget_initial'];
this._social_budget_final = map['social_budget_final'];
this._edu_budget_initial = map['edu_budget_initial'];
this._edu_budget_final = map['edu_budget_final'];
this._travel_budget_initial = map['travel_budget_initial'];
this._travel_budget_final = map['travel_budget_final'];
this._savings_budget_initial = map['savings_budget_initial'];
this._savings_budget_final = map['savings_budget_final'];
}
}
Database helper class:
import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:jewell_1/user_data.dart';
class DbHelper {
// Create a private instance of the class
static final DbHelper _dbhelper = new DbHelper._internal();
String DB_name = "user_data.db";
static final int DATABASE_VERSION = 2;
String userDataTable = 'user_data_table';
String colUserId = 'userid';
String colUsername = 'username';
String colPassword = 'password';
String colEmail = 'email';
String colFirstName = 'first_name';
String colLastName = 'last_name';
String colDate = 'date';
String colMonthId = 'month_id';
String colSubtract = 'subtract';
String colMonthSalary = 'month_salary';
String colLogDetails = 'log_details';
String colLBI = 'living_budget_initial';
String colLBF = 'living_budget_final';
String colSOBI = 'social_budget_initial';
String colSOBF = 'social_budget_final';
String colEBI = 'edu_budget_initial';
String colEBF = 'edu_budget_final';
String colTBI = 'travel_budget_initial';
String colTBF = 'travel_budget_final';
String colSBI = 'savings_budget_initial';
String colSBF = 'savings_budget_final';
// Create an empty private named constructor
DbHelper._internal();
// Use the factory to always return the same instance
factory DbHelper() {
return _dbhelper;
}
static Database _db;
Future<Database> get db async {
if (_db == null) {
_db = await initializeDb();
}
return _db;
}
Future<Database> initializeDb() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + "todos.db";
var dbTodos = await openDatabase(path, version: DATABASE_VERSION, onCreate: _createDb);
return dbTodos;
}
void _createDb(Database db, int newVersion) async {
await db.execute(
"CREATE TABLE $userDataTable($colUserId INETEGER PRIMARY KEY, $colUsername TEXT," +
"$colPassword TEXT, $colEmail TEXT, $colFirstName TEXT,"
"$colLastName TEXT, $colDate TEXT, $colMonthId INTEGER,"
" $colSubtract BOOLEAN, $colMonthSalary DOUBLE, $colLogDetails TEXT,"
" $colEBI DOUBLE, $colEBF DOUBLE, $colTBI DOUBLE, $colTBF DOUBLE, "
" $colLBI DOUBLE, $colLBF DOUBLE, $colSBI DOUBLE, $colSBF DOUBLE,"
" $colSOBI DOUBLE, $colSOBF DOUBLE)");
}
Future<int> insertData(UserData data) async {
Database db = await this.db;
var result = await db.insert(userDataTable, data.toMap());
return result;
}
Future<List> getDatas() async {
Database db = await this.db;
var result =
await db.rawQuery("SELECT * FROM $userDataTable order by $colUserId ASC");
return result;
}
Future<int> getCount() async {
Database db = await this.db;
var result = Sqflite.firstIntValue(
await db.rawQuery("SELECT COUNT (*) FROM $userDataTable"));
return result;
}
Future<int> updateData(UserData data) async {
var db = await this.db;
var result = await db
.update(userDataTable, data.toMap(), where: "$colUserId=?", whereArgs: [data.userid]);
return result;
}
Future<int> deleteData(int id) async {
int result;
var db = await this.db;
result = await db.rawDelete("DELETE FROM $userDataTable WHERE $colUserId=$id");
return result;
}
}
To be more specific, I am confused on not writing the query of a new table, but rather at this point:
var result = await db.insert(userDataTable, data.toMap());
return result;
The result is only containing my userDataTable and if I create another table, then am I supposed to add another var result? I tried to have:
var result2 = await db.insert(budgetsTable, data.toMap());
This seemed wrong. Also, I didn't know how to return both results.
That was specifically where/when I started to scavenge the internet of people who have made CRUD operations using more than one table on sqlite with Flutter.