Unity功能——实现根据关键词(可以非数值类型)进行排序与搜索

28 篇文章 5 订阅
26 篇文章 1 订阅
一、功能:
1、排序:根据最新更新的时间进行排序;
2、搜索:根据关键词搜索;
(1)非全字匹配:

只要包含关键词里的内容就是搜索结果之一;

如关键词:srty;搜索结果:s23,rcc,rysff,srty等等;

(2)全字匹配:

包含完整关键词才匹配,并根据和关键词匹配程度,排序搜索结果;

如关键词:srty;搜索结果:srty,srtykkk,016srty,pouucsrtyLt等等;

(3)匹配规则:

        相似度: +/- [(X.Y)+1];

        1)X表示匹配词里关键词前面字符的个数;

        2)Y表示匹配词里关键词后面字符和个数;

        3)区分大小写,相似度为(1+X.Y),完全匹配,则为1;

        4)不区分大小写,相似度为-(1+X.Y),完全匹配,则为-1;

二、数据结构设置
1、数据格式

        每条数据有唯一id、name、version,

        根据name和versionNumber,看是否包含搜索关键词;

public string id { get; set; }
public string name { get; set; }
public string inform { get; set; }
public string leastTime { get; set; }
2、查找数据的字典

        存放数据的字典,可快速通过key值定位目标数据和目标物体;

        使用列表记录key值,可根据Data里的Time对key值进行排序,遍历列表即可按顺序设置物体;

/// <summary>
/// projectItemDatas[index,projectData];
/// index:从0开始
/// 把一条条项目数据用字典链接,方便查找
/// </summary>
public Dictionary<int, Data> ProjectDatas = new Dictionary<int, Data>();

//GameObject/Item:当前要查找的目标数据
private KeyValuePair<int, Data> curItemData;

//记录此Item的data在ProjectDatas里的索引号
public Dictionary<int, RectTransform> projectItemDic = new Dictionary<int, RectTransform>();
3、存储所有数据的类

        使用class类记录持久类数据,可方便数据本地存储或与服务器交互存储数据;

//Data:
[Serializable]
public class Data
{
    public string id { get; set; }
    public string name { get; set; }
    public string versionNumber { get; set; }
    public string leastTime { get; set; }
}
4、记录匹配程度的结构体

        使用struct结构,记录轻量、临时数据,方便读取/修改等使用;

初始搜索列表:
//当前正在用的PageBtn个数(可能少于等于总PageBtn数,即少于等于pageBtnDic个数)
private int usingPageBtnCount;
private List<int> searchList = new List<int>();

优化后搜索列表:
///根据搜索条件(如Data的name和version值),
///记录包含关键词的Item与关键词的相关程度、
///所在位置(相似度里大小位置,方便根据相似度进行排序),字典里的key值(方便找到对应Item))
public struct SearchedItem
{
    /// <summary>
    /// 当前Item匹配关键词,对应的相似度大小
    /// </summary>
    public float similarityValue { get; set; }

    /// <summary>
    /// 当前Item的相似度的大小在列表里排第几
    /// </summary>
    public int similarityIndex { get; set; }

    /// <summary>
    /// 当前Item在字典里的索引值
    /// </summary>
    public int itemIndex { get; set; }
}
5、辅助更新匹配程度的List列表

        方便动态元素的增删修改;

//Order:
//根据排序条件(如Data的leastTime值),对字典里的key排序,并用列表记录排序结果
private List<int> projectKeyList = new List<int>();
三、根据数据实例化对象

根据获取到的数据,实例化对应GameObject / Item

1、解析数据
//Data    
private  void InitDatas(JArray datas)
    {
        int index = 0;
        foreach (var item in datas)
        {
            Data _datas = new Data();
            string name = item["name"].ToString();
            _projectItemDatas.id = item["id"].ToString();
            _projectItemDatas.versionNumber = item["versionNumber"].ToString();
            string leastTime = item["leastTime"].ToString();
            ProjectDatas.Add(index, _projectItemDatas);
            index++;
        }
    }
2、清理Item原来的子物体
/// <summary>
/// 清除父物体下面的所有子物体
/// </summary>
/// <param name="parent"></param>
private void ClearChilds(Transform parent)
{
    if (parent.childCount > 0)
    {
        for (int i = 0; i < parent.childCount; i++)
        {
            Destroy(parent.GetChild(i).gameObject);
        }
    }
}
 3、设置Item新绑定的子物体
