我正在寻找从记录创建树视图的最佳和最快的算法。
我的数据库中有这样的记录:
ID|name|parent
1 Foo 0
2 Boo 1
3 Coo 1
4 Goo 2
我想收到的结构是:
[{
name: Foo
children: [
{
name: Boo
children: [
{
name: Goo
children: []
}
]
},
{
name: Coo
children: []
}
]
}]
我已尝试递归执行此操作,但我担心我的解决方案不够理想。
此致
答案 0 :(得分:0)
通常,您分三步执行此操作:
首先,按 parent 对列表进行排序。
接下来,创建这些结构的哈希映射(字典),由ID索引。那就是:
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Com.XMG.UnityLib.Community.OnlineGame;
using Com.XMG.UnityLib.Tracking;
#if UNITY_IPHONE
using FlurryAccess = FlurryBinding;
#elif UNITY_ANDROID
using FlurryAccess = FlurryAndroid;
#endif
public abstract partial class XMGAnalyticsManager : XMGSingleton<XMGAnalyticsManager> {
#region Private Fields
/// <summary>
/// The type of the current build.
/// For Analytics QA testing point to Analytics
/// For QA Testing point to SandBox
/// For Production/Release build point to Production
/// </summary>
[SerializeField]
private BuildTypeEnum buildType = BuildTypeEnum.SandBox;
/// <summary>
/// The type of the android store type
/// </summary>
[SerializeField]
private AndroidStoreTypeEnum androidStoreType = AndroidStoreTypeEnum.Google;
/// <summary>
/// The swrve authentication Info
/// </summary>
[SerializeField]
private SwrveAuthentication swrveAuth;
/// <summary>
/// The flurry auth.
/// </summary>
[SerializeField]
private FlurryAuthentication flurryAuth;
/// <summary>
/// Sends events every X seconds.
/// </summary>
[SerializeField]
private float sendEventsEverySeconds = 30;
/// <summary>
/// The parent screen.
/// </summary>
[SerializeField]
private string parentScreen = "SplashPage";
/// <summary>
/// The grand parent screen.
/// </summary>
string grandParentScreen = "null";
/// <summary>
/// My external IP.
/// </summary>
string myExtIP = "";
/// <summary>
/// The swrve component.
/// </summary>
private SwrveComponent swrveComponent;
private Dictionary<string, JSONObject> swrveResources = new Dictionary<string, JSONObject>();
/// <summary>
/// Resource hashmap ----> (key: resource name, value: json object)
/// </summary>
public Dictionary<string, JSONObject> Resources {
get {
return this.swrveResources;
}
}
/// <summary>
/// This event is triggered when swrve data is updated.
/// </summary>
public static event Action ItemsConfigurationUpdatedEvent = delegate {};
/// <summary>
/// True if we are in the fetching of AB Test Data State
/// </summary>
private bool isFetchingABParams = false;
/// <summary>
/// The current swrve identifier.
/// </summary>
private string currentSwrveId = null;
/// <summary>
/// The android bundle version.
/// </summary>
[SerializeField]
protected string androidBundleVersion = "1.0";
#endregion
#region Public Properties
/// <summary>
/// True if it's a production build
/// </summary>
public bool IsProductionBuild {
get { return buildType == BuildTypeEnum.Production; }
}
/// <summary>
/// Gets the type of the android store.
/// </summary>
public AndroidStoreTypeEnum AndroidStoreType {
get { return androidStoreType; }
set { androidStoreType = value; }
}
/// <summary>
/// True if its a google play store build
/// </summary>
public bool IsGoogleStoreType {
get { return androidStoreType == AndroidStoreTypeEnum.Google; }
}
/// <summary>
/// True if its an Amazon store build
/// </summary>
public bool IsAmazonStoreType {
get { return androidStoreType == AndroidStoreTypeEnum.Amazon; }
}
#endregion
#region Monobehaviors & Init
/// <summary>
/// Add event Listeners
/// </summary>
void Awake() {
XMGOnlineProfileManager.OnProfileLoadedEvent += ProfileHasLoadedCallback;
XMGCommunityManager.OnCreateRealProfileOnServerEvent += ProfileCreatedOnServerCallback;
XMGCommunityManager.OnNewFriendsAddedEvent += NewFriendsAddedCallback;
RegisterGameListeners();
// TODO: Listen for Profile Change events
swrveAuth.Init();
flurryAuth.Init();
}
/// <summary>
/// Starts the SWRVE Event timer
/// </summary>
void Start() {
StartSwrveTimer();
StartCoroutine( CheckIP() );
}
/// <summary>
/// Checks for the IP
/// </summary>
/// <returns>
/// The IP
/// </returns>
IEnumerator CheckIP(){
WWW myExtIPWWW = new WWW("http://checkip.dyndns.org");
if( myExtIPWWW != null ) {
yield return myExtIPWWW;
if( myExtIPWWW.error == null ) {
myExtIP=myExtIPWWW.text;
myExtIP=myExtIP.Substring(myExtIP.IndexOf(":")+1);
myExtIP=myExtIP.Substring(0,myExtIP.IndexOf("<"));
}
}
}
/// <summary>
/// Detach event Listeners
/// </summary>
void OnDestroy() {
XMGOnlineProfileManager.OnProfileLoadedEvent -= ProfileHasLoadedCallback;
XMGCommunityManager.OnCreateRealProfileOnServerEvent -= ProfileCreatedOnServerCallback;
XMGCommunityManager.OnNewFriendsAddedEvent -= NewFriendsAddedCallback;
UnRegisterGameListeners();
}
/// <summary>
/// If Pause end session
/// If Resume Start session and Re-Login.
/// At any case post events
/// </summary>
/// <param name='pause'>
/// Pause.
/// </param>
void OnApplicationPause(bool pause) {
if (currentSwrveId != null) {
if(pause) {
swrveComponent.Swrve_AddSessionEnd();
} else {
swrveComponent.Swrve_AddSessionStart();
SessionStart();
AnalyticsLogout();
AnalyticsLogin();
}
LogUserProperties();
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
}
}
/// <summary>
/// Init plugins.The Profile Has loaded
/// </summary>
void ProfileHasLoadedCallback() {
// Get Swrve component
swrveComponent = gameObject.GetComponentInChildren<SwrveComponent>();
AnalyticsLogin();
// Check if we've set the random user property, if not then set it
if (!PlayerPrefs.HasKey(RANDOM_TEST_PROPERTY_KEY)) {
PlayerPrefs.SetString(RANDOM_TEST_PROPERTY_KEY, "true");
JSONObject userProperty = new JSONObject(JSONObject.Type.OBJECT);
int random = NGUITools.RandomRange(1, 100);
userProperty.AddField("RandNum", random.ToString());
swrveComponent.Swrve_AddUserUpdateEvent(userProperty.ToString());
}
}
/// <summary>
/// A new Online Profile created callback.
/// </summary>
void ProfileCreatedOnServerCallback() {
AddNamedEvent( "Community.FacebookUserCreated" );
}
/// <summary>
/// New friends were added callback.
/// </summary>
/// <param name='newFriendsAdded'>
/// The New friends added
/// </param>
void NewFriendsAddedCallback( int newFriendsAdded ) {
JSONObject jTournamentFriends = new JSONObject(JSONObject.Type.OBJECT);
jTournamentFriends.AddField( "NewFriendsAdded", BucketGeneral( newFriendsAdded ) );
AddNamedEvent ( "Community.TournamentAddedFriend", jTournamentFriends );
}
#endregion
#region Abstract & Virtual Methods
/// <summary>
/// Gets the tracking Object
/// </summary>
protected abstract XMGTracking GetTracking();
/// <summary>
/// Registers the game listeners.
/// </summary>
protected abstract void RegisterGameListeners();
/// <summary>
/// Unregister the game listeners.
/// </summary>
protected abstract void UnRegisterGameListeners();
/// <summary>
/// Logs the game user properties.
/// </summary>
/// <param name='jUserProperties'>
/// The JSON Object with the user properties.
/// </param>
protected abstract void LogGameUserProperties(JSONObject jUserProperties);
/// <summary>
/// A session Is Started
/// </summary>
protected abstract void SessionStart();
/// <summary>
/// The session has ended
/// </summary>
protected abstract void SessionEnd();
/// <summary>
/// Gets the user start time.
/// Default to Now
/// Override to provide the user's start time
/// </summary>
/// <returns>
/// The user start time.
/// </returns>
protected virtual long GetUserStartTime() {
return (long)XMGUnityUtil.ToUnixTimeFromUniversal( DateTime.UtcNow );
}
#endregion
#region Private Methods
/// <summary>
/// Starts the swrve timer.
/// </summary>
private void StartSwrveTimer() {
StartCoroutine( StartEventsTimer());
}
/// <summary>
/// Logs the user properties.
/// And Calls abstract LogGameUserProperties
/// </summary>
private void LogUserProperties() {
JSONObject jUserProperties = new JSONObject(JSONObject.Type.OBJECT);
// Add here Common User Properties
XMGTracking tracking = GetTracking();
jUserProperties.AddField( UP_FIRST_LAUNCH_DAY, tracking.FirstLaunch.Day );
jUserProperties.AddField( UP_FIRST_LAUNCH_MONTH, tracking.FirstLaunch.Month );
jUserProperties.AddField( UP_FIRST_LAUNCH_YEAR, tracking.FirstLaunch.Year );
jUserProperties.AddField( UP_FIRST_LAUNCH_EPOCH, XMGUnityUtil.ToUnixTimeFromUniversal( tracking.FirstLaunch ).ToString() );
jUserProperties.AddField( UP_TOTAL_TAPJOY_REWARDS, tracking.TotalTapJoyPointsEarned );
jUserProperties.AddField( UP_TOTAL_SPONSORPAY_REWARDS, tracking.TotalSponsorPayPointsEarned );
jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLICKED, tracking.TotalChartBoostAdsClicked );
jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLOSED, tracking.TotalChartBoostAdsClosed );
jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_EARNED, tracking.SoftCurrencyEarned );
jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_EARNED, tracking.HardCurrencyEarned );
jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_SPENT, tracking.SoftCurrencySpent );
jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_SPENT, tracking.HardCurrencySpent );
#if UNITY_IPHONE
jUserProperties.AddField( UP_VERION, EtceteraTwoBinding.getInfoPlistValue("CFBundleVersion") );
#elif UNITY_ANDROID
jUserProperties.AddField( UP_VERION, androidBundleVersion );
#endif
jUserProperties.AddField( UP_TOTAL_FRIENDS, tracking.TotalFriends );
jUserProperties.AddField( UP_TOTAL_UNIQUE_DAYS_PLAYED, tracking.TotalUniqueDaysPlayed );
if( string.IsNullOrEmpty( myExtIP ) ) {
jUserProperties.AddField( UP_IP, myExtIP );
}
LogGameUserProperties(jUserProperties);
swrveComponent.Swrve_AddUserUpdateEvent(jUserProperties.ToString());
}
#endregion
#region Authentication
/// <summary>
/// Logs in to Flurry and SWRVE
/// </summary>
void AnalyticsLogin() {
FlurryLogin();
if( currentSwrveId != null ) {
if( !currentSwrveId.Equals( XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId ) ) {
Debug.Log("There is already a swrve account signed in!!! You must close the session first!");
AnalyticsLogout();
} else {
// This swrve user is already logged in, bail out of this function
Debug.Log("Attempt to log into swrve twice with the same id... Ignoring.");
return;
}
}
SwrveLogin(XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId);
}
/// <summary>
/// Logout from Flurry and SWRVE
/// </summary>
private void AnalyticsLogout() {
FlurryLogout();
if(currentSwrveId == null) {
Debug.LogError("There is no active swrve user!!! Cannot close session.");
return;
}
SwrveLogout();
}
/// <summary>
/// Starts Flurry Session
/// </summary>
private void FlurryLogin() {
#if UNITY_IPHONE
FlurryAccess.startSession(flurryAuth.FlurryKey);
#elif UNITY_ANDROID
FlurryAccess.onStartSession(flurryAuth.FlurryKey, true, true );
#endif
}
/// <summary>
/// Logouts from Flurry
/// </summary>
private void FlurryLogout() {
#if UNITY_ANDROID
FlurryAccess.onEndSession();
#endif
}
/// <summary>
/// Login to Swrve
/// </summary>
/// <param name='swrveId'>
/// The Swrve identifier.
/// </param>
private void SwrveLogin(string swrveId) {
currentSwrveId = swrveId;
int gameId = 0;
string apiKey = "";
if (buildType == BuildTypeEnum.Analytics) {
gameId = swrveAuth.AnalyticsGameID;
apiKey = swrveAuth.AnalyticsAPIKey;
} else if (buildType == BuildTypeEnum.SandBox) {
gameId = swrveAuth.SandboxGameID;
apiKey = swrveAuth.SandboxAPIKey;
} else if( buildType == BuildTypeEnum.Production ) {
gameId = swrveAuth.ProductionGameID;
apiKey = swrveAuth.ProductionAPIKey;
}
InitializeSWRVEComponent(gameId,
apiKey,
swrveAuth.PersonalKey,
swrveAuth.URLABTest
);
}
/// <summary>
/// Logout from Swrve
/// </summary>
private void SwrveLogout() {
// Make sure to close the Swrve session
SessionEnd();
swrveComponent.Swrve_AddSessionEnd();
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
currentSwrveId = null;
}
/// <summary>
/// Initializes the SWRVE component.
/// </summary>
/// <param name='gameId'>
/// The Game identifier.
/// </param>
/// <param name='apiKey'>
/// The API key.
/// </param>
/// <param name='personalKey'>
/// Personal key.
/// </param>
/// <param name='abURL'>
/// The Ab Test server URL.
/// </param>
private void InitializeSWRVEComponent(int gameId, string apiKey, string personalKey, string abURL) {
swrveComponent.gameId = gameId;
swrveComponent.apiKey = apiKey;
swrveComponent.abTestServer = abURL;
// Setup swrve for new user
swrveComponent.Swrve_AddSessionStart();
SessionStart();
if(isFetchingABParams == false) {
isFetchingABParams = true;
StartCoroutine(GetAllResourcesWithABTests());
}
}
#endregion
#region A/B Helpers
/// <summary>
/// Returns the most recent JSON configuration for a resource from swrve or NULL if there is none.
/// This means it could return cached data that is old. If you want to retireve the latest from swrve.
/// </summary>
/// <param name="itemId">
/// A <see cref="System.String"/>
/// </param>
/// <returns>
/// A <see cref="JSONObject"/>
/// </returns>
public JSONObject GetItemParams(string itemId) {
JSONObject item = null;
swrveResources.TryGetValue(itemId, out item);
return item;
}
/// <summary>
/// Gets all resources with AB tests.
/// </summary>
/// <returns>
/// The all resources with AB tests.
/// </returns>
private IEnumerator GetAllResourcesWithABTests() {
string swrveURLRequest = string.Format(swrveAuth.URLABTestResources + "?api_key={0}&user={1}&joined={2}", swrveComponent.apiKey, currentSwrveId, GetUserStartTime() );
WWW itemRequest = new WWW(swrveURLRequest);
yield return itemRequest; // Yield until a result is returned.
if(itemRequest.error != null) {
Debug.LogWarning("Error attempting to fetch Swrve A/B Resource data: " + itemRequest.error);
LoadCachedConfig();
// Bail!
yield break;
}
// Process all the resources into a hashmap ( key: resource name, value: json object )
JSONObject jResources = new JSONObject(itemRequest.text);
if(jResources == null || jResources.type == JSONObject.Type.NULL) {
// Bad data from swrve, abort!
Debug.LogError("Bad A/B resource data from swrve!");
LoadCachedConfig();
yield break;
} else {
//Debug.LogWarning( "Data from SWRVE: " + jResources );
}
XMGSaveLoadUtils.Instance.SaveEncryptedField(CACHED_CONFIG_KEY, itemRequest.text); // Replace the old cached config.
ParseResources(jResources);
if( swrveResources.Count > 0 ) {
ItemsConfigurationUpdatedEvent();
}
isFetchingABParams = false;
}
/// <summary>
/// Loads the cached swrve config file
/// </summary>
private void LoadCachedConfig() {
string cached = XMGSaveLoadUtils.Instance.LoadEncryptedField(CACHED_CONFIG_KEY);
if(string.IsNullOrEmpty(cached)) {
// There was no cached data
return;
}
// Apply the cached resources so it doesn't block updates.
JSONObject jResources = new JSONObject(cached);
ParseResources(jResources);
if( swrveResources.Count > 0 ) {
ItemsConfigurationUpdatedEvent();
}
}
/// <summary>
/// Expects a JSON array of resources.
/// </summary>
/// <param name="resources">
/// A <see cref="JSONObject"/>
/// </param>
private void ParseResources(JSONObject jResources) {
swrveResources.Clear();
//Debug.Log( "Parsing SWRVE Resources: " + jResources );
if(jResources == null || jResources.type != JSONObject.Type.ARRAY) {
// Bad data from swrve, abort!
Debug.LogError("Could not parse resource data, unexpected format!");
return;
}
foreach(JSONObject data in jResources.list) {
JSONObject key = data.GetField("uid");
if(key == null || key.type != JSONObject.Type.STRING || string.IsNullOrEmpty( JSONObject.GetString(key) ) ) {
// Bad item, on to the next
Debug.LogWarning("Bad item, no property 'uid' in " + data.print());
continue;
}
// Add the resource over top of any precached configuration
swrveResources[JSONObject.GetString(key)] = data;
}
}
#endregion
#region Event Handling
/// <summary>
/// Adds the named event.
/// </summary>
/// <param name='name'>
/// The EventName.
/// </param>
public void AddNamedEvent(string name) {
swrveComponent.Swrve_AddNamedEvent(name, "{}");
#if UNITY_IPHONE
FlurryAccess.logEvent(name, false);
#elif UNITY_ANDROID
FlurryAccess.logEvent(name);
#endif
}
/// <summary>
/// Adds a named event with Payload
/// </summary>
/// <param name='name'>
/// The EventName.
/// </param>
/// <param name='payload'>
/// Payload.
/// </param>
public void AddNamedEvent(string name, JSONObject payload) {
string data = payload.ToString();
if(!string.IsNullOrEmpty(data)) {
swrveComponent.Swrve_AddNamedEvent(name, data);
} else {
swrveComponent.Swrve_AddNamedEvent(name, "{}");
}
#if UNITY_IPHONE
FlurryAccess.logEventWithParameters(name, payload.ToDictionary(), false);
#elif UNITY_ANDROID
FlurryAccess.logEvent(name, payload.ToDictionary());
#endif
}
/// <summary>
/// Infinite Loop to send events in the queue
/// </summary>
private IEnumerator StartEventsTimer() {
float timeInterval = Time.realtimeSinceStartup;
while (true) {
if(Time.realtimeSinceStartup - timeInterval >= sendEventsEverySeconds) {
SendEvents();
timeInterval = Time.realtimeSinceStartup;
}
yield return new WaitForSeconds( 1 );
}
}
/// <summary>
/// Sends the events to SWRVE
/// </summary>
private void SendEvents() {
if(currentSwrveId != null) {
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
}
}
#endregion
#region UI Events
/// <summary>
/// Logs a UI screen visit event.
/// Records the current screen, the parent screen, and the grandparent screen.
/// </summary>
/// <param name='screenName'>
/// Screen name.
/// </param>
public void ScreenVisitEvent(string screenName) {
JSONObject eventParams = new JSONObject(JSONObject.Type.OBJECT);
if(parentScreen != null) {
eventParams.AddField("parent", parentScreen);
}
if(grandParentScreen != null) {
eventParams.AddField("grandParent", grandParentScreen);
}
AddNamedEvent("UI.Flow." + screenName, eventParams);
eventParams.AddField("screen", screenName);
AddNamedEvent("UI.Flow.Screens", eventParams);
grandParentScreen = parentScreen;
parentScreen = screenName;
}
/// <summary>
/// Logs a UI button pressed event
/// </summary>
/// <param name='buttonName'>
/// The Button name.
/// </param>
public void ButtonPressedEvent( string buttonName ) {
AddNamedEvent("UI.ButtonPressed." + buttonName );
}
#endregion
#region Purchase Events
/// <summary>
/// Sends an analytics buy in event for purchasing inapps.
/// </summary>
/// <param name='rewardCurrency'>
/// Reward currency
/// </param>
/// <param name='rewardAmount'>
/// Amount of rewardCurrency
/// </param>
/// <param name='localCost'>
/// The real money price in local currency ( e.g 0.99 )
/// </param>
/// <param name='localCurrency'>
/// CAD, USD, etc...
/// </param>
public void AddBuyInEvent(string rewardCurrency, int rewardAmount, float localCost, string localCurrency, string itemID ) {
swrveComponent.Swrve_AddBuyInEvent( "", rewardCurrency, rewardAmount, Mathf.Round(localCost* 100)/100, localCurrency );
AddNamedEvent( "Purhases.IAP" );
AddNamedEvent( "Purhases.IAP.HardCurrency" + itemID );
}
/// <summary>
/// Analytics event when a user purchases an item in-game.
/// </summary>
/// <param name='itemID'>
/// The UID for the Item
/// </param>
/// <param name='currency'>
/// The currency
/// </param>
/// <param name='itemCost'>
/// Item cost.
/// </param>
/// <param name='itemQuantity'>
/// Item quantity.
/// </param>
public void AddPurchaseEvent( string itemID, string currency, int itemCost, int itemQuantity ) {
swrveComponent.Swrve_AddPurchaseItemEvent( itemID, currency, itemCost, itemQuantity );
}
/// <summary>
/// Adds a purchase conversion event.
/// </summary>
/// <param name='itemID'>
///The unique Item ID
/// </param>
public void AddPurchaseConversionPackEvent( string itemID ) {
AddNamedEvent( "Purchases.HardCurrency.SoftCurrency" );
AddNamedEvent( "Purhases.HardCurrency.SoftCurrency." + itemID );
}
#endregion
}
(我只是使用类似C的伪代码。您必须翻译成您正在使用的任何语言。)
然后,按顺序浏览列表。对于每个节点,将其添加到字典中,并将其添加到父节点。那就是:
class Node
{
int Id;
string Name;
int Parent;
List<node> Children;
}
此时您有一个字典,其中包含顶级的所有项目。但是有孩子的节点也会填充它们。然后,您可以通过遍历字典输出树视图,并仅输出父项为0的节点。即:
for each item in list
newNode = CreateNewNode(id, name, parent);
dictionary.Add(id, newNode);
if (parent != 0)
dictionary[parent].Children.Add(newNode);
// Output initial stuff here
// and then output all top-level nodes
for each node in dictionary
if (node.Parent == 0)
{
outputNode(node, 0);
}
// Output closing braces here
是递归的:
outputNode