【问题标题】:How can I do a "deep compare" or "diff" on two Structs?如何对两个结构进行“深度比较”或“差异”?
【发布时间】:2011-03-09 23:52:20
【问题描述】:

(这是一个冷融合问题)

我有两个不同的结构,它们可能包含也可能不包含相同的数据,我想看看它们是否包含!我的结构将始终包含简单的值(数字、字符串或布尔值),因为它们是使用 DeserializeJSON 创建的,所以希望这可以轻松完成。

我找到了 Ben Nadel 的帖子 here,但该技术似乎对我不起作用。这是我迄今为止尝试过的(那里有一些 cfwheels 代码):

itemA = DeSerializeJSON(model("itemsnapshot").findByKey(4).json);
itemB = DeSerializeJSON(model("itemsnapshot").findByKey(5).json);

StructDelete(itemA,"updatedAt");
StructDelete(itemB,"updatedAt");
StructDelete(itemA,"createdAt");
StructDelete(itemB,"createdAt");

writedump(itemA);
writedump(itemB);

out = itemA.Equals(itemB);
writedump(out);

结果如下:

Struct
code string C112
companyid number 1
cost number 5000
deletedAt string 
description string Nightstand
id number 70634
itemtypeid string 13
projectid number 8
unittypeid string

Struct
code string C112
companyid number 1
cost number 5000
deletedAt string 
description string Nightstand
id number 70634
itemtypeid string 13
projectid number 8
unittypeid string 

boolean false

因此,正如您将在上面看到的,尽管 Struct 中的数据看起来完全匹配,但它们没有通过 Equals() 测试。

还有其他人成功做到这一点吗?

【问题讨论】:

  • 嗯.. 错字?因为“id number 70634”和“idnumber 70634”不一样

标签: java coldfusion railo cfwheels cfml


【解决方案1】:
if(serializeJSON(itemA) eq serializeJSON(itemB))
  //They match!
else
  //They don't!

您已经在使用 JSON 来操作这些,您应该继续使用它。 不是说十年后有人在乎,但这种方法对我有用。