public void AddProjectItem(KeyValuePair<int, Data> itemData)
{
    RectTransform item = Instantiate(projectItemPrefab, projectItemParent);
    item.Find("Name").GetComponent<TMP_Text>().text = itemData.Value.name;
    item.Find("Version").GetComponent<TMP_Text>().text = itemData.Value.versionNumber;
    item.Find("Time").GetComponent<TMP_Text>().text = itemData.Value.leastTime;
    item.GetComponent<Button>().onClick.AddListener(delegate { ClickProjectItem(itemData); });
    item.gameObject.SetActive(false);
    projectItemDic.Add(itemData.Key, item);
}
4、初始化Item
private void InitedItem()
{
    projectItemDic.Clear();
    ClearChilds(projectItemParent);
    foreach (var itemData in ProjectDatas)
    {
        AddProjectItem(itemData);
    }
    projectKeyList.Clear();
    for (int i = 0; i < projectItemDic.Count; i++)
    {
        projectKeyList.Add(i);
    }
}
四、对Item进行排序功能

先清空显示列表,然后对列表里的Item进行排序,再根据排序依次把Item移回显示列表

可对全部Item进行排序,也可指定Item进行排序

1、对输入的关键词比大小
(1)关键词是时间:
/// <summary>
/// 处理string类型的time,并获得其int类型的list
/// </summary>
/// <param name="a">time格式:“2021/12/30 17:59 am”</param>
/// <returns>返回格式“[2021,12,30,17,59,00]”</returns>
private List<int> GetTimeList(string a)
{
    List<int> listValue = new List<int>();
    a = Regex.Replace(a, @"\s", ",");//空格转换为“,”
    a = a.Replace(":", ",");
    a = a.Replace("/", ",");
    a = a.Replace("am", "");//删除am字符
    string[] newA = a.Split(',');
    for (int i = 0; i < newA.Length; i++)
    {
        int index = i;
        if (string.IsNullOrEmpty(newA[index])) newA[index] = "00";
        listValue.Add(int.Parse(newA[index]));
    }
    return listValue;
}
(2)关键字是字符串:
/// <summary>
/// 判断a是否大于等于b
/// </summary>
/// <param name="a">字符串a</param>
/// <param name="b">字符串b</param>
/// <returns></returns>
private bool CompareString(string a, string b)
{
    int isCompare = 0;
    List<int> aList = GetTimeList(a);
    List<int> bList = GetTimeList(b);
    int len = aList.Count <= bList.Count ? aList.Count : bList.Count;
    for (int i = 0; i < len; i++)
    {
        int index = i;
        if (aList[index] > bList[index]) isCompare = 1;
        if (aList[index] < bList[index]) isCompare = -1;
        if (isCompare != 0) break;
    }
    return (isCompare == -1) ? false : true;
}
2、根据更新/创建时间进行排序
//排序列表
private void SetItemOrder(List<int> orderList)
{
    //根据最新时间进行排序
    SortProjectKeyList(orderList);
    for (int i = 0; i < orderList.Count; i++)
    {
        projectItemDic[orderList[i]].SetParent(projectItemParent);
    }
}
3、选择Item进行排序
/// <summary>
/// 选择排序指定列表
/// </summary>
/// <param name="itemKeyList"></param>
private void SortProjectKeyList(List<int> itemKeyList)
{
    for (int i = 0; i < itemKeyList.Count - 1; i++)
    {
        string maxVal = projectItemDic[itemKeyList[i]].Find("Time").GetComponent<TMP_Text>().text;
        int maxIndex = i;
        for (int j = i + 1; j < itemKeyList.Count; j++)
        {
            string nowValue = projectItemDic[itemKeyList[j]].Find("Time").GetComponent<TMP_Text>().text;
            if (!CompareString(maxVal, nowValue))
            {
                maxVal = nowValue;
                maxIndex = j;
            }
       }
       int temp = itemKeyList[i];
       itemKeyList[i] = itemKeyList[maxIndex];
       itemKeyList[maxIndex] = temp;
    }
}
4、将排序好的Item依次放到显示面板
public void ClickRankBtn()
{
    //将所有Item移到临时父物体上
    for (int i = 0; i < projectItemDic.Count; i++)
    {
        projectItemDic[i].SetParent(tempProjectParents);
    }
    
    //先对key值列表排序,再按顺序依次将目标Item移回projectItemParent上
    if (!isSearch) SetItemOrder(projectKeyList);//对所有数据进行排序
    else SetItemOrder(searchedItems);//对搜索结果列表进行排序
    
    //将剩下的Item全部移回projectItemParent
    for (int i = 0; i < projectItemDic.Count; i++)
    {
        if (projectItemDic[i].parent != projectItemParent)
        projectItemDic[i].SetParent(projectItemParent);
    }
}
五、搜索匹配功能

