一、功能:
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参数,来修改数据成员。