【讨论】:

    【解决方案2】:

    这是我快速拼凑的东西。它有参数来确定是否对值和键进行区分大小写的比较。在某种实用程序 CFC 中抛出这两个函数(StructEquals()ArrayEquals())。

    限制:不适用于包含查询或对象的结构/数组。

    <cffunction name="StructEquals" access="public" returntype="boolean" output="false"
                hint="Returns whether two structures are equal, going deep.">
      <cfargument name="stc1" type="struct" required="true" hint="First struct to be compared." />
      <cfargument name="stc2" type="struct" required="true" hint="Second struct to be compared." />
      <cfargument name="blnCaseSensitive" type="boolean" required="false" default="false" hint="Whether or not values are compared case-sensitive." />
      <cfargument name="blnCaseSensitiveKeys" type="boolean" required="false" default="false" hint="Whether or not structure keys are compared case-sensitive." />
      <cfscript>
        if(StructCount(stc1) != StructCount(stc2))
          return false;
    
        var arrKeys1 = StructKeyArray(stc1);
        var arrKeys2 = StructKeyArray(stc2);
    
        ArraySort(arrKeys1, 'text');
        ArraySort(arrKeys2, 'text');
    
        if(!ArrayEquals(arrKeys1, arrKeys2, blnCaseSensitiveKeys, blnCaseSensitiveKeys))
          return false;
    
        for(var i = 1; i <= ArrayLen(arrKeys1); i++) {
          var strKey = arrKeys1[i];
    
          if(IsStruct(stc1[strKey])) {
            if(!IsStruct(stc2[strKey]))
              return false;
            if(!StructEquals(stc1[strKey], stc2[strKey], blnCaseSensitive, blnCaseSensitiveKeys))
              return false;
          }
          else if(IsArray(stc1[strKey])) {
            if(!IsArray(stc2[strKey]))
              return false;
            if(!ArrayEquals(stc1[strKey], stc2[strKey], blnCaseSensitive, blnCaseSensitiveKeys))
              return false;
          }
          else if(IsSimpleValue(stc1[strKey]) && IsSimpleValue(stc2[strKey])) {
            if(blnCaseSensitive) {
              if(Compare(stc1[strKey], stc2[strKey]) != 0)
                return false;
            }
            else {
              if(CompareNoCase(stc1[strKey], stc2[strKey]) != 0)
                return false;
            }
          }
          else {
            throw("Can only compare structures, arrays, and simple values. No queries or complex objects.");
          }
        }
    
        return true;
      </cfscript>
    </cffunction>
    
    <cffunction name="ArrayEquals" access="public" returntype="boolean" output="false"
                hint="Returns whether two arrays are equal, including deep comparison if the arrays contain structures or sub-arrays.">
      <cfargument name="arr1" type="array" required="true" hint="First struct to be compared." />
      <cfargument name="arr2" type="array" required="true" hint="Second struct to be compared." />
      <cfargument name="blnCaseSensitive" type="boolean" required="false" default="false" hint="Whether or not values are compared case-sensitive." />
      <cfargument name="blnCaseSensitiveKeys" type="boolean" required="false" default="false" hint="Whether or not structure keys are compared case-sensitive, if array contains structures." />
      <cfscript>
        if(ArrayLen(arr1) != ArrayLen(arr2))
          return false;
    
        for(var i = 1; i <= ArrayLen(arr1); i++) {
          if(IsStruct(arr1[i])) {
            if(!IsStruct(arr2[i]))
              return false;
            if(!StructEquals(arr1[i], arr2[i], blnCaseSensitive, blnCaseSensitiveKeys))
              return false;
          }
          else if(IsArray(arr1[i])) {
            if(!IsArray(arr2[i]))
              return false;
            if(!ArrayEquals(arr1[i], arr2[i], blnCaseSensitive, blnCaseSensitiveKeys))
              return false;
          }
          else if(IsSimpleValue(arr1[i]) && IsSimpleValue(arr2[i])) {
            if(blnCaseSensitive) {
              if(Compare(arr1[i], arr2[i]) != 0)
                return false;
            }
            else {
              if(CompareNoCase(arr1[i], arr2[i]) != 0)
                return false;
            }
          }
          else {
            throw("Can only compare structures, arrays, and simple values. No queries or complex objects.");
          }
        }
    
        return true;
      </cfscript>
    </cffunction>
    

    单元测试适合任何感兴趣的人:

    public void function test_StructEquals() {
      AssertTrue(utils.StructEquals({}, StructNew()));
      AssertTrue(utils.StructEquals({}, StructNew(), true, true));
    
      AssertFalse(utils.StructEquals({}, {"a": "b", "c": "d"}));
    
      AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"C": "D", "A": "B"}));
      AssertFalse(utils.StructEquals({"a": "b", "c": "d"}, {"C": "D", "A": "B"}, true, false));
      AssertFalse(utils.StructEquals({"a": "b", "c": "d"}, {"C": "D", "A": "B"}, false, true));
    
      AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"C": "d", "A": "b"}));
      AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"C": "d", "A": "b"}, true, false));
      AssertFalse(utils.StructEquals({"a": "b", "c": "d"}, {"C": "d", "A": "b"}, false, true));
    
      AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"c": "D", "a": "B"}));
      AssertFalse(utils.StructEquals({"a": "b", "c": "d"}, {"c": "D", "a": "B"}, true, false));
      AssertTrue(utils.StructEquals({"a": "b", "c": "d"}, {"c": "D", "a": "B"}, false, true));
    
      var stc1 = {
        "test": {
          "hello": "world",
          "goodbye": "space",
          "somearr": [
            { "a": 1, "b": 2 },
            "WORD",
            [
              { "x": 97, "y": 98, "z": 99 },
              { "i": 50, "j": 51, "k": 52 }
            ]
          ]
        }
      };
      var stc2 = {
        "test": {
          "goodbye": "space",
          "hello": "world",
          "somearr": [
            { "a": 1, "b": 2 },
            "WORD",
            [
              { "z": 99, "x": 97, "y": 98 },
              { "i": 50, "k": 52, "j": 51 }
            ]
          ]
        }
      };
    
      AssertTrue(utils.StructEquals(stc1, stc2, true, true));
      stc2.test.somearr[2] = "WOrD";
      AssertTrue(utils.StructEquals(stc1, stc2));
      AssertTrue(utils.StructEquals(stc1, stc2, false, true));
      AssertFalse(utils.StructEquals(stc1, stc2, true, false));
      stc2.test.somearr[3][1] = { "z": 99, "X": 97, "y": 98 };
      AssertTrue(utils.StructEquals(stc1, stc2));
      AssertFalse(utils.StructEquals(stc1, stc2, false, true));
      AssertFalse(utils.StructEquals(stc1, stc2, true, false));
      stc2.test.somearr[2] = "WORD";
      AssertTrue(utils.StructEquals(stc1, stc2));
      AssertFalse(utils.StructEquals(stc1, stc2, false, true));
      AssertTrue(utils.StructEquals(stc1, stc2, true, false));
    }
    
    public void function test_ArrayEquals() {
      AssertTrue(utils.ArrayEquals([], ArrayNew(1)));
      AssertTrue(utils.ArrayEquals([], ArrayNew(1), true, true));
    
      AssertFalse(utils.ArrayEquals([], [1, 2, 3]));
    
      AssertTrue(utils.ArrayEquals(['a', 'b', 'c'], ['A', 'B', 'C']));
      AssertFalse(utils.ArrayEquals(['a', 'b', 'c'], ['A', 'B', 'C'], true, false));
      AssertTrue(utils.ArrayEquals(['a', 'b', 'c'], ['A', 'B', 'C'], false, true));
    
      AssertFalse(utils.ArrayEquals(['a', 'b', 'c'], ['a', 'c', 'b']));
    
      AssertTrue(utils.ArrayEquals([1, 2, 3], [1, 2, 3]));
      AssertFalse(utils.ArrayEquals([1, 2, 3], [1, 3, 2]));
    }
    

    【讨论】:

      【解决方案3】:

      您也可以使用 CFC 继承的本机 Java 方法来执行此操作。

      isThisTrue = ObjA.equals(ObjB);
      

      【讨论】:

      • 请注意,这是我在最初的帖子中演示的技术。我的两个结构看起来相似,它产生“假”。 equals() 并非在所有情况下都有效。
      【解决方案4】:

      Coldfusion 结构中隐藏着一个方便的小方法,称为 hashCode()。虽然请记住,这是无证的。

      <cfif struct1.hashCode() Eq struct2.hashCode()>
      
      </cfif>
      

      【讨论】:

      • 不,刚刚在Railo 上试了一下,hashCode 值总是不同的。虽然... st1.toString() Eq st2.toString() 适用于 Railo ;)
      【解决方案5】:

      如果您使用的是 CF9 或 Railo 3

      ArrayContains([struct1], struct2);  //case-sensitive
      

      ArrayFindNoCase([struct1], struct2));  //case-insensitive, 0 if not the same.
      ArrayContainsNoCase([struct1], struct2); // if you use Railo
      

      【讨论】:

      • 好消息。尽管我试图在我的问题中进行简单的真假比较,但 zarko 函数生成的 diff 数组激发了我的界面更改,这将提供更多有用的功能。不过,在运行完整差异之前,我可以使用其中之一进行初步检查。
      【解决方案6】:

      这是 Ben 的解决方案,可以根据我的需要快速调整,您可以进一步调整(并希望使其更漂亮):

      <cffunction name="DiffStructs" hint="Compute the differences between two structures" access="public" output="true" returntype="array" >
              <cfargument name="First" type="struct" required="true" />
              <cfargument name="Second" type="struct" required="true" />
              <cfargument name="ignoreMissing" type="boolean" required="false" default="false" />
              <cfargument name="ignoreFirstEmptyString" type="boolean" required="false" default="false" />
              <cfargument name="ignoreSecondEmptyString" type="boolean" required="false" default="false" />
      
              <cfset var Result = arrayNew(1) >
              <cfset var Keys = structNew() >
              <cfset var KeyName = "" >
              <cfset var obj = "" >
              <cfset var firstOk = true >
              <cfset var secondOk = true >
      
              <cfloop collection="#Arguments.First#" item="KeyName">
                      <cfset Keys[KeyName]=1>
              </cfloop>
              <cfloop collection="#Arguments.Second#" item="KeyName">
                      <cfset Keys[KeyName]=1>
              </cfloop>
              <cfloop collection="#Keys#" item="KeyName">
                  <cfif NOT StructKeyExists(Arguments.First, KeyName)  >
                          <cfif NOT arguments.ignoreMissing>
                              <cfif structFind(Arguments.Second, KeyName) neq "">
                                  <cfif arguments.ignoreSecondEmptyString>
                                      <cfset obj = {  key = KeyName
                                                      ,old = ""
                                                      ,new = structFind(Arguments.Second, KeyName) } >
                                      <cfset arrayAppend(Result, obj )>
                                  </cfif>
                              </cfif>
                          </cfif>
      
                  <cfelseif NOT StructKeyExists(Arguments.Second, KeyName)>
                          <cfif NOT arguments.ignoreMissing>
                              <cfif structFind(Arguments.First, KeyName) neq "">
                                  <cfif arguments.ignoreFirstEmptyString >
                                      <cfset obj = {  key = KeyName
                                                      ,old = structFind(Arguments.First, KeyName) 
                                                      ,new = "" } >
                                      <cfset arrayAppend(Result, obj )>
                                  </cfif>
                              </cfif>
                          </cfif>
      
                  <cfelseif Arguments.First[KeyName] NEQ Arguments.Second[KeyName] >
      
                      <cfset firstOk = true >
                      <cfset secondOk = true >
      
                      <cfif structFind(Arguments.Second, KeyName) eq "">
                          <cfif arguments.ignoreSecondEmptyString>
                              <cfset firstOk = false >
                          </cfif>
                      </cfif>
      
                      <cfif structFind(Arguments.First, KeyName) eq "">
                          <cfif arguments.ignoreFirstEmptyString>
                              <cfset secondOk = false >
                          </cfif>
                      </cfif>
      
                      <cfif firstOk AND secondOk >
                          <cfset obj = {  key = KeyName
                                          ,old = structFind(Arguments.First, KeyName) 
                                          ,new = structFind(Arguments.Second, KeyName) } >
                          <cfset arrayAppend(Result, obj )>
                      </cfif>
                  </cfif>
      
              </cfloop>
      
              <cfreturn Result>
          </cffunction>
      

      【讨论】:

      • 感谢您的快速回复 - 这非常接近我的需要!
      • 我认为您希望第 25 行上的 NOT arguments.ignoreFirstEmptyString 和第 37 行上的 NOT arguments.ignoreSecondEmptyString 。如果密钥存在于一个结构中但不存在于另一个结构中,则不会返回。如果您使用NOT,那么如果它在一个结构中而不是另一个结构中,它将返回密钥。
      猜你喜欢
      • 2017-05-10
      • 2018-02-28
      • 1970-01-01
      • 2012-03-10
      • 2015-10-19
      • 2020-06-10
      • 1970-01-01
      • 2011-10-17
      • 1970-01-01
      相关资源
      最近更新 更多