根据名称和版本号搜索:

1、非全字匹配

只要包含关键词里的字符,不一定全字匹配,就都属于目标对象

EG:关键词:测试项目001;搜索结果:测试,001,项目,1,测试1;

(1)判断是否包含关键词里的内容
/// <summary>
/// 判断是否包含搜索目标(不区分大小写)
/// </summary>
/// <param name="key">输入的/查找匹配的关键词</param>
/// <param name="str">和关键词进行匹配的字符串</param>
/// <param name="stringComparison">StringComparison 枚举定义一些规则;OrdinalIgnoreCase 忽略大小写</param>
/// <returns>返回结果:“True”为包含关键词,“False”为不包含关键词</returns>
private bool ContainKey(string key, string str)
{
    bool isExit = false;
    char[] arrKey = key.ToCharArray();
    for (int i = 0; i < arrKey.Length; i++)
    {
        if (str.Contains(arrKey[i]))
        {
            isExit = true;
            break;
        }
    }
    return isExit;
}
(2)进行匹配搜索
private void GetSearchList(string key)
{
    searchList.Clear();
    if (!string.IsNullOrEmpty(key))
    {
        foreach (var item in projectKeyList)
        {
            string itemName = projectItemDic[item].Find("Name").GetComponent<TextMeshProUGUI>().text;
            string itemVersion = projectItemDic[item].Find("Version").GetComponent<TextMeshProUGUI>().text;
            string itemCharacter = itemName + itemVersion;//该Item里出现的所有字符
            if (ContainKey(key, itemCharacter)) searchList.Add(item);
        }
    }
    else
    {
        foreach (var index in projectKeyList)
        {
            searchList.Add(index);
        }
    }
}
2、全字匹配

并根据和关键词匹配程度,排序搜索结果

EG:关键词:测试0;搜索结果:测试0,测试01,项目测试0,项目测试035;

(1)对比关键词,获得相似度
//相似度为 +/-[(X.Y)+1];
//X表示匹配词里关键词前面字符的个数;
//Y表示匹配词里关键词后面字符和个数;
//区分大小写,相似度为(1+X.Y),完全匹配,则为1;
//不区分大小写,相似度为-(1+X.Y),完全匹配,则为-1;
/// <summary>
/// 根据searchedItems已有元素的相似度和该Item的相似程度,获得该Item的相似度
/// </summary>
/// <param name="strList">当前Item的搜索范围</param>
/// <param name="key">搜索关键词</param>
/// <returns>搜索范围与关键词的相似程度值</returns>
private float GetSimilarity(List<string> strList, string key)
{
    float similarity = 0;//similarity>=1,或<=-1;
    int firstIndex=-1, lastIndex=-1;
    foreach (var str in strList)
    {
        if (str.Contains(key))
        {
            firstIndex = str.IndexOf(key);//Index区分大小写,index>=0;
            lastIndex = str.Length - (firstIndex + key.Length);
            similarity = firstIndex + 1 + lastIndex / 10;
            break;
        }
        else if (str.ToLower().Contains(key.ToLower()))//统一大小写后获取判断
        {
            firstIndex = str.ToLower().IndexOf(key.ToLower());
            lastIndex = str.Length - (firstIndex + key.Length);
            similarity = -(firstIndex + 1 + lastIndex / 10);
            break;
        }
    }
    return similarity;
}
(2)进行匹配并设置相似度
/// <summary>
/// 获取包含关键词的itemIndex,并设置好其相似度
/// </summary>
/// <param name="strList">当前item的搜索范围</param>
/// <param name="key">关键词</param>
/// <param name="itemIndex">当前item的索引值</param>
private void AddContainKeyItemWithSimilarity(List<string> strList, string key, int itemIndex)
{
    //对搜索范围进行关键词匹配
    foreach (var str in strList)
    {
        if (str.ToLower().Contains(key.ToLower()))//匹配成功
        {
            SearchedItem item = new SearchedItem();
            item.itemIndex = itemIndex;
            item.similarityIndex = itemIndex;
            //获取匹配相似度
            item.similarityValue = GetSimilarity(strList, key);
            searchedItems.Add(item);
            break;
        }
    }
}
(3)根据相似度进行排序
//冒泡排序
//根据相似值大小,进行排序
//绝对值越大,越不相似,序号越大;索引值从0开始;
//绝对值相同,负值的序号比正值的序号大;
private void SortBySimilarity()
{
    int temp;//交换标志
    bool exChange;
    for (int i = 0; i < searchedItems.Count; i++)
    {
        exChange = false;
        for (int j = searchedItems.Count -2; j <=i; j--)
        {
            float jValue = Math.Abs(searchedItems[j].similarityValue);
            float jNextValue = Math.Abs(searchedItems[j + 1].similarityValue);

            if (jNextValue == jValue)
            {
                //负值序号大于正值序号
                if(searchedItems[j + 1].similarityValue> searchedItems[j].similarityValue)
                {
                    temp = searchedItems[j + 1].similarityIndex;
                    ChangeSimilarityIndex(searchedItems[j + 1], j + 1, searchedItems[j].similarityIndex);
                    ChangeSimilarityIndex(searchedItems[j], j, temp);
                    exChange = true;
                }
           }
           else if (jNextValue < jValue)//绝对值越大,序号越大
           {
                temp = searchedItems[j + 1].similarityIndex;
                ChangeSimilarityIndex(searchedItems[j + 1], j + 1, searchedItems[j].similarityIndex);
                ChangeSimilarityIndex(searchedItems[j], j, temp);
                exChange = true;
           }
        }
        if (!exChange) break;//本趟排序未发生交换,提前终止算法
    }
}
(4)对相似度相同的,根据时间进行排序

参考上面关键字是时间排序,这里省略

/// <summary>
/// 对相似度相同的Item根据时间进行排序
/// </summary>
private void SortSearchByTime()
{
    //TODO:
}
(5) 根据相似的,设置相似度索引值
private void ChangeSimilarityIndex(SearchedItem item,int listIndex,int newSimilarityIndex)
{
    SearchedItem newItem = new SearchedItem();
    newItem.similarityIndex = newSimilarityIndex;
    newItem.itemIndex = searchedItems[listIndex].itemIndex;
    newItem.similarityValue = searchedItems[listIndex].similarityValue;
    searchedItems[listIndex] = newItem;
}
(6)对列表进行搜索排序
private void GetSearchList(string key)
{
    searchedItems.Clear();
    if (!string.IsNullOrEmpty(key))
    {
        List<string> itemCharaterList = new List<string>();
        foreach (var item in projectKeyList)
        {
            itemCharaterList.Clear();
            string itemName = projectItemDic[item].Find("Name").GetComponent<TextMeshProUGUI>().text;
            string itemVersion = projectItemDic[item].Find("Version").GetComponent<TextMeshProUGUI>().text;
            itemCharaterList.Add(itemName);
            itemCharaterList.Add(itemVersion);
            //将匹配成功的目标Item存入list,并获取其相似度
            AddContainKeyItemWithSimilarity(itemCharaterList, key, item);
        }
        //根据相似度大小,设置相似度序列
        if (searchedItems.Count > 1)
        {
            for (int i = 0; i < searchedItems.Count; i++)
            {
                int index = i;
                ChangeSimilarityIndex(searchedItems[index], index, index);
            }
            SortBySimilarity();
        }
        //对相似度相同的Item根据时间进行排序
        if (searchedItems.Count > 1) SortSearchByTime();
    }
    else
    {
        foreach (var index in projectKeyList)
        {
            SearchedItem item = new SearchedItem();
            item.itemIndex = index;
            item.similarityIndex = index;
            item.similarityValue = index;
            searchedItems.Add(item);
        }
    }
}

六、优化
1、struct优化class

这里SearchItem使用List<struct>结构,但是在初始赋值列表后,之后每次搜索时,

不论是计算赋值相似度还是排序相似度,设置相似度索引值,都要进行装箱拆箱操作,就挺麻烦。

所以后续是把struct类型改成class类型,简化操作;

private void ChangeSimilarityIndex(SearchedItem item,int listIndex,int newSimilarityIndex)
{
    SearchedItem newItem = new SearchedItem();
    newItem.similarityIndex = newSimilarityIndex;
    newItem.itemIndex = searchedItems[listIndex].itemIndex;
    newItem.similarityValue = searchedItems[listIndex].similarityValue;
    searchedItems[listIndex] = newItem;
}
2、优化修改属性方法

从直接覆盖,改为通过反射,引用修改单个属性;

上面修改列表里的某个结构体的某个属性,直接简单粗暴的使用新建结构体赋值覆盖的方法。

还有其他方法可以修改结构体里的数据,

如:反射,Object传参(先把结构体转换为object,运行完成后再将object 赋值给 struct);

最正规的应该是使用ref,out参数,来修改数据成员。